Merge pull request #511 from PurpleI2P/openssl

recent changes
This commit is contained in:
orignal 2016-06-10 14:46:23 -04:00 committed by GitHub
commit ccc24337be
57 changed files with 1340 additions and 359 deletions

View File

@ -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

View File

@ -159,7 +159,7 @@ namespace client
int AddressBookFilesystemStorage::Save (const std::map<std::string, i2p::data::IdentHash>& addresses)
{
if (addresses.size() == 0) {
if (addresses.empty()) {
LogPrint(eLogWarning, "Addressbook: not saving empty addressbook");
return 0;
}

86
ChangeLog Normal file
View File

@ -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

View File

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

View File

@ -178,6 +178,13 @@ namespace config {
("bob.port", value<uint16_t>()->default_value(2827), "BOB listen port")
;
options_description i2cp("I2CP options");
i2cp.add_options()
("i2cp.enabled", value<bool>()->default_value(false), "Enable or disable I2CP")
("i2cp.address", value<std::string>()->default_value("127.0.0.1"), "I2CP listen address")
("i2cp.port", value<uint16_t>()->default_value(7654), "I2CP listen port")
;
options_description i2pcontrol("I2PControl options");
i2pcontrol.add_options()
("i2pcontrol.enabled", value<bool>()->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)
;

View File

@ -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<typename T>
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<typename T>
bool SetOption(const char *name, const T& value) {

View File

@ -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
{

View File

@ -22,9 +22,7 @@ namespace i2p
virtual bool stop();
virtual void run () {};
bool isLogging;
bool isDaemon;
bool running;
protected:

View File

@ -45,7 +45,7 @@ namespace i2p
{
bool DaemonLinux::start()
{
if (isDaemon == 1)
if (isDaemon)
{
pid_t pid;
pid = fork();
@ -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

View File

@ -45,7 +45,7 @@ namespace i2p
return false;
}
if (isDaemon == 1)
if (isDaemon)
{
LogPrint(eLogDebug, "Daemon: running as service");
I2PService service(SERVICE_NAME);

View File

@ -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<std::string, std::string> * 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<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
{
auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), m_EncryptionPublicKey, tunnels);
// sign
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO
SetLeaseSet (leaseSet);
}
}
}

View File

@ -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<const i2p::data::LocalLeaseSet> GetLeaseSet ();
std::shared_ptr<i2p::tunnel::TunnelPool> 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<std::shared_ptr<i2p::tunnel::InboundTunnel> > 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<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
@ -166,28 +161,31 @@ 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<const i2p::data::IdentityEx> 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; };
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
protected:
// I2CP
void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
private:
std::shared_ptr<ClientDestination> GetSharedFromThis ()
{ return std::static_pointer_cast<ClientDestination>(shared_from_this ()); }
{ return std::static_pointer_cast<ClientDestination>(shared_from_this ()); }
void PersistTemporaryKeys ();
private:
i2p::data::PrivateKeys m_Keys;
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;

View File

@ -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;
}
@ -406,11 +397,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)

17
HTTP.h
View File

@ -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);
/**
@ -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("/") {};
@ -89,10 +88,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 +109,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();

View File

@ -1,16 +1,8 @@
#include <cstring>
#include <cassert>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <string>
#include <atomic>
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
#include "Destination.h"
#include "HTTPProxy.h"
#include "util.h"
#include "Identity.h"
@ -20,6 +12,7 @@
#include "I2PEndian.h"
#include "I2PTunnel.h"
#include "Config.h"
#include "HTTP.h"
namespace i2p
{
@ -43,7 +36,7 @@ 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 HTTPRequestFailed(/*std::string message*/);
void RedirectToJumpService();
void ExtractRequest();
bool IsI2PAddress();
@ -56,7 +49,6 @@ namespace proxy
uint8_t m_http_buff[http_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> 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
@ -99,17 +91,10 @@ namespace proxy
/* All hope is lost beyond this point */
//TODO: handle this apropriately
void HTTPProxyHandler::HTTPRequestFailed(const char *message)
void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/)
{
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";
m_Response = ss.str();
boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response),
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));
}
@ -120,8 +105,7 @@ 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";
m_Response = response.str ();
boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response),
boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()),
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
}
@ -133,21 +117,13 @@ namespace proxy
void HTTPProxyHandler::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<int>(port);
m_path = path;
i2p::http::URL url;
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 HTTPProxyHandler::ValidateHTTPRequest()
@ -155,7 +131,7 @@ namespace proxy
if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" )
{
LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version);
HTTPRequestFailed("unsupported HTTP version");
HTTPRequestFailed(); //TODO: send right stuff
return false;
}
return true;
@ -292,13 +268,13 @@ namespace proxy
case '\n': EnterState(DONE); break;
default:
LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff));
HTTPRequestFailed("rejected invalid request");
HTTPRequestFailed(); //TODO: add correct code
return false;
}
break;
default:
LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state);
HTTPRequestFailed("invalid parser state");
HTTPRequestFailed(); //TODO: add correct code 500
return false;
}
http_buff++;
@ -354,7 +330,7 @@ namespace proxy
else
{
LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info");
HTTPRequestFailed("error when creating the stream, check logs");
HTTPRequestFailed(); // TODO: Send correct error message host unreachable
}
}
@ -367,5 +343,6 @@ namespace proxy
{
return std::make_shared<HTTPProxyHandler> (this, socket);
}
}
}

View File

@ -1,6 +1,13 @@
#ifndef HTTP_PROXY_H__
#define HTTP_PROXY_H__
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
#include "Destination.h"
namespace i2p
{
namespace proxy

View File

@ -27,156 +27,6 @@
namespace i2p {
namespace http {
const char *itoopieImage =
"<img alt=\"ICToopie Icon\" src=\"data:image/png;base64,"
"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\" />";
const char *itoopieFavicon =
"data:image/png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv"
@ -667,7 +517,7 @@ namespace http {
i2p::config::GetOption("http.auth", needAuth);
i2p::config::GetOption("http.user", user);
i2p::config::GetOption("http.pass", pass);
};
}
void HTTPConnection::Receive ()
{
@ -755,12 +605,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 +693,9 @@ namespace http {
return;
}
s << "<b>SUCCESS</b>:&nbsp;Command accepted<br><br>\r\n";
s << "<a href=\"/?page=commands\">Back to commands list</a>";
s << "<a href=\"/?page=commands\">Back to commands list</a><br>\r\n";
s << "<p>You will be redirected in 5 seconds</b>";
res.add_header("Refresh", "5; url=/?page=commands");
}
void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)

View File

@ -3,7 +3,6 @@
namespace i2p {
namespace http {
extern const char *itoopieImage;
extern const char *itoopieFavicon;
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;

587
I2CP.cpp
View File

@ -1,29 +1,139 @@
/*
* 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 <string.h>
#include <stdlib.h>
#include <openssl/rand.h>
#include "I2PEndian.h"
#include "Log.h"
#include "Timestamp.h"
#include "LeaseSet.h"
#include "ClientContext.h"
#include "Transports.h"
#include "Signature.h"
#include "I2CP.h"
namespace i2p
{
namespace client
{
I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic):
LeaseSetDestination (isPublic), m_Owner (owner), m_Identity (identity)
I2CPDestination::I2CPDestination (std::shared_ptr<I2CPSession> owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params):
LeaseSetDestination (isPublic, &params), m_Owner (owner), m_Identity (identity)
{
}
void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key)
{
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<std::shared_ptr<i2p::tunnel::InboundTunnel> > 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 ());
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)
{
auto ls = new i2p::data::LocalLeaseSet (m_Identity, buf, len);
ls->SetExpirationTime (m_LeaseSetExpirationTime);
SetLeaseSet (ls);
}
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 ();
htobe32buf (buf, len);
memcpy (buf + 4, payload, len);
msg->len += len + 4;
msg->FillI2NPMessageHeader (eI2NPData);
auto remote = FindLeaseSet (ident);
if (remote)
GetService ().post (std::bind (&I2CPDestination::SendMsg, GetSharedFromThis (), msg, remote));
else
{
auto s = GetSharedFromThis ();
RequestDestination (ident,
[s, msg, nonce](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
{
bool sent = s->SendMsg (msg, ls);
s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
}
else
s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet);
});
}
}
bool I2CPDestination::SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote)
{
auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel ();
auto leases = remote->GetNonExpiredLeases ();
if (!leases.empty () && outboundTunnel)
{
std::vector<i2p::tunnel::TunnelMessageBlock> 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);
return true;
}
else
{
if (outboundTunnel)
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<boost::asio::ip::tcp::socket> 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), m_MessageID (0), m_IsSendAccepted (true)
{
}
I2CPSession::~I2CPSession ()
{
delete[] m_NextMessage;
}
void I2CPSession::Start ()
{
ReadProtocolByte ();
}
I2CPSession::~I2CPSession ()
void I2CPSession::Stop ()
{
delete[] m_NextMessage;
Terminate ();
}
void I2CPSession::ReadProtocolByte ()
@ -34,7 +144,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 ();
@ -54,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)
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);
@ -102,17 +221,463 @@ namespace client
void I2CPSession::Terminate ()
{
if (m_Destination)
{
m_Destination->Stop ();
m_Destination = nullptr;
}
if (m_Socket)
{
m_Socket->close ();
m_Socket = nullptr;
}
m_Owner.RemoveSession (GetSessionID ());
}
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 + 1), 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::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping)
// TODO: move to Base.cpp
{
size_t offset = 0;
while (offset < len)
{
std::string param = ExtractString (buf + offset, len - offset);
offset += param.length ();
if (buf[offset] != '=')
{
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));
}
}
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);
delete[] payload;
}
I2CPServer::I2CPServer (const std::string& interface, int port)
void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len)
{
RAND_bytes ((uint8_t *)&m_SessionID, 2);
auto identity = std::make_shared<i2p::data::IdentityEx>();
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<std::string, std::string> params;
ExtractMapping (buf + offset, optionsSize, params);
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;
if (!m_Destination)
{
m_Destination = std::make_shared<I2CPDestination>(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
{
LogPrint (eLogError, "I2CP: create session signature verification falied");
SendSessionStatusMessage (3); // invalid
}
}
void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len)
{
SendSessionStatusMessage (0); // destroy
LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed");
if (m_Destination)
{
m_Destination->Stop ();
m_Destination = 0;
}
}
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];
htobe16buf (buf, m_SessionID);
buf[2] = status;
SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3);
}
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++);
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);
if (sessionID == m_SessionID)
{
size_t offset = 2;
if (m_Destination)
{
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);
}
}
else
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);
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: 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);
if (sessionID == m_SessionID)
{
uint32_t requestID = bufbe32toh (buf + 2);
//uint32_t timeout = bufbe32toh (buf + 6);
i2p::data::IdentHash ident;
switch (buf[10])
{
case 0: // hash
ident = i2p::data::IdentHash (buf + 11);
break;
case 1: // address
{
auto name = ExtractString (buf + 11, len - 11);
if (!i2p::client::context.GetAddressBook ().GetIdentHash (name, ident))
{
LogPrint (eLogError, "I2CP: address ", name, " not found");
SendHostReplyMessage (requestID, nullptr);
return;
}
break;
}
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<i2p::data::LeaseSet> leaseSet)
{
s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr);
});
}
}
else
SendHostReplyMessage (requestID, nullptr);
}
else
LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID);
}
void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> 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);
}
}
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<i2p::data::LeaseSet> 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::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
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));
}
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;
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;
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 ()
{
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->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 ());
}
}
}
void I2CPServer::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (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<boost::asio::ip::tcp::socket> 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<I2CPSession>(*this, socket);
m_Sessions[session->GetSessionID ()] = session;
session->Start ();
}
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);
}
}
}

119
I2CP.h
View File

@ -1,9 +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 I2CP_H__
#define I2CP_H__
#include <inttypes.h>
#include <string>
#include <memory>
#include <thread>
#include <map>
#include <boost/asio.hpp>
#include "Destination.h"
@ -11,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;
@ -19,27 +29,69 @@ 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;
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;
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;
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
{
eI2CPMessageStatusAccepted = 1,
eI2CPMessageStatusGuaranteedSuccess = 4,
eI2CPMessageStatusGuaranteedFailure = 5,
eI2CPMessageStatusNoLeaseSet = 21
};
// params
const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet";
const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability";
class I2CPSession;
class I2CPDestination: public LeaseSetDestination
{
public:
I2CPDestination (I2CPSession& owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic);
I2CPDestination (std::shared_ptr<I2CPSession> owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params);
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, uint32_t nonce); // called from I2CPSession
// implements LocalDestination
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Identity; };
protected:
// implements LocalDestination
std::shared_ptr<const i2p::data::IdentityEx> 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);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
private:
I2CPSession& m_Owner;
std::shared_ptr<I2CPDestination> GetSharedFromThis ()
{ return std::static_pointer_cast<I2CPDestination>(shared_from_this ()); }
bool SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
private:
std::shared_ptr<I2CPSession> m_Owner;
std::shared_ptr<const i2p::data::IdentityEx> m_Identity;
uint8_t m_EncryptionPrivateKey[256];
uint64_t m_LeaseSetExpirationTime;
};
class I2CPServer;
@ -50,8 +102,26 @@ namespace client
I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
~I2CPSession ();
void Start ();
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);
void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status);
// 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 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);
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:
@ -60,6 +130,14 @@ namespace client
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleNextMessage (const uint8_t * buf);
void Terminate ();
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<std::string, std::string>& mapping);
void SendSessionStatusMessage (uint8_t status);
void SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity);
private:
@ -69,6 +147,9 @@ namespace client
size_t m_NextMessageLen, m_NextMessageOffset;
std::shared_ptr<I2CPDestination> m_Destination;
uint16_t m_SessionID;
uint32_t m_MessageID;
bool m_IsSendAccepted;
};
typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len);
@ -77,10 +158,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<boost::asio::ip::tcp::socket> socket);
private:
I2CPMessageHandler m_MessagesHandlers[256];
I2CPMessageHandler m_MessagesHandlers[256];
std::map<uint16_t, std::shared_ptr<I2CPSession> > m_Sessions;
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
public:

View File

@ -3,7 +3,6 @@
#include "ClientContext.h"
#include "I2PService.h"
namespace i2p
{
namespace client
@ -28,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);
}
}
@ -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 ());
}
}
}
}

View File

@ -179,9 +179,7 @@ namespace data
virtual ~LocalDestination() {};
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
};

View File

@ -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++)
{
@ -231,6 +232,14 @@ namespace data
// we don't sign it yet. must be signed later on
}
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> 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 ();

View File

@ -4,6 +4,7 @@
#include <inttypes.h>
#include <string.h>
#include <vector>
#include <set>
#include <memory>
#include "Identity.h"
@ -88,14 +89,19 @@ namespace data
public:
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len);
~LocalLeaseSet () { delete[] m_Buffer; };
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 (); };
uint8_t * GetLeases () { return m_Leases; };
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); };
@ -104,7 +110,7 @@ namespace data
uint64_t m_ExpirationTime; // in milliseconds
std::shared_ptr<const IdentityEx> m_Identity;
uint8_t * m_Buffer;
uint8_t * m_Buffer, * m_Leases;
size_t m_BufferLen;
};
}

20
Log.cpp
View File

@ -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] =
{
@ -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:
@ -130,10 +131,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<std::ofstream> (path, flags);
if (os->is_open ()) {
if (os->is_open ())
{
m_Logfile = path;
m_Destination = eLogFile;
m_LogStream = os;

7
Log.h
View File

@ -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<std::ostream> s);
void SendTo (std::shared_ptr<std::ostream> 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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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 \

View File

@ -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

View File

@ -849,7 +849,8 @@ namespace data
template<typename Filter>
std::shared_ptr<const RouterInfo> 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++)
{

View File

@ -1,7 +1,6 @@
#include <string.h>
#include <fstream>
#include <sstream>
#include <boost/regex.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <openssl/ssl.h>

View File

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

View File

@ -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 ();)
{
@ -336,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
@ -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

View File

@ -606,7 +606,7 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
{
if (!m_Peers.size ()) return nullptr;
if (m_Peers.empty ()) return nullptr;
std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.begin ();
std::advance (it, rand () % m_Peers.size ());

View File

@ -358,7 +358,7 @@ namespace tunnel
std::shared_ptr<OutboundTunnel> 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<OutboundTunnel> tunnel;
for (auto it: m_OutboundTunnels)

View File

@ -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<i2p::I2NPMessage> msg);

View File

@ -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%\ (

View File

@ -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()

View File

@ -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

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i2pd (2.7.0-1) unstable; urgency=low
* updated to version 2.7.0/0.9.25
-- hagen <hagen@i2pmail.org> Wed, 18 May 2016 01:11:04 +0000
i2pd (2.2.0-2) unstable; urgency=low
* updated to version 2.2.0

1
debian/control vendored
View File

@ -4,7 +4,6 @@ Priority: extra
Maintainer: hagen <hagen@i2pmail.org>
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,

1
debian/i2pd.default vendored
View File

@ -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.

6
debian/i2pd.init vendored
View File

@ -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 $?
}

41
debian/i2pd.openrc vendored Normal file
View File

@ -0,0 +1,41 @@
#!/sbin/openrc-run
pidfile="/var/run/i2pd.pid"
logfile="/var/log/i2pd.log"
mainconf="/etc/i2pd/i2pd.conf"
tunconf="/etc/i2pd/tunnels.conf"
name="i2pd"
command="/usr/sbin/i2pd"
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"
start_stop_daemon_args="--chuid i2pd"
depend() {
need mountall
use net
after bootmisc
}
start_pre() {
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
}

259
docs/Doxyfile Normal file
View File

@ -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

View File

@ -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 ..
```

View File

@ -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

View File

@ -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.

View File

@ -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

BIN
docs/itoopieImage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -33,7 +33,6 @@
#include <mutex>
#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

View File

@ -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

View File

@ -0,0 +1,25 @@
#include <cassert>
#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;
}

View File

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