mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-30 04:37:50 +02:00
Implement #243, separate core/client (PCH support dropped for now)
This commit is contained in:
parent
bdaf2c16aa
commit
8ac9520dfd
153 changed files with 360 additions and 20020 deletions
645
client/BOB.cpp
Normal file
645
client/BOB.cpp
Normal file
|
@ -0,0 +1,645 @@
|
|||
#include <string.h>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "util/Log.h"
|
||||
#include "ClientContext.h"
|
||||
#include "BOB.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination):
|
||||
BOBI2PTunnel (localDestination),
|
||||
m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port))
|
||||
{
|
||||
}
|
||||
|
||||
BOBI2PInboundTunnel::~BOBI2PInboundTunnel ()
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::Start ()
|
||||
{
|
||||
m_Acceptor.listen ();
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::Stop ()
|
||||
{
|
||||
m_Acceptor.close();
|
||||
ClearHandlers ();
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::Accept ()
|
||||
{
|
||||
auto receiver = std::make_shared<AddressReceiver> ();
|
||||
receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (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, std::shared_ptr<AddressReceiver> receiver)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
Accept ();
|
||||
ReceiveAddress (receiver);
|
||||
}
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
|
||||
{
|
||||
receiver->socket->async_read_some (boost::asio::buffer(
|
||||
receiver->buffer + receiver->bufferOffset,
|
||||
BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
|
||||
std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
|
||||
std::placeholders::_1, std::placeholders::_2, receiver));
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||
std::shared_ptr<AddressReceiver> receiver)
|
||||
{
|
||||
if (ecode)
|
||||
LogPrint ("BOB inbound tunnel read error: ", ecode.message ());
|
||||
else
|
||||
{
|
||||
receiver->bufferOffset += bytes_transferred;
|
||||
receiver->buffer[receiver->bufferOffset] = 0;
|
||||
char * eol = strchr (receiver->buffer, '\n');
|
||||
if (eol)
|
||||
{
|
||||
*eol = 0;
|
||||
|
||||
receiver->data = (uint8_t *)eol + 1;
|
||||
receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1);
|
||||
i2p::data::IdentHash ident;
|
||||
if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident))
|
||||
{
|
||||
LogPrint (eLogError, "BOB address ", receiver->buffer, " not found");
|
||||
return;
|
||||
}
|
||||
auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident);
|
||||
if (leaseSet)
|
||||
CreateConnection (receiver, leaseSet);
|
||||
else
|
||||
GetLocalDestination ()->RequestDestination (ident,
|
||||
std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
|
||||
this, std::placeholders::_1, receiver));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE)
|
||||
ReceiveAddress (receiver);
|
||||
else
|
||||
LogPrint ("BOB missing inbound address ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver)
|
||||
{
|
||||
if (leaseSet)
|
||||
CreateConnection (receiver, leaseSet);
|
||||
else
|
||||
LogPrint ("LeaseSet for BOB inbound destination not found");
|
||||
}
|
||||
|
||||
void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
|
||||
{
|
||||
LogPrint ("New BOB inbound connection");
|
||||
auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet);
|
||||
AddHandler (connection);
|
||||
connection->I2PConnect (receiver->data, receiver->dataLen);
|
||||
}
|
||||
|
||||
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
|
||||
m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet)
|
||||
{
|
||||
}
|
||||
|
||||
void BOBI2POutboundTunnel::Start ()
|
||||
{
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void BOBI2POutboundTunnel::Stop ()
|
||||
{
|
||||
ClearHandlers ();
|
||||
}
|
||||
|
||||
void BOBI2POutboundTunnel::Accept ()
|
||||
{
|
||||
auto localDestination = GetLocalDestination ();
|
||||
if (localDestination)
|
||||
localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1));
|
||||
else
|
||||
LogPrint ("Local destination not set for server tunnel");
|
||||
}
|
||||
|
||||
void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), m_Endpoint, m_IsQuiet);
|
||||
AddHandler (conn);
|
||||
conn->Connect ();
|
||||
}
|
||||
}
|
||||
|
||||
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
|
||||
m_LocalDestination (localDestination),
|
||||
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
BOBDestination::~BOBDestination ()
|
||||
{
|
||||
delete m_OutboundTunnel;
|
||||
delete m_InboundTunnel;
|
||||
i2p::client::context.DeleteLocalDestination (m_LocalDestination);
|
||||
}
|
||||
|
||||
void BOBDestination::Start ()
|
||||
{
|
||||
if (m_OutboundTunnel) m_OutboundTunnel->Start ();
|
||||
if (m_InboundTunnel) m_InboundTunnel->Start ();
|
||||
}
|
||||
|
||||
void BOBDestination::Stop ()
|
||||
{
|
||||
StopTunnels ();
|
||||
m_LocalDestination->Stop ();
|
||||
}
|
||||
|
||||
void BOBDestination::StopTunnels ()
|
||||
{
|
||||
if (m_OutboundTunnel)
|
||||
{
|
||||
m_OutboundTunnel->Stop ();
|
||||
delete m_OutboundTunnel;
|
||||
m_OutboundTunnel = nullptr;
|
||||
}
|
||||
if (m_InboundTunnel)
|
||||
{
|
||||
m_InboundTunnel->Stop ();
|
||||
delete m_InboundTunnel;
|
||||
m_InboundTunnel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BOBDestination::CreateInboundTunnel (int port)
|
||||
{
|
||||
if (!m_InboundTunnel)
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
BOBCommandSession::~BOBCommandSession ()
|
||||
{
|
||||
}
|
||||
|
||||
void BOBCommandSession::Terminate ()
|
||||
{
|
||||
m_Socket.close ();
|
||||
m_IsOpen = false;
|
||||
}
|
||||
|
||||
void BOBCommandSession::Receive ()
|
||||
{
|
||||
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset),
|
||||
std::bind(&BOBCommandSession::HandleReceived, shared_from_this (),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("BOB command channel read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t size = m_ReceiveBufferOffset + bytes_transferred;
|
||||
m_ReceiveBuffer[size] = 0;
|
||||
char * eol = strchr (m_ReceiveBuffer, '\n');
|
||||
if (eol)
|
||||
{
|
||||
*eol = 0;
|
||||
char * operand = strchr (m_ReceiveBuffer, ' ');
|
||||
if (operand)
|
||||
{
|
||||
*operand = 0;
|
||||
operand++;
|
||||
}
|
||||
else
|
||||
operand = eol;
|
||||
// process command
|
||||
auto& handlers = m_Owner.GetCommandHandlers ();
|
||||
auto it = handlers.find (m_ReceiveBuffer);
|
||||
if (it != handlers.end ())
|
||||
(this->*(it->second))(operand, eol - operand);
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "BOB unknown command ", m_ReceiveBuffer);
|
||||
SendReplyError ("unknown command");
|
||||
}
|
||||
|
||||
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
|
||||
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size < BOB_COMMAND_BUFFER_SIZE)
|
||||
m_ReceiveBufferOffset = size;
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Malformed input of the BOB command channel");
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BOBCommandSession::Send (size_t len)
|
||||
{
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len),
|
||||
boost::asio::transfer_all (),
|
||||
std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("BOB command channel send error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_IsOpen)
|
||||
Receive ();
|
||||
else
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
|
||||
void BOBCommandSession::SendReplyOK (const char * msg)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
|
||||
#else
|
||||
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
|
||||
#endif
|
||||
Send (len);
|
||||
}
|
||||
|
||||
void BOBCommandSession::SendReplyError (const char * msg)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
|
||||
#else
|
||||
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
|
||||
#endif
|
||||
Send (len);
|
||||
}
|
||||
|
||||
void BOBCommandSession::SendVersion ()
|
||||
{
|
||||
size_t len = strlen (BOB_VERSION);
|
||||
memcpy (m_SendBuffer, BOB_VERSION, len);
|
||||
Send (len);
|
||||
}
|
||||
|
||||
void BOBCommandSession::SendData (const char * nickname)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
|
||||
#else
|
||||
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
|
||||
#endif
|
||||
Send (len);
|
||||
}
|
||||
|
||||
void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: zap");
|
||||
Terminate ();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: start ", m_Nickname);
|
||||
if (!m_CurrentDestination)
|
||||
{
|
||||
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options));
|
||||
m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
|
||||
}
|
||||
if (m_InPort)
|
||||
m_CurrentDestination->CreateInboundTunnel (m_InPort);
|
||||
if (m_OutPort && !m_Address.empty ())
|
||||
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
|
||||
m_CurrentDestination->Start ();
|
||||
SendReplyOK ("tunnel starting");
|
||||
}
|
||||
|
||||
void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
auto dest = m_Owner.FindDestination (m_Nickname);
|
||||
if (dest)
|
||||
{
|
||||
dest->StopTunnels ();
|
||||
SendReplyOK ("tunnel stopping");
|
||||
}
|
||||
else
|
||||
SendReplyError ("tunnel not found");
|
||||
}
|
||||
|
||||
void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: setnick ", operand);
|
||||
m_Nickname = operand;
|
||||
std::string msg ("Nickname set to ");
|
||||
msg += operand;
|
||||
SendReplyOK (msg.c_str ());
|
||||
}
|
||||
|
||||
void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: getnick ", operand);
|
||||
m_CurrentDestination = m_Owner.FindDestination (operand);
|
||||
if (m_CurrentDestination)
|
||||
{
|
||||
m_Keys = m_CurrentDestination->GetKeys ();
|
||||
m_Nickname = operand;
|
||||
std::string msg ("Nickname set to ");
|
||||
msg += operand;
|
||||
SendReplyOK (msg.c_str ());
|
||||
}
|
||||
else
|
||||
SendReplyError ("tunnel not found");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: getkeys");
|
||||
SendReplyOK (m_Keys.ToBase64 ().c_str ());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: outhost ", operand);
|
||||
m_Address = operand;
|
||||
SendReplyOK ("outhost set");
|
||||
}
|
||||
|
||||
void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: outport ", operand);
|
||||
m_OutPort = boost::lexical_cast<int>(operand);
|
||||
SendReplyOK ("outbound port set");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: inport ", operand);
|
||||
m_InPort = boost::lexical_cast<int>(operand);
|
||||
SendReplyOK ("inbound port set");
|
||||
}
|
||||
|
||||
void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: quiet");
|
||||
m_IsQuiet = true;
|
||||
SendReplyOK ("quiet");
|
||||
}
|
||||
|
||||
void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: lookup ", operand);
|
||||
i2p::data::IdentHash ident;
|
||||
if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination)
|
||||
{
|
||||
SendReplyError ("Address Not found");
|
||||
return;
|
||||
}
|
||||
auto localDestination = m_CurrentDestination->GetLocalDestination ();
|
||||
auto leaseSet = localDestination->FindLeaseSet (ident);
|
||||
if (leaseSet)
|
||||
SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ());
|
||||
else
|
||||
{
|
||||
auto s = shared_from_this ();
|
||||
localDestination->RequestDestination (ident,
|
||||
[s](std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||
{
|
||||
if (ls)
|
||||
s->SendReplyOK (ls->GetIdentity ().ToBase64 ().c_str ());
|
||||
else
|
||||
s->SendReplyError ("LeaseSet Not found");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: clear");
|
||||
m_Owner.DeleteDestination (m_Nickname);
|
||||
SendReplyOK ("cleared");
|
||||
}
|
||||
|
||||
void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: list");
|
||||
auto& destinations = m_Owner.GetDestinations ();
|
||||
for (auto it: destinations)
|
||||
SendData (it.first.c_str ());
|
||||
SendReplyOK ("Listing done");
|
||||
}
|
||||
|
||||
void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "BOB: option ", operand);
|
||||
const char * value = strchr (operand, '=');
|
||||
if (value)
|
||||
{
|
||||
*(const_cast<char *>(value)) = 0;
|
||||
m_Options[operand] = value + 1;
|
||||
*(const_cast<char *>(value)) = '=';
|
||||
SendReplyOK ("option");
|
||||
}
|
||||
else
|
||||
SendReplyError ("malformed");
|
||||
}
|
||||
|
||||
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::address::from_string(address), port)
|
||||
)
|
||||
{
|
||||
// command -> handler
|
||||
m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
|
||||
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
|
||||
}
|
||||
|
||||
BOBCommandChannel::~BOBCommandChannel ()
|
||||
{
|
||||
Stop ();
|
||||
for (auto it: m_Destinations)
|
||||
delete it.second;
|
||||
}
|
||||
|
||||
void BOBCommandChannel::Start ()
|
||||
{
|
||||
Accept ();
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this));
|
||||
}
|
||||
|
||||
void BOBCommandChannel::Stop ()
|
||||
{
|
||||
m_IsRunning = false;
|
||||
for (auto it: m_Destinations)
|
||||
it.second->Stop ();
|
||||
m_Acceptor.cancel ();
|
||||
m_Service.stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BOBCommandChannel::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "BOB: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest)
|
||||
{
|
||||
m_Destinations[name] = dest;
|
||||
}
|
||||
|
||||
void BOBCommandChannel::DeleteDestination (const std::string& name)
|
||||
{
|
||||
auto it = m_Destinations.find (name);
|
||||
if (it != m_Destinations.end ())
|
||||
{
|
||||
it->second->Stop ();
|
||||
delete it->second;
|
||||
m_Destinations.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
BOBDestination * BOBCommandChannel::FindDestination (const std::string& name)
|
||||
{
|
||||
auto it = m_Destinations.find (name);
|
||||
if (it != m_Destinations.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BOBCommandChannel::Accept ()
|
||||
{
|
||||
auto newSession = std::make_shared<BOBCommandSession> (*this);
|
||||
m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this,
|
||||
std::placeholders::_1, newSession));
|
||||
}
|
||||
|
||||
void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Accept ();
|
||||
|
||||
if (!ecode)
|
||||
{
|
||||
LogPrint (eLogInfo, "New BOB command connection from ", session->GetSocket ().remote_endpoint ());
|
||||
session->SendVersion ();
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "BOB accept error: ", ecode.message ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
237
client/BOB.h
Normal file
237
client/BOB.h
Normal file
|
@ -0,0 +1,237 @@
|
|||
#ifndef BOB_H__
|
||||
#define BOB_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include "I2PTunnel.h"
|
||||
#include "I2PService.h"
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
const size_t BOB_COMMAND_BUFFER_SIZE = 1024;
|
||||
const char BOB_COMMAND_ZAP[] = "zap";
|
||||
const char BOB_COMMAND_QUIT[] = "quit";
|
||||
const char BOB_COMMAND_START[] = "start";
|
||||
const char BOB_COMMAND_STOP[] = "stop";
|
||||
const char BOB_COMMAND_SETNICK[] = "setnick";
|
||||
const char BOB_COMMAND_GETNICK[] = "getnick";
|
||||
const char BOB_COMMAND_NEWKEYS[] = "newkeys";
|
||||
const char BOB_COMMAND_GETKEYS[] = "getkeys";
|
||||
const char BOB_COMMAND_SETKEYS[] = "setkeys";
|
||||
const char BOB_COMMAND_GETDEST[] = "getdest";
|
||||
const char BOB_COMMAND_OUTHOST[] = "outhost";
|
||||
const char BOB_COMMAND_OUTPORT[] = "outport";
|
||||
const char BOB_COMMAND_INHOST[] = "inhost";
|
||||
const char BOB_COMMAND_INPORT[] = "inport";
|
||||
const char BOB_COMMAND_QUIET[] = "quiet";
|
||||
const char BOB_COMMAND_LOOKUP[] = "lookup";
|
||||
const char BOB_COMMAND_CLEAR[] = "clear";
|
||||
const char BOB_COMMAND_LIST[] = "list";
|
||||
const char BOB_COMMAND_OPTION[] = "option";
|
||||
|
||||
const char BOB_VERSION[] = "BOB 00.00.10\nOK\n";
|
||||
const char BOB_REPLY_OK[] = "OK %s\n";
|
||||
const char BOB_REPLY_ERROR[] = "ERROR %s\n";
|
||||
const char BOB_DATA[] = "NICKNAME %s\n";
|
||||
|
||||
class BOBI2PTunnel: public I2PService
|
||||
{
|
||||
public:
|
||||
|
||||
BOBI2PTunnel (std::shared_ptr<ClientDestination> localDestination):
|
||||
I2PService (localDestination) {};
|
||||
|
||||
virtual void Start () {};
|
||||
virtual void Stop () {};
|
||||
};
|
||||
|
||||
class BOBI2PInboundTunnel: public BOBI2PTunnel
|
||||
{
|
||||
struct AddressReceiver
|
||||
{
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> 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) {};
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination);
|
||||
~BOBI2PInboundTunnel ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver);
|
||||
|
||||
void ReceiveAddress (std::shared_ptr<AddressReceiver> receiver);
|
||||
void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||
std::shared_ptr<AddressReceiver> receiver);
|
||||
|
||||
void HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver);
|
||||
|
||||
void CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
};
|
||||
|
||||
class BOBI2POutboundTunnel: public BOBI2PTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
void SetQuiet () { m_IsQuiet = true; };
|
||||
|
||||
private:
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||
bool m_IsQuiet;
|
||||
};
|
||||
|
||||
|
||||
class BOBDestination
|
||||
{
|
||||
public:
|
||||
|
||||
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
|
||||
~BOBDestination ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
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 (); };
|
||||
std::shared_ptr<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<ClientDestination> m_LocalDestination;
|
||||
BOBI2POutboundTunnel * m_OutboundTunnel;
|
||||
BOBI2PInboundTunnel * m_InboundTunnel;
|
||||
};
|
||||
|
||||
class BOBCommandChannel;
|
||||
class BOBCommandSession: public std::enable_shared_from_this<BOBCommandSession>
|
||||
{
|
||||
public:
|
||||
|
||||
BOBCommandSession (BOBCommandChannel& owner);
|
||||
~BOBCommandSession ();
|
||||
void Terminate ();
|
||||
|
||||
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||
void SendVersion ();
|
||||
|
||||
// command handlers
|
||||
void ZapCommandHandler (const char * operand, size_t len);
|
||||
void QuitCommandHandler (const char * operand, size_t len);
|
||||
void StartCommandHandler (const char * operand, size_t len);
|
||||
void StopCommandHandler (const char * operand, size_t len);
|
||||
void SetNickCommandHandler (const char * operand, size_t len);
|
||||
void GetNickCommandHandler (const char * operand, size_t len);
|
||||
void NewkeysCommandHandler (const char * operand, size_t len);
|
||||
void SetkeysCommandHandler (const char * operand, size_t len);
|
||||
void GetkeysCommandHandler (const char * operand, size_t len);
|
||||
void GetdestCommandHandler (const char * operand, size_t len);
|
||||
void OuthostCommandHandler (const char * operand, size_t len);
|
||||
void OutportCommandHandler (const char * operand, size_t len);
|
||||
void InhostCommandHandler (const char * operand, size_t len);
|
||||
void InportCommandHandler (const char * operand, size_t len);
|
||||
void QuietCommandHandler (const char * operand, size_t len);
|
||||
void LookupCommandHandler (const char * operand, size_t len);
|
||||
void ClearCommandHandler (const char * operand, size_t len);
|
||||
void ListCommandHandler (const char * operand, size_t len);
|
||||
void OptionCommandHandler (const char * operand, size_t len);
|
||||
|
||||
private:
|
||||
|
||||
void Receive ();
|
||||
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
|
||||
void Send (size_t len);
|
||||
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void SendReplyOK (const char * msg);
|
||||
void SendReplyError (const char * msg);
|
||||
void SendData (const char * nickname);
|
||||
|
||||
private:
|
||||
|
||||
BOBCommandChannel& m_Owner;
|
||||
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;
|
||||
std::string m_Nickname, m_Address;
|
||||
int m_InPort, m_OutPort;
|
||||
i2p::data::PrivateKeys m_Keys;
|
||||
std::map<std::string, std::string> m_Options;
|
||||
BOBDestination * m_CurrentDestination;
|
||||
};
|
||||
typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len);
|
||||
|
||||
class BOBCommandChannel
|
||||
{
|
||||
public:
|
||||
|
||||
BOBCommandChannel(const std::string& address, int port);
|
||||
~BOBCommandChannel ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
void AddDestination (const std::string& name, BOBDestination * dest);
|
||||
void DeleteDestination (const std::string& name);
|
||||
BOBDestination * FindDestination (const std::string& name);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void Accept ();
|
||||
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
std::map<std::string, BOBDestination *> m_Destinations;
|
||||
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
|
||||
|
||||
public:
|
||||
|
||||
const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; };
|
||||
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
61
client/CMakeLists.txt
Normal file
61
client/CMakeLists.txt
Normal file
|
@ -0,0 +1,61 @@
|
|||
set(CLIENT_SRC
|
||||
"BOB.cpp"
|
||||
"ClientContext.cpp"
|
||||
"Daemon.cpp"
|
||||
"HTTPProxy.cpp"
|
||||
"HTTPServer.cpp"
|
||||
"I2PService.cpp"
|
||||
"i2pcontrol/I2PControl.cpp"
|
||||
"i2pcontrol/I2PControlServer.cpp"
|
||||
"I2PTunnel.cpp"
|
||||
"SAM.cpp"
|
||||
"SOCKS.cpp"
|
||||
"i2p.cpp"
|
||||
)
|
||||
|
||||
include_directories(".")
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
list(APPEND CLIENT_SRC "DaemonWin32.cpp")
|
||||
list(APPEND CLIENT_SRC "Win32Service.cpp")
|
||||
endif()
|
||||
|
||||
if(WITH_BINARY)
|
||||
add_executable(${CLIENT_NAME} ${CLIENT_SRC})
|
||||
if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe
|
||||
set_target_properties(${CLIENT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
|
||||
if(WITH_STATIC)
|
||||
set_target_properties(${CLIENT_NAME} PROPERTIES LINK_FLAGS "-static" )
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set_target_properties(${CLIENT_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)
|
||||
list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES)
|
||||
if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*")
|
||||
list(REMOVE_AT Boost_LIBRARIES -1)
|
||||
endif()
|
||||
target_link_libraries(
|
||||
${CLIENT_NAME} ${CORE_NAME}
|
||||
${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
${CLIENT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
if(MSVC)
|
||||
install(FILES
|
||||
$<TARGET_PDB_FILE:${CLIENT_NAME}> DESTINATION "bin" CONFIGURATIONS DEBUG
|
||||
)
|
||||
endif()
|
||||
endif()
|
363
client/ClientContext.cpp
Normal file
363
client/ClientContext.cpp
Normal file
|
@ -0,0 +1,363 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include "util/util.h"
|
||||
#include "util/Log.h"
|
||||
#include "Identity.h"
|
||||
#include "ClientContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
ClientContext context;
|
||||
|
||||
ClientContext::ClientContext (): m_SharedLocalDestination (nullptr),
|
||||
m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr),
|
||||
m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ClientContext::~ClientContext ()
|
||||
{
|
||||
delete m_HttpProxy;
|
||||
delete m_SocksProxy;
|
||||
delete m_SamBridge;
|
||||
delete m_BOBCommandChannel;
|
||||
delete m_I2PControlService;
|
||||
}
|
||||
|
||||
void ClientContext::Start ()
|
||||
{
|
||||
if (!m_SharedLocalDestination)
|
||||
{
|
||||
m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA
|
||||
m_Destinations[m_SharedLocalDestination->GetIdentity ().GetIdentHash ()] = m_SharedLocalDestination;
|
||||
m_SharedLocalDestination->Start ();
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> localDestination;
|
||||
// proxies
|
||||
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("-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("-socksproxyaddress", "127.0.0.1"),
|
||||
i2p::util::config::GetArg("-socksproxyport", 4447),
|
||||
localDestination
|
||||
);
|
||||
m_SocksProxy->Start();
|
||||
LogPrint("SOCKS Proxy Started");
|
||||
|
||||
// I2P tunnels
|
||||
std::string ircDestination = i2p::util::config::GetArg("-ircdest", "");
|
||||
if (ircDestination.length () > 0) // ircdest is presented
|
||||
{
|
||||
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 ();
|
||||
// TODO: allow muliple tunnels on the same port (but on a different address)
|
||||
m_ClientTunnels.insert(std::make_pair(
|
||||
ircPort, std::unique_ptr<I2PClientTunnel>(ircTunnel)
|
||||
));
|
||||
LogPrint("IRC tunnel started");
|
||||
}
|
||||
std::string eepKeys = i2p::util::config::GetArg("-eepkeys", "");
|
||||
if (eepKeys.length () > 0) // eepkeys file is presented
|
||||
{
|
||||
localDestination = LoadLocalDestination (eepKeys, true);
|
||||
auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eepaddress", "127.0.0.1"),
|
||||
i2p::util::config::GetArg("-eepport", 80), localDestination);
|
||||
serverTunnel->Start ();
|
||||
m_ServerTunnels.insert (std::make_pair(localDestination->GetIdentHash (), std::unique_ptr<I2PServerTunnel>(serverTunnel)));
|
||||
LogPrint("Server tunnel started");
|
||||
}
|
||||
ReadTunnels ();
|
||||
|
||||
// SAM
|
||||
int samPort = i2p::util::config::GetArg("-samport", 0);
|
||||
if (samPort)
|
||||
{
|
||||
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)
|
||||
{
|
||||
m_BOBCommandChannel = new BOBCommandChannel(
|
||||
i2p::util::config::GetArg("-bobaddress", "127.0.0.1"), bobPort
|
||||
);
|
||||
m_BOBCommandChannel->Start ();
|
||||
LogPrint("BOB command channel started");
|
||||
}
|
||||
|
||||
// I2P Control
|
||||
int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0);
|
||||
if(i2pcontrolPort) {
|
||||
m_I2PControlService = new I2PControlService(
|
||||
i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"),
|
||||
i2pcontrolPort,
|
||||
i2p::util::config::GetArg("-i2pcontrolpassword", I2P_CONTROL_DEFAULT_PASSWORD)
|
||||
);
|
||||
m_I2PControlService->Start();
|
||||
LogPrint("I2PControl started");
|
||||
}
|
||||
m_AddressBook.Start (m_SharedLocalDestination.get());
|
||||
}
|
||||
|
||||
void ClientContext::Stop ()
|
||||
{
|
||||
m_HttpProxy->Stop();
|
||||
delete m_HttpProxy;
|
||||
m_HttpProxy = nullptr;
|
||||
LogPrint("HTTP Proxy stopped");
|
||||
m_SocksProxy->Stop();
|
||||
delete m_SocksProxy;
|
||||
m_SocksProxy = nullptr;
|
||||
LogPrint("SOCKS Proxy stopped");
|
||||
for (auto& it: m_ClientTunnels)
|
||||
{
|
||||
it.second->Stop ();
|
||||
LogPrint("I2P client tunnel on port ", it.first, " stopped");
|
||||
}
|
||||
m_ClientTunnels.clear ();
|
||||
for (auto& it: m_ServerTunnels)
|
||||
{
|
||||
it.second->Stop ();
|
||||
LogPrint("I2P server tunnel stopped");
|
||||
}
|
||||
m_ServerTunnels.clear ();
|
||||
if (m_SamBridge)
|
||||
{
|
||||
m_SamBridge->Stop ();
|
||||
delete m_SamBridge;
|
||||
m_SamBridge = nullptr;
|
||||
LogPrint("SAM brdige stopped");
|
||||
}
|
||||
if (m_BOBCommandChannel)
|
||||
{
|
||||
m_BOBCommandChannel->Stop ();
|
||||
delete m_BOBCommandChannel;
|
||||
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 ();
|
||||
m_Destinations.clear ();
|
||||
m_SharedLocalDestination = nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic)
|
||||
{
|
||||
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 ())
|
||||
{
|
||||
s.seekg (0, std::ios::end);
|
||||
size_t len = s.tellg();
|
||||
s.seekg (0, std::ios::beg);
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
s.read ((char *)buf, len);
|
||||
keys.FromBuffer (buf, len);
|
||||
delete[] buf;
|
||||
LogPrint ("Local address ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("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 ();
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
len = keys.ToBuffer (buf, len);
|
||||
f.write ((char *)buf, len);
|
||||
delete[] buf;
|
||||
|
||||
LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " created");
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
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 = std::make_shared<ClientDestination> (keys, isPublic);
|
||||
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
}
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType,
|
||||
const std::map<std::string, std::string> * params)
|
||||
{
|
||||
i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
|
||||
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
void ClientContext::DeleteLocalDestination (std::shared_ptr<ClientDestination> destination)
|
||||
{
|
||||
if (!destination) return;
|
||||
auto it = m_Destinations.find (destination->GetIdentHash ());
|
||||
if (it != m_Destinations.end ())
|
||||
{
|
||||
auto d = it->second;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations.erase (it);
|
||||
}
|
||||
d->Stop ();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
|
||||
const std::map<std::string, std::string> * params)
|
||||
{
|
||||
auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ());
|
||||
if (it != m_Destinations.end ())
|
||||
{
|
||||
LogPrint ("Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " exists");
|
||||
if (!it->second->IsRunning ())
|
||||
{
|
||||
it->second->Start ();
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
|
||||
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||
m_Destinations[keys.GetPublic ().GetIdentHash ()] = localDestination;
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
std::shared_ptr<ClientDestination> ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const
|
||||
{
|
||||
auto it = m_Destinations.find (destination);
|
||||
if (it != m_Destinations.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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(const std::exception& ex) {
|
||||
LogPrint(eLogWarning, "Can't read ", pathTunnelsConfigFile, ": ", ex.what ());
|
||||
return;
|
||||
}
|
||||
|
||||
int numClientTunnels = 0, numServerTunnels = 0;
|
||||
for(auto& section: pt) {
|
||||
std::string name = section.first;
|
||||
try {
|
||||
std::string type = section.second.get<std::string> (I2P_TUNNELS_SECTION_TYPE);
|
||||
if(type == I2P_TUNNELS_SECTION_TYPE_CLIENT) {
|
||||
// mandatory params
|
||||
std::string dest = section.second.get<std::string> (I2P_CLIENT_TUNNEL_DESTINATION);
|
||||
int port = section.second.get<int> (I2P_CLIENT_TUNNEL_PORT);
|
||||
// optional params
|
||||
std::string address = section.second.get(
|
||||
I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"
|
||||
);
|
||||
std::string keys = section.second.get(I2P_CLIENT_TUNNEL_KEYS, "");
|
||||
int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0);
|
||||
|
||||
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||
if(keys.length () > 0)
|
||||
localDestination = LoadLocalDestination (keys, false);
|
||||
|
||||
auto clientTunnel = new I2PClientTunnel(
|
||||
dest, address, port, localDestination, destinationPort
|
||||
);
|
||||
// TODO: allow muliple tunnels on the same port (but on a different address)
|
||||
if(m_ClientTunnels.insert(std::make_pair(port, std::unique_ptr<I2PClientTunnel>(clientTunnel))).second)
|
||||
clientTunnel->Start ();
|
||||
else
|
||||
LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists");
|
||||
numClientTunnels++;
|
||||
} else if(type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP)
|
||||
{
|
||||
// mandatory params
|
||||
std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST);
|
||||
int port = section.second.get<int> (I2P_SERVER_TUNNEL_PORT);
|
||||
std::string keys = section.second.get<std::string> (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);
|
||||
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<i2p::data::IdentHash> 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<I2PServerTunnel>(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 ", pathTunnelsConfigFile);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LogPrint (eLogError, "Can't read tunnel ", name, " params: ", ex.what ());
|
||||
}
|
||||
}
|
||||
LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created");
|
||||
LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created");
|
||||
}
|
||||
}
|
||||
}
|
86
client/ClientContext.h
Normal file
86
client/ClientContext.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
#ifndef CLIENT_CONTEXT_H__
|
||||
#define CLIENT_CONTEXT_H__
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Destination.h"
|
||||
#include "HTTPProxy.h"
|
||||
#include "SOCKS.h"
|
||||
#include "I2PTunnel.h"
|
||||
#include "SAM.h"
|
||||
#include "BOB.h"
|
||||
#include "AddressBook.h"
|
||||
#include "i2pcontrol/I2PControlServer.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
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_ADDRESS[] = "address";
|
||||
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 I2P_SERVER_TUNNEL_INPORT[] = "inport";
|
||||
const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist";
|
||||
|
||||
class ClientContext
|
||||
{
|
||||
public:
|
||||
|
||||
ClientContext ();
|
||||
~ClientContext ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
std::shared_ptr<ClientDestination> GetSharedLocalDestination () const { return m_SharedLocalDestination; };
|
||||
std::shared_ptr<ClientDestination> CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1,
|
||||
const std::map<std::string, std::string> * params = nullptr); // transient
|
||||
std::shared_ptr<ClientDestination> CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
|
||||
const std::map<std::string, std::string> * params = nullptr);
|
||||
void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
|
||||
std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
|
||||
std::shared_ptr<ClientDestination> LoadLocalDestination (const std::string& filename, bool isPublic);
|
||||
|
||||
AddressBook& GetAddressBook () { return m_AddressBook; };
|
||||
const SAMBridge * GetSAMBridge () const { return m_SamBridge; };
|
||||
|
||||
private:
|
||||
|
||||
void ReadTunnels ();
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_DestinationsMutex;
|
||||
std::map<i2p::data::IdentHash, std::shared_ptr<ClientDestination> > m_Destinations;
|
||||
std::shared_ptr<ClientDestination> m_SharedLocalDestination;
|
||||
|
||||
AddressBook m_AddressBook;
|
||||
|
||||
i2p::proxy::HTTPProxy * m_HttpProxy;
|
||||
i2p::proxy::SOCKSProxy * m_SocksProxy;
|
||||
std::map<int, std::unique_ptr<I2PClientTunnel> > m_ClientTunnels; // port->tunnel
|
||||
std::map<i2p::data::IdentHash, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // destination->tunnel
|
||||
SAMBridge * m_SamBridge;
|
||||
BOBCommandChannel * m_BOBCommandChannel;
|
||||
I2PControlService * m_I2PControlService;
|
||||
|
||||
public:
|
||||
// for HTTP
|
||||
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
|
||||
};
|
||||
|
||||
extern ClientContext context;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
146
client/Daemon.cpp
Normal file
146
client/Daemon.cpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
#include <thread>
|
||||
|
||||
#include "Daemon.h"
|
||||
|
||||
#include "util/Log.h"
|
||||
#include "version.h"
|
||||
#include "transport/Transports.h"
|
||||
#include "transport/NTCPSession.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "RouterContext.h"
|
||||
#include "tunnel/Tunnel.h"
|
||||
#include "NetDb.h"
|
||||
#include "Garlic.h"
|
||||
#include "util/util.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "HTTPServer.h"
|
||||
#include "ClientContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
class Daemon_Singleton::Daemon_Singleton_Private
|
||||
{
|
||||
public:
|
||||
Daemon_Singleton_Private() : httpServer(nullptr)
|
||||
{};
|
||||
~Daemon_Singleton_Private()
|
||||
{
|
||||
delete httpServer;
|
||||
};
|
||||
|
||||
i2p::util::HTTPServer *httpServer;
|
||||
};
|
||||
|
||||
Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {};
|
||||
Daemon_Singleton::~Daemon_Singleton() {
|
||||
delete &d;
|
||||
};
|
||||
|
||||
bool Daemon_Singleton::IsService () const
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return i2p::util::config::GetArg("-service", 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::init(int argc, char* argv[])
|
||||
{
|
||||
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());
|
||||
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
|
||||
|
||||
isDaemon = i2p::util::config::GetArg("-daemon", 0);
|
||||
isLogging = i2p::util::config::GetArg("-log", 1);
|
||||
|
||||
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])
|
||||
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));
|
||||
auto bandwidth = i2p::util::config::GetArg("-bandwidth", "");
|
||||
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]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::start()
|
||||
{
|
||||
// initialize log
|
||||
if (isLogging)
|
||||
{
|
||||
if (isDaemon)
|
||||
{
|
||||
std::string logfile_path = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string();
|
||||
#ifndef _WIN32
|
||||
logfile_path.append("/i2pd.log");
|
||||
#else
|
||||
logfile_path.append("\\i2pd.log");
|
||||
#endif
|
||||
StartLog (logfile_path);
|
||||
}
|
||||
else
|
||||
StartLog (""); // write to stdout
|
||||
}
|
||||
|
||||
d.httpServer = 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();
|
||||
LogPrint("NetDB started");
|
||||
i2p::transport::transports.Start();
|
||||
LogPrint("Transports started");
|
||||
i2p::tunnel::tunnels.Start();
|
||||
LogPrint("Tunnels started");
|
||||
i2p::client::context.Start ();
|
||||
LogPrint("Client started");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon_Singleton::stop()
|
||||
{
|
||||
LogPrint("Shutdown started.");
|
||||
i2p::client::context.Stop();
|
||||
LogPrint("Client stopped");
|
||||
i2p::tunnel::tunnels.Stop();
|
||||
LogPrint("Tunnels stopped");
|
||||
i2p::transport::transports.Stop();
|
||||
LogPrint("Transports stopped");
|
||||
i2p::data::netdb.Stop();
|
||||
LogPrint("NetDB stopped");
|
||||
d.httpServer->Stop();
|
||||
LogPrint("HTTP Server stopped");
|
||||
|
||||
StopLog ();
|
||||
|
||||
delete d.httpServer; d.httpServer = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
73
client/Daemon.h
Normal file
73
client/Daemon.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Daemon i2p::util::DaemonWin32::Instance()
|
||||
#else
|
||||
#define Daemon i2p::util::DaemonLinux::Instance()
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
class Daemon_Singleton_Private;
|
||||
class Daemon_Singleton
|
||||
{
|
||||
public:
|
||||
virtual bool init(int argc, char* argv[]);
|
||||
virtual bool start();
|
||||
virtual bool stop();
|
||||
|
||||
int isLogging;
|
||||
int isDaemon;
|
||||
|
||||
int running;
|
||||
|
||||
protected:
|
||||
Daemon_Singleton();
|
||||
virtual ~Daemon_Singleton();
|
||||
|
||||
bool IsService () const;
|
||||
|
||||
// d-pointer for httpServer, httpProxy, etc.
|
||||
class Daemon_Singleton_Private;
|
||||
Daemon_Singleton_Private &d;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
class DaemonWin32 : public Daemon_Singleton
|
||||
{
|
||||
public:
|
||||
static DaemonWin32& Instance()
|
||||
{
|
||||
static DaemonWin32 instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
virtual bool init(int argc, char* argv[]);
|
||||
virtual bool start();
|
||||
virtual bool stop();
|
||||
};
|
||||
#else
|
||||
class DaemonLinux : public Daemon_Singleton
|
||||
{
|
||||
public:
|
||||
DaemonLinux() = default;
|
||||
|
||||
static DaemonLinux& Instance()
|
||||
{
|
||||
static DaemonLinux instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
virtual bool start();
|
||||
virtual bool stop();
|
||||
private:
|
||||
std::string pidfile;
|
||||
int pidFilehandle;
|
||||
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
119
client/DaemonLinux.cpp
Normal file
119
client/DaemonLinux.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#include "Daemon.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "util/Log.h"
|
||||
#include "util/util.h"
|
||||
|
||||
|
||||
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("Reloading config.");
|
||||
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
|
||||
break;
|
||||
case SIGABRT:
|
||||
case SIGTERM:
|
||||
case SIGINT:
|
||||
Daemon.running = 0; // Exit loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
bool DaemonLinux::start()
|
||||
{
|
||||
if (isDaemon == 1)
|
||||
{
|
||||
pid_t pid;
|
||||
pid = fork();
|
||||
if (pid > 0) // parent
|
||||
::exit (EXIT_SUCCESS);
|
||||
|
||||
if (pid < 0) // error
|
||||
return false;
|
||||
|
||||
// child
|
||||
umask(0);
|
||||
int sid = setsid();
|
||||
if (sid < 0)
|
||||
{
|
||||
LogPrint("Error, could not create process group.");
|
||||
return false;
|
||||
}
|
||||
std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy
|
||||
chdir(d.c_str());
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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("Error, could not create pid file (", pidfile, ")\nIs an instance already running?");
|
||||
return false;
|
||||
}
|
||||
if (lockf(pidFilehandle, F_TLOCK, 0) == -1)
|
||||
{
|
||||
LogPrint("Error, could not lock pid file (", pidfile, ")\nIs an instance already running?");
|
||||
return false;
|
||||
}
|
||||
char pid[10];
|
||||
sprintf(pid, "%d\n", getpid());
|
||||
write(pidFilehandle, pid, strlen(pid));
|
||||
|
||||
// Signal handler
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handle_signal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigaction(SIGHUP, &sa, 0);
|
||||
sigaction(SIGABRT, &sa, 0);
|
||||
sigaction(SIGTERM, &sa, 0);
|
||||
sigaction(SIGINT, &sa, 0);
|
||||
|
||||
return Daemon_Singleton::start();
|
||||
}
|
||||
|
||||
bool DaemonLinux::stop()
|
||||
{
|
||||
close(pidFilehandle);
|
||||
unlink(pidfile.c_str());
|
||||
|
||||
return Daemon_Singleton::stop();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
83
client/DaemonWin32.cpp
Normal file
83
client/DaemonWin32.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "Daemon.h"
|
||||
#include "util/util.h"
|
||||
#include "util/Log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "./Win32/Win32Service.h"
|
||||
|
||||
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;
|
||||
if (I2PService::isService())
|
||||
isDaemon = 1;
|
||||
else
|
||||
isDaemon = 0;
|
||||
|
||||
std::string serviceControl = i2p::util::config::GetArg("-service", "none");
|
||||
if (serviceControl == "install")
|
||||
{
|
||||
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")
|
||||
{
|
||||
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)
|
||||
{
|
||||
LogPrint("Service session");
|
||||
I2PService service(SERVICE_NAME);
|
||||
if (!I2PService::Run(service))
|
||||
{
|
||||
LogPrint("Service failed to run w/err 0x%08lx\n", GetLastError());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else
|
||||
LogPrint("User session");
|
||||
|
||||
return true;
|
||||
}
|
||||
bool DaemonWin32::start()
|
||||
{
|
||||
setlocale(LC_CTYPE, "");
|
||||
SetConsoleCP(1251);
|
||||
SetConsoleOutputCP(1251);
|
||||
setlocale(LC_ALL, "Russian");
|
||||
|
||||
return Daemon_Singleton::start();
|
||||
}
|
||||
|
||||
bool DaemonWin32::stop()
|
||||
{
|
||||
return Daemon_Singleton::stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
299
client/HTTPProxy.cpp
Normal file
299
client/HTTPProxy.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include "HTTPProxy.h"
|
||||
#include "util/util.h"
|
||||
#include "Identity.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include "util/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<HTTPProxyHandler>
|
||||
{
|
||||
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();
|
||||
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<i2p::stream::Stream> stream);
|
||||
|
||||
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_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, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
|
||||
I2PServiceHandler(parent), m_sock(sock)
|
||||
{ EnterState(GET_METHOD); }
|
||||
~HTTPProxyHandler() { Terminate(); }
|
||||
void Handle () { AsyncSockRead(); }
|
||||
};
|
||||
|
||||
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, shared_from_this(),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
} else {
|
||||
LogPrint(eLogError,"--- HTTP Proxy no socket for read");
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPProxyHandler::Terminate() {
|
||||
if (Kill()) return;
|
||||
if (m_sock)
|
||||
{
|
||||
LogPrint(eLogDebug,"--- HTTP Proxy close sock");
|
||||
m_sock->close();
|
||||
m_sock = nullptr;
|
||||
}
|
||||
Done(shared_from_this());
|
||||
}
|
||||
|
||||
/* All hope is lost beyond this point */
|
||||
//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";
|
||||
boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()),
|
||||
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_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(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,"--- HTTP Proxy server is: ",server, " port is: ", port, "\n path is: ",path);
|
||||
m_address = server;
|
||||
m_port = boost::lexical_cast<int>(port);
|
||||
m_path = path;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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));
|
||||
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
|
||||
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;
|
||||
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");
|
||||
m_request.append(reinterpret_cast<const char *>(http_buff),len);
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
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);
|
||||
GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete,
|
||||
shared_from_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<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
if (Kill()) return;
|
||||
LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection");
|
||||
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream);
|
||||
GetOwner()->AddHandler (connection);
|
||||
connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_request.data()), m_request.size());
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination):
|
||||
TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::client::I2PServiceHandler> HTTPProxyServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||
{
|
||||
return std::make_shared<HTTPProxyHandler> (this, socket);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
32
client/HTTPProxy.h
Normal file
32
client/HTTPProxy.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#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
|
||||
{
|
||||
class HTTPProxyServer: public i2p::client::TCPIPAcceptor
|
||||
{
|
||||
public:
|
||||
|
||||
HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
|
||||
~HTTPProxyServer() {};
|
||||
|
||||
protected:
|
||||
// Implements TCPIPAcceptor
|
||||
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
const char* GetName() { return "HTTP Proxy"; }
|
||||
};
|
||||
|
||||
typedef HTTPProxyServer HTTPProxy;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
994
client/HTTPServer.cpp
Normal file
994
client/HTTPServer.cpp
Normal file
|
@ -0,0 +1,994 @@
|
|||
#include <boost/bind.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <ctime>
|
||||
#include "util/base64.h"
|
||||
#include "util/Log.h"
|
||||
#include "tunnel/Tunnel.h"
|
||||
#include "tunnel/TransitTunnel.h"
|
||||
#include "transport/Transports.h"
|
||||
#include "NetDb.h"
|
||||
#include "util/I2PEndian.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "RouterContext.h"
|
||||
#include "ClientContext.h"
|
||||
#include "HTTPServer.h"
|
||||
|
||||
// For image and info
|
||||
#include "version.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
const std::string HTTPConnection::itoopieImage =
|
||||
"<img alt=\"ICToopie Icon\" src=\"data:image/png;base64,"
|
||||
"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\" />";
|
||||
|
||||
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"
|
||||
"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"
|
||||
"AP4AAAD+AAAA/Z8Atv+YAK//HwAk/QAAAP4AAAD/AAAAZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHA"
|
||||
"AAAP8AAAD+AAAA/gAAAP+RAKbxkgCo8ZMAqfGTAKnxkgCo8XYAiPQEAAX+AAAA/yUAK/sBAAH+ACg3/"
|
||||
"QC+//8Atvr/AG6W/QAAAP6YAK//kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp"
|
||||
"/5MAqf+TAKn/kwCp/5MAqf9oAHf+AAAA/kAASf6UAKv/kwCp/0cAUv4AAAD+AAAA/gAAAMkAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAACRAAAA/wAAAP4AAAD+BQAG/nsAjfOUAKrxkwCp8ZMAqfGTAKjxn"
|
||||
"QC08JcArvCYAK/xSQBU9wAAAP4Aqen+ALf7/wC3+v4AAAD+WwBo/pMAqf+TAKn/kwCp/5MAqf+TAKn/"
|
||||
"kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5wAs/6ZAK//kwCp/5MAqf9bAGj"
|
||||
"+AAAA/gAAAP4AAAD/AAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE0AAAD7AAAA/QAAAP4AA"
|
||||
"AD/TABY+J0AtPCTAKnxkwCp8ZMAqfGTAKnxkwCp8Y4ApPEAAAD/AFRx/QC4/P8Avf//AC0+/RQAGP2c"
|
||||
"ALP/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+TAKn/kwCp/5MAqf+"
|
||||
"TAKn/kwCp/5MAqf+TAKn/YQBv/wAAAP4AAAD+AAAA/wAAAG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"/////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"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMAAAD+AAAA/"
|
||||
"gCGtv0Atvr/ALn+/wARF/0AAAD3cHBw7bGxseqioqLuOjo67QAAAP8ARFz9ALz//wC3+/8At/v/AML/"
|
||||
"/wAbJf0AAAD/jo6O5v////b//////f397zw8PPEAAAD/AGCC/QC4/P8Auv//AEJb/QAAAP4AAAD/AAA"
|
||||
"AUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAABpAAAA/wAAAP4AERf9AMP//wC3+/8Auv/+AEVf/AAAAP8AAAD/AAAA/wAAAP0AWnn9"
|
||||
"AMH//wC3+/8At/v/ALf7/wC3+/8AtPf+ABsm/QAAAP8AAADwDg4O+QAAAPwAAAD+AD1T/QDB//8At/v"
|
||||
"/AKLd/gAAAP4AAAD+AAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPIAAAD+AAAA/gBfgP0Auf7/ALf7/wC5/v8A"
|
||||
"wv//AJnS/gCWzv4Awf//ALj9/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDC//8Ahbb+AFBt/QA3TP0"
|
||||
"ATGn9AI/D/gC+//8At/v/AMD//wATGv0AAAD+AAAA/wAAAE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA9AAAA/wAA"
|
||||
"AP4AAAD+AJPJ/QC2+v8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC"
|
||||
"3+/8At/v/ALf7/wC4/f8Au///ALj9/wC3+/8At/v/AML//wA0SP0AAAD+AAAA/QAAAMMAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAJYAAAD/AAAA/gAAAP4Andj9ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf"
|
||||
"7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/AML//wA+Vf0AAAD+AA"
|
||||
"AA/gAAAPYAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwwAAAP4AAAD+AAAA/gCGt/0AvP/"
|
||||
"/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atv"
|
||||
"r/ALr//gAoNv0AAAD+AAAA/gAAAP8AAAAeAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
"AAAC9AAAA/wAAAP4AAAD+AEFY/QC6/f4At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/"
|
||||
"8At/v/ALf7/wC2+v8Aw///AICv/gAEBv0AAAH+AAAA/QAAAP8AAAAiAAAAAQAAAAAAAAAAAAAAAAAAA"
|
||||
"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";
|
||||
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_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
|
||||
{
|
||||
|
||||
const char name_value_separator[] = { ':', ' ' };
|
||||
const char crlf[] = { '\r', '\n' };
|
||||
|
||||
} // namespace misc_strings
|
||||
|
||||
std::vector<boost::asio::const_buffer> HTTPConnection::reply::to_buffers(int status)
|
||||
{
|
||||
std::vector<boost::asio::const_buffer> buffers;
|
||||
if (headers.size () > 0)
|
||||
{
|
||||
buffers.push_back(boost::asio::buffer("HTTP/1.1 ", 9));
|
||||
buffers.push_back(boost::asio::buffer(boost::lexical_cast<std::string>(status), 3));
|
||||
buffers.push_back(boost::asio::buffer(" ", 1));
|
||||
std::string 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void HTTPConnection::Terminate ()
|
||||
{
|
||||
m_Socket->close ();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
else if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
|
||||
void HTTPConnection::RunRequest ()
|
||||
{
|
||||
auto address = ExtractAddress ();
|
||||
if (address.length () > 1 && address[1] != '?') // not just '/' or '/?'
|
||||
return; // TODO: error handling
|
||||
|
||||
HandleRequest (address);
|
||||
}
|
||||
|
||||
std::string HTTPConnection::ExtractAddress ()
|
||||
{
|
||||
char * get = strstr (m_Buffer, "GET");
|
||||
if (get)
|
||||
{
|
||||
char * http = strstr (get, "HTTP");
|
||||
if (http)
|
||||
return std::string (get + 4, http - get - 5);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void HTTPConnection::ExtractParams (const std::string& str, std::map<std::string, std::string>& params)
|
||||
{
|
||||
if (str[0] != '&') return;
|
||||
size_t pos = 1, end;
|
||||
do
|
||||
{
|
||||
end = str.find ('&', pos);
|
||||
std::string param = str.substr (pos, end - pos);
|
||||
LogPrint (param);
|
||||
size_t e = param.find ('=');
|
||||
if (e != std::string::npos)
|
||||
params[param.substr(0, e)] = param.substr(e+1);
|
||||
pos = end + 1;
|
||||
}
|
||||
while (end != std::string::npos);
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleWriteReply (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 ();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleRequest (const std::string& address)
|
||||
{
|
||||
std::stringstream s;
|
||||
// Html5 head start
|
||||
s << "<!DOCTYPE html>\n<html lang=\"en\">"; // TODO: Add support for locale.
|
||||
s << "<head><meta charset=\"utf-8\" />"; // TODO: Find something to parse html/template system. This is horrible.
|
||||
s << "<link rel='shortcut icon' href='";
|
||||
s << itoopieFavicon;
|
||||
s << "' /><title>Purple I2P " << VERSION " Webconsole</title></head>";
|
||||
// Head end
|
||||
if (address.length () > 1)
|
||||
HandleCommand (address.substr (2), s);
|
||||
else
|
||||
FillContent (s);
|
||||
s << "</html>";
|
||||
SendReply (s.str ());
|
||||
}
|
||||
|
||||
void HTTPConnection::FillContent (std::stringstream& s)
|
||||
{
|
||||
s << "<h2>Welcome to the Webconsole!</h2><br>";
|
||||
s << "<b>Uptime:</b> " << boost::posix_time::to_simple_string (
|
||||
boost::posix_time::time_duration (boost::posix_time::seconds (
|
||||
i2p::context.GetUptime ()))) << "<br>";
|
||||
s << "<b>Status:</b> ";
|
||||
switch (i2p::context.GetStatus ())
|
||||
{
|
||||
case eRouterStatusOK: s << "OK"; break;
|
||||
case eRouterStatusTesting: s << "Testing"; break;
|
||||
case eRouterStatusFirewalled: s << "Firewalled"; break;
|
||||
default: s << "Unknown";
|
||||
}
|
||||
s << "<br>";
|
||||
s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>";
|
||||
s << "<b>Received:</b> " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K";
|
||||
s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)<br>";
|
||||
s << "<b>Sent:</b> " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K";
|
||||
s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)<br>";
|
||||
s << "<b>Data path:</b> " << i2p::util::filesystem::GetDataDir().string() << "<br><br>";
|
||||
s << "<b>Our external address:</b>" << "<br>" ;
|
||||
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 << "<br>";
|
||||
}
|
||||
s << "<br><b>Routers:</b> <i>" << i2p::data::netdb.GetNumRouters () << "</i> ";
|
||||
s << "<b>Floodfills:</b> <i>" << i2p::data::netdb.GetNumFloodfills () << "</i> ";
|
||||
s << "<b>LeaseSets:</b> <i>" << i2p::data::netdb.GetNumLeaseSets () << "</i><br>";
|
||||
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_LOCAL_DESTINATIONS << ">Local destinations</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TUNNELS << ">Tunnels</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSIT_TUNNELS << ">Transit tunnels</a></b>";
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSPORTS << ">Transports</a></b>";
|
||||
if (i2p::client::context.GetSAMBridge ())
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_SAM_SESSIONS << ">SAM sessions</a></b>";
|
||||
s << "<br>";
|
||||
|
||||
if (i2p::context.AcceptsTunnels ())
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << ">Stop accepting tunnels</a></b><br>";
|
||||
else
|
||||
s << "<br><b><a href=/?" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << ">Start accepting tunnels</a></b><br>";
|
||||
}
|
||||
|
||||
void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s)
|
||||
{
|
||||
size_t paramsPos = command.find('&');
|
||||
std::string cmd = command.substr (0, paramsPos);
|
||||
if (cmd == HTTP_COMMAND_TRANSPORTS)
|
||||
ShowTransports (s);
|
||||
else if (cmd == HTTP_COMMAND_TUNNELS)
|
||||
ShowTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS)
|
||||
ShowTransitTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
|
||||
StartAcceptingTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
|
||||
StopAcceptingTunnels (s);
|
||||
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS)
|
||||
ShowLocalDestinations (s);
|
||||
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION)
|
||||
{
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (command.substr (paramsPos), params);
|
||||
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<std::string, std::string> params;
|
||||
ExtractParams (command.substr (paramsPos), params);
|
||||
auto id = params[HTTP_PARAM_SAM_SESSION_ID];
|
||||
ShowSAMSession (id, s);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTransports (std::stringstream& s)
|
||||
{
|
||||
auto ntcpServer = i2p::transport::transports.GetNTCPServer ();
|
||||
if (ntcpServer)
|
||||
{
|
||||
s << "NTCP<br>";
|
||||
for (auto it: ntcpServer->GetNTCPSessions ())
|
||||
{
|
||||
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 << "<br>";
|
||||
}
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
auto ssuServer = i2p::transport::transports.GetSSUServer ();
|
||||
if (ssuServer)
|
||||
{
|
||||
s << "<br>SSU<br>";
|
||||
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 << "-->";
|
||||
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 << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTunnels (std::stringstream& s)
|
||||
{
|
||||
s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "<br>";
|
||||
|
||||
for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->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 () << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
|
||||
for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ())
|
||||
{
|
||||
it.second->GetTunnelConfig ()->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 () << "<br>";
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowTransitTunnels (std::stringstream& s)
|
||||
{
|
||||
for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
|
||||
{
|
||||
if (dynamic_cast<i2p::tunnel::TransitTunnelGateway *>(it.second))
|
||||
s << it.second->GetTunnelID () << "-->";
|
||||
else if (dynamic_cast<i2p::tunnel::TransitTunnelEndpoint *>(it.second))
|
||||
s << "-->" << it.second->GetTunnelID ();
|
||||
else
|
||||
s << "-->" << it.second->GetTunnelID () << "-->";
|
||||
s << " " << it.second->GetNumTransmittedBytes () << "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowLocalDestinations (std::stringstream& s)
|
||||
{
|
||||
for (auto& it: i2p::client::context.GetDestinations ())
|
||||
{
|
||||
auto ident = it.second->GetIdentHash ();;
|
||||
s << "<a href=/?" << HTTP_COMMAND_LOCAL_DESTINATION;
|
||||
s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">";
|
||||
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s)
|
||||
{
|
||||
i2p::data::IdentHash ident;
|
||||
ident.FromBase32 (b32);
|
||||
auto dest = i2p::client::context.FindLocalDestination (ident);
|
||||
if (dest)
|
||||
{
|
||||
s << "<b>Base64:</b><br>" << dest->GetIdentity ().ToBase64 () << "<br><br>";
|
||||
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>";
|
||||
auto pool = dest->GetTunnelPool ();
|
||||
if (pool)
|
||||
{
|
||||
s << "<b>Tunnels:</b><br>";
|
||||
for (auto it: pool->GetOutboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->Print (s);
|
||||
auto state = it->GetState ();
|
||||
if (state == i2p::tunnel::eTunnelStateFailed)
|
||||
s << " " << "Failed";
|
||||
else if (state == i2p::tunnel::eTunnelStateExpiring)
|
||||
s << " " << "Exp";
|
||||
s << "<br>" << std::endl;
|
||||
}
|
||||
for (auto it: pool->GetInboundTunnels ())
|
||||
{
|
||||
it->GetTunnelConfig ()->Print (s);
|
||||
auto state = it->GetState ();
|
||||
if (state == i2p::tunnel::eTunnelStateFailed)
|
||||
s << " " << "Failed";
|
||||
else if (state == i2p::tunnel::eTunnelStateExpiring)
|
||||
s << " " << "Exp";
|
||||
s << "<br>" << std::endl;
|
||||
}
|
||||
}
|
||||
s << "<br><b>Streams:</b><br>";
|
||||
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 << "<br>"<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::ShowSAMSessions (std::stringstream& s)
|
||||
{
|
||||
auto sam = i2p::client::context.GetSAMBridge ();
|
||||
if (sam)
|
||||
{
|
||||
for (auto& it: sam->GetSessions ())
|
||||
{
|
||||
s << "<a href=/?" << HTTP_COMMAND_SAM_SESSION;
|
||||
s << "&" << HTTP_PARAM_SAM_SESSION_ID << "=" << it.first << ">";
|
||||
s << it.first << "</a><br>" << 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)
|
||||
{
|
||||
auto& ident = session->localDestination->GetIdentHash();
|
||||
s << "<a href=/?" << HTTP_COMMAND_LOCAL_DESTINATION;
|
||||
s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">";
|
||||
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>" << std::endl;
|
||||
s << "<b>Streams:</b><br>";
|
||||
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 << "<br>" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPConnection::StartAcceptingTunnels (std::stringstream& s)
|
||||
{
|
||||
i2p::context.SetAcceptsTunnels (true);
|
||||
s << "Accepting tunnels started" << std::endl;
|
||||
}
|
||||
|
||||
void HTTPConnection::StopAcceptingTunnels (std::stringstream& s)
|
||||
{
|
||||
i2p::context.SetAcceptsTunnels (false);
|
||||
s << "Accepting tunnels stopped" << std::endl;
|
||||
}
|
||||
|
||||
void HTTPConnection::SendReply (const std::string& content, int status)
|
||||
{
|
||||
m_Reply.content = content;
|
||||
m_Reply.headers.resize(3);
|
||||
// we need the date header to be compliant 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<std::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));
|
||||
}
|
||||
|
||||
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)),
|
||||
m_NewSocket (nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HTTPServer::~HTTPServer ()
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void HTTPServer::Start ()
|
||||
{
|
||||
m_Thread = new std::thread (std::bind (&HTTPServer::Run, this));
|
||||
m_Acceptor.listen ();
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void HTTPServer::Stop ()
|
||||
{
|
||||
m_Acceptor.close();
|
||||
m_Service.stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::Run ()
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void HTTPServer::HandleAccept(const boost::system::error_code& ecode)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
CreateConnection(m_NewSocket);
|
||||
Accept ();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket)
|
||||
{
|
||||
auto conn = std::make_shared<HTTPConnection> (m_NewSocket);
|
||||
conn->Receive ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
127
client/HTTPServer.h
Normal file
127
client/HTTPServer.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
#ifndef HTTP_SERVER_H__
|
||||
#define HTTP_SERVER_H__
|
||||
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/array.hpp>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
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<HTTPConnection>
|
||||
{
|
||||
protected:
|
||||
|
||||
struct header
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct request
|
||||
{
|
||||
std::string method;
|
||||
std::string uri;
|
||||
std::string host;
|
||||
int port;
|
||||
int http_version_major;
|
||||
int http_version_minor;
|
||||
std::vector<header> headers;
|
||||
};
|
||||
|
||||
struct reply
|
||||
{
|
||||
std::vector<header> headers;
|
||||
std::string content;
|
||||
|
||||
std::vector<boost::asio::const_buffer> to_buffers (int status);
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
HTTPConnection (boost::asio::ip::tcp::socket * socket):
|
||||
m_Socket (socket), m_Timer (socket->get_io_service ()),
|
||||
m_BufferLen (0) {};
|
||||
~HTTPConnection() { delete m_Socket; }
|
||||
void Receive ();
|
||||
|
||||
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 SendReply (const std::string& content, int status = 200);
|
||||
|
||||
void HandleRequest (const std::string& address);
|
||||
void HandleCommand (const std::string& command, std::stringstream& s);
|
||||
void ShowTransports (std::stringstream& s);
|
||||
void ShowTunnels (std::stringstream& s);
|
||||
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);
|
||||
std::string ExtractAddress ();
|
||||
void ExtractParams (const std::string& str, std::map<std::string, std::string>& params);
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
boost::asio::ip::tcp::socket * m_Socket;
|
||||
boost::asio::deadline_timer m_Timer;
|
||||
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
|
||||
size_t m_BufferLen;
|
||||
request m_Request;
|
||||
reply m_Reply;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void RunRequest ();
|
||||
|
||||
public:
|
||||
|
||||
static const std::string itoopieImage;
|
||||
static const std::string itoopieFavicon;
|
||||
};
|
||||
|
||||
class HTTPServer
|
||||
{
|
||||
public:
|
||||
|
||||
HTTPServer (const std::string& address, int port);
|
||||
virtual ~HTTPServer ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void Accept ();
|
||||
void HandleAccept(const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
std::thread * 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
79
client/I2PService.cpp
Normal file
79
client/I2PService.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#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 (std::shared_ptr<ClientDestination> localDestination):
|
||||
m_LocalDestination (localDestination ? localDestination :
|
||||
i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE))
|
||||
{
|
||||
}
|
||||
|
||||
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))
|
||||
m_LocalDestination->CreateStream (streamRequestComplete, identHash, port);
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Remote destination ", dest, " not found");
|
||||
streamRequestComplete (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void TCPIPAcceptor::Start ()
|
||||
{
|
||||
m_Acceptor.listen ();
|
||||
Accept ();
|
||||
}
|
||||
|
||||
void TCPIPAcceptor::Stop ()
|
||||
{
|
||||
m_Acceptor.close();
|
||||
m_Timer.cancel ();
|
||||
ClearHandlers();
|
||||
}
|
||||
|
||||
void TCPIPAcceptor::Accept ()
|
||||
{
|
||||
auto newSocket = std::make_shared<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, std::shared_ptr<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();
|
||||
Accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
LogPrint (eLogError,"--- ",GetName()," Closing socket on accept because: ", ecode.message ());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
107
client/I2PService.h
Normal file
107
client/I2PService.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
#ifndef I2PSERVICE_H__
|
||||
#define I2PSERVICE_H__
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Destination.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
class I2PServiceHandler;
|
||||
class I2PService
|
||||
{
|
||||
public:
|
||||
I2PService (std::shared_ptr<ClientDestination> localDestination = nullptr);
|
||||
I2PService (i2p::data::SigningKeyType kt);
|
||||
virtual ~I2PService () { ClearHandlers (); }
|
||||
|
||||
inline void AddHandler (std::shared_ptr<I2PServiceHandler> conn)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||
m_Handlers.insert(conn);
|
||||
}
|
||||
inline void RemoveHandler (std::shared_ptr<I2PServiceHandler> conn)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||
m_Handlers.erase(conn);
|
||||
}
|
||||
inline void ClearHandlers ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||
m_Handlers.clear();
|
||||
}
|
||||
|
||||
inline std::shared_ptr<ClientDestination> GetLocalDestination () { return m_LocalDestination; }
|
||||
inline void SetLocalDestination (std::shared_ptr<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 (); }
|
||||
|
||||
virtual void Start () = 0;
|
||||
virtual void Stop () = 0;
|
||||
|
||||
virtual const char* GetName() { return "Generic I2P Service"; }
|
||||
private:
|
||||
|
||||
std::shared_ptr<ClientDestination> m_LocalDestination;
|
||||
std::unordered_set<std::shared_ptr<I2PServiceHandler> > m_Handlers;
|
||||
std::mutex m_HandlersMutex;
|
||||
};
|
||||
|
||||
/*Simple interface for I2PHandlers, allows detection of finalization amongst other things */
|
||||
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
|
||||
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); }
|
||||
// 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<I2PServiceHandler> 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<bool> 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
|
||||
{
|
||||
public:
|
||||
TCPIPAcceptor(const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination = nullptr)
|
||||
: I2PService(localDestination), m_Acceptor(GetService(), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
|
||||
m_Timer (GetService()) {}
|
||||
TCPIPAcceptor(const std::string& address, int port, i2p::data::SigningKeyType kt)
|
||||
: I2PService(kt), 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
|
||||
void Start ();
|
||||
//If you override this make sure you call it from the children
|
||||
void Stop ();
|
||||
protected:
|
||||
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<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, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
boost::asio::deadline_timer m_Timer;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
405
client/I2PTunnel.cpp
Normal file
405
client/I2PTunnel.cpp
Normal file
|
@ -0,0 +1,405 @@
|
|||
#include <cassert>
|
||||
#include "util/Log.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include "I2PTunnel.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port):
|
||||
I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
|
||||
m_IsQuiet (true)
|
||||
{
|
||||
m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
|
||||
}
|
||||
|
||||
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
|
||||
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
|
||||
m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
|
||||
{
|
||||
}
|
||||
|
||||
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket, const boost::asio::ip::tcp::endpoint& target, bool quiet):
|
||||
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
|
||||
m_RemoteEndpoint (target), m_IsQuiet (quiet)
|
||||
{
|
||||
}
|
||||
|
||||
I2PTunnelConnection::~I2PTunnelConnection ()
|
||||
{
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len)
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
if (msg)
|
||||
m_Stream->Send (msg, len); // connect and send
|
||||
else
|
||||
m_Stream->Send (m_Buffer, 0); // connect
|
||||
}
|
||||
StreamReceive ();
|
||||
Receive ();
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::Connect ()
|
||||
{
|
||||
if (m_Socket)
|
||||
m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::Terminate ()
|
||||
{
|
||||
if (Kill()) return;
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
m_Stream.reset ();
|
||||
}
|
||||
m_Socket->close ();
|
||||
Done(shared_from_this ());
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::Receive ()
|
||||
{
|
||||
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
|
||||
std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
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 ();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel write error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
StreamReceive ();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel stream read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
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)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("I2PTunnel connect error: ", ecode.message ());
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("I2PTunnel connected");
|
||||
if (m_IsQuiet)
|
||||
StreamReceive ();
|
||||
else
|
||||
{
|
||||
// send destination first like received from I2P
|
||||
std::string dest = m_Stream->GetRemoteIdentity ().ToBase64 ();
|
||||
dest += "\n";
|
||||
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
|
||||
HandleStreamReceive (boost::system::error_code (), dest.size ());
|
||||
}
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
const boost::asio::ip::tcp::endpoint& target, const std::string& host):
|
||||
I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false)
|
||||
{
|
||||
}
|
||||
|
||||
void I2PTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
|
||||
{
|
||||
if (m_HeaderSent)
|
||||
I2PTunnelConnection::Write (buf, len);
|
||||
else
|
||||
{
|
||||
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 ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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<I2PClientTunnelHandler>
|
||||
{
|
||||
public:
|
||||
I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination,
|
||||
int destinationPort, std::shared_ptr<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<i2p::stream::Stream> stream);
|
||||
i2p::data::IdentHash m_DestinationIdentHash;
|
||||
int m_DestinationPort;
|
||||
std::shared_ptr<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, m_DestinationPort);
|
||||
}
|
||||
|
||||
void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
if (Kill()) return;
|
||||
LogPrint (eLogInfo,"New I2PTunnel connection");
|
||||
auto connection = std::make_shared<I2PTunnelConnection>(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();
|
||||
m_Socket = nullptr;
|
||||
}
|
||||
Done(shared_from_this());
|
||||
}
|
||||
|
||||
I2PClientTunnel::I2PClientTunnel(
|
||||
const std::string& destination, const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, int destinationPort
|
||||
)
|
||||
: TCPIPAcceptor(address, port, localDestination), m_Destination(destination),
|
||||
m_DestinationIdentHash(nullptr), m_DestinationPort(destinationPort)
|
||||
{}
|
||||
|
||||
void I2PClientTunnel::Start ()
|
||||
{
|
||||
TCPIPAcceptor::Start ();
|
||||
GetIdentHash();
|
||||
}
|
||||
|
||||
void I2PClientTunnel::Stop ()
|
||||
{
|
||||
TCPIPAcceptor::Stop();
|
||||
auto *originalIdentHash = m_DestinationIdentHash;
|
||||
m_DestinationIdentHash = nullptr;
|
||||
delete originalIdentHash;
|
||||
}
|
||||
|
||||
/* HACK: maybe we should create a caching IdentHash provider in AddressBook */
|
||||
const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash ()
|
||||
{
|
||||
if (!m_DestinationIdentHash)
|
||||
{
|
||||
i2p::data::IdentHash identHash;
|
||||
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");
|
||||
}
|
||||
return m_DestinationIdentHash;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2PServiceHandler> I2PClientTunnel::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||
{
|
||||
const i2p::data::IdentHash *identHash = GetIdentHash();
|
||||
if (identHash)
|
||||
return std::make_shared<I2PClientTunnelHandler>(this, *identHash, m_DestinationPort, socket);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
I2PServerTunnel::I2PServerTunnel (const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, int inport):
|
||||
I2PService (localDestination), m_Address (address), m_Port (port), m_IsAccessList (false)
|
||||
{
|
||||
m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port);
|
||||
}
|
||||
|
||||
void I2PServerTunnel::Start ()
|
||||
{
|
||||
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<boost::asio::ip::tcp::resolver>(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 ()
|
||||
{
|
||||
ClearHandlers ();
|
||||
}
|
||||
|
||||
void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
std::shared_ptr<boost::asio::ip::tcp::resolver> 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<i2p::data::IdentHash>& accessList)
|
||||
{
|
||||
m_AccessList = accessList;
|
||||
m_IsAccessList = true;
|
||||
}
|
||||
|
||||
void I2PServerTunnel::Accept ()
|
||||
{
|
||||
if (m_PortDestination)
|
||||
m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
|
||||
|
||||
auto localDestination = GetLocalDestination ();
|
||||
if (localDestination)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
void I2PServerTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
CreateI2PConnection (stream);
|
||||
}
|
||||
}
|
||||
|
||||
void I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint ());
|
||||
AddHandler (conn);
|
||||
conn->Connect ();
|
||||
}
|
||||
|
||||
I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int inport):
|
||||
I2PServerTunnel (address, port, localDestination, inport)
|
||||
{
|
||||
}
|
||||
|
||||
void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
auto conn = std::make_shared<I2PTunnelConnectionHTTP> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), GetAddress ());
|
||||
AddHandler (conn);
|
||||
conn->Connect ();
|
||||
}
|
||||
}
|
||||
}
|
156
client/I2PTunnel.h
Normal file
156
client/I2PTunnel.h
Normal file
|
@ -0,0 +1,156 @@
|
|||
#ifndef I2PTUNNEL_H__
|
||||
#define I2PTUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Identity.h"
|
||||
#include "Destination.h"
|
||||
#include "Streaming.h"
|
||||
#include "I2PService.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
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
|
||||
|
||||
|
||||
class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this<I2PTunnelConnection>
|
||||
{
|
||||
public:
|
||||
|
||||
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port = 0); // to I2P
|
||||
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
|
||||
I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, std::shared_ptr<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 ();
|
||||
|
||||
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 ();
|
||||
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandleConnect (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE];
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
|
||||
std::shared_ptr<i2p::stream::Stream> m_Stream;
|
||||
boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
|
||||
bool m_IsQuiet; // don't send destination
|
||||
};
|
||||
|
||||
class I2PTunnelConnectionHTTP: public I2PTunnelConnection
|
||||
{
|
||||
public:
|
||||
|
||||
I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> 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;
|
||||
};
|
||||
|
||||
class I2PClientTunnel: public TCPIPAcceptor
|
||||
{
|
||||
protected:
|
||||
|
||||
// Implements TCPIPAcceptor
|
||||
std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
const char* GetName() { return "I2P Client Tunnel"; }
|
||||
|
||||
public:
|
||||
|
||||
I2PClientTunnel(
|
||||
const std::string& destination, const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, int destinationPort = 0
|
||||
);
|
||||
~I2PClientTunnel () {}
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
private:
|
||||
|
||||
const i2p::data::IdentHash * GetIdentHash ();
|
||||
|
||||
std::string m_Destination;
|
||||
const i2p::data::IdentHash * m_DestinationIdentHash;
|
||||
int m_DestinationPort;
|
||||
};
|
||||
|
||||
class I2PServerTunnel: public I2PService
|
||||
{
|
||||
public:
|
||||
|
||||
I2PServerTunnel (const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, int inport = 0);
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
void SetAccessList (const std::set<i2p::data::IdentHash>& 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 HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
virtual void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
|
||||
private:
|
||||
|
||||
std::string m_Address;
|
||||
int m_Port;
|
||||
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> m_PortDestination;
|
||||
std::set<i2p::data::IdentHash> m_AccessList;
|
||||
bool m_IsAccessList;
|
||||
};
|
||||
|
||||
class I2PServerTunnelHTTP: public I2PServerTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
I2PServerTunnelHTTP (const std::string& address, int port,
|
||||
std::shared_ptr<ClientDestination> localDestination, int inport = 0);
|
||||
|
||||
private:
|
||||
|
||||
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
874
client/SAM.cpp
Normal file
874
client/SAM.cpp
Normal file
|
@ -0,0 +1,874 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "util/base64.h"
|
||||
#include "Identity.h"
|
||||
#include "util/Log.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include "SAM.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
SAMSocket::SAMSocket (SAMBridge& owner):
|
||||
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()),
|
||||
m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false),
|
||||
m_Stream (nullptr), m_Session (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SAMSocket::~SAMSocket ()
|
||||
{
|
||||
Terminate ();
|
||||
}
|
||||
|
||||
void SAMSocket::CloseStream ()
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
m_Stream->Close ();
|
||||
m_Stream.reset ();
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::Terminate ()
|
||||
{
|
||||
CloseStream ();
|
||||
|
||||
switch (m_SocketType)
|
||||
{
|
||||
case eSAMSocketTypeSession:
|
||||
m_Owner.CloseSession (m_ID);
|
||||
break;
|
||||
case eSAMSocketTypeStream:
|
||||
{
|
||||
if (m_Session)
|
||||
m_Session->sockets.remove (shared_from_this ());
|
||||
break;
|
||||
}
|
||||
case eSAMSocketTypeAcceptor:
|
||||
{
|
||||
if (m_Session)
|
||||
{
|
||||
m_Session->sockets.remove (shared_from_this ());
|
||||
m_Session->localDestination->StopAcceptingStreams ();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
m_SocketType = eSAMSocketTypeTerminated;
|
||||
m_Socket.close ();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM handshake read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
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)
|
||||
{
|
||||
separator = strchr (separator + 1, ' ');
|
||||
if (separator)
|
||||
*separator = 0;
|
||||
}
|
||||
|
||||
if (!strcmp (m_Buffer, SAM_HANDSHAKE))
|
||||
{
|
||||
std::string version("3.0");
|
||||
// try to find MIN and MAX, 3.0 if not found
|
||||
if (separator)
|
||||
{
|
||||
separator++;
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (separator, params);
|
||||
auto it = params.find (SAM_PARAM_MAX);
|
||||
// TODO: check MIN as well
|
||||
if (it != params.end ())
|
||||
version = it->second;
|
||||
}
|
||||
if (version[0] == '3') // we support v3 (3.0 and 3.1) only
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
|
||||
#else
|
||||
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::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint ("SAM handshake mismatch");
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM handshake reply send error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
|
||||
std::bind(&SAMSocket::HandleMessage, shared_from_this (),
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close)
|
||||
{
|
||||
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));
|
||||
else
|
||||
{
|
||||
if (close)
|
||||
Terminate ();
|
||||
else
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM reply send error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (close)
|
||||
Terminate ();
|
||||
else
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes_transferred += m_BufferOffset;
|
||||
m_BufferOffset = 0;
|
||||
m_Buffer[bytes_transferred] = 0;
|
||||
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
|
||||
if (eol)
|
||||
{
|
||||
*eol = 0;
|
||||
char * separator = strchr (m_Buffer, ' ');
|
||||
if (separator)
|
||||
{
|
||||
separator = strchr (separator + 1, ' ');
|
||||
if (separator)
|
||||
*separator = 0;
|
||||
else
|
||||
separator = eol;
|
||||
|
||||
if (!strcmp (m_Buffer, SAM_SESSION_CREATE))
|
||||
ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
|
||||
else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT))
|
||||
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_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 len = bytes_transferred - (separator - m_Buffer) - 1;
|
||||
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
|
||||
if (processed < len)
|
||||
{
|
||||
m_BufferOffset = len - processed;
|
||||
if (processed > 0)
|
||||
memmove (m_Buffer, separator + 1 + processed, m_BufferOffset);
|
||||
else
|
||||
{
|
||||
// restore string back
|
||||
*separator = ' ';
|
||||
*eol = '\n';
|
||||
}
|
||||
}
|
||||
// since it's SAM v1 reply is not expected
|
||||
Receive ();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "SAM unexpected message ", m_Buffer);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "SAM malformed message ", m_Buffer);
|
||||
Terminate ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "SAM incomplete message ", bytes_transferred);
|
||||
m_BufferOffset = bytes_transferred;
|
||||
// try to receive remaining message
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessSessionCreate (char * buf, size_t len)
|
||||
{
|
||||
LogPrint ("SAM session create: ", buf);
|
||||
std::map<std::string, std::string> 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];
|
||||
m_ID = id;
|
||||
if (m_Owner.FindSession (id))
|
||||
{
|
||||
// session exists
|
||||
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// create destination
|
||||
m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms);
|
||||
if (m_Session)
|
||||
{
|
||||
m_SocketType = eSAMSocketTypeSession;
|
||||
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));
|
||||
}
|
||||
|
||||
if (m_Session->localDestination->IsReady ())
|
||||
SendSessionCreateReplyOk ();
|
||||
else
|
||||
{
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
|
||||
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true);
|
||||
}
|
||||
|
||||
void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
if (m_Session->localDestination->IsReady ())
|
||||
SendSessionCreateReplyOk ();
|
||||
else
|
||||
{
|
||||
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
|
||||
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::SendSessionCreateReplyOk ()
|
||||
{
|
||||
uint8_t buf[1024];
|
||||
char priv[1024];
|
||||
size_t l = m_Session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024);
|
||||
size_t l1 = i2p::util::ByteStreamToBase64 (buf, l, priv, 1024);
|
||||
priv[l1] = 0;
|
||||
#ifdef _MSC_VER
|
||||
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
|
||||
#else
|
||||
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, l2, false);
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessStreamConnect (char * buf, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "SAM stream connect: ", buf);
|
||||
std::map<std::string, std::string> 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];
|
||||
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
|
||||
m_ID = id;
|
||||
m_Session = m_Owner.FindSession (id);
|
||||
if (m_Session)
|
||||
{
|
||||
i2p::data::IdentityEx dest;
|
||||
size_t len = dest.FromBase64(destination);
|
||||
if (len > 0)
|
||||
{
|
||||
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
|
||||
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
|
||||
}
|
||||
|
||||
void SAMSocket::Connect (std::shared_ptr<const i2p::data::LeaseSet> remote)
|
||||
{
|
||||
m_SocketType = eSAMSocketTypeStream;
|
||||
m_Session->sockets.push_back (shared_from_this ());
|
||||
m_Stream = m_Session->localDestination->CreateStream (remote);
|
||||
m_Stream->Send ((uint8_t *)m_Buffer, 0); // connect
|
||||
I2PReceive ();
|
||||
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
|
||||
}
|
||||
|
||||
void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet)
|
||||
{
|
||||
if (leaseSet)
|
||||
Connect (leaseSet);
|
||||
else
|
||||
{
|
||||
LogPrint ("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);
|
||||
std::map<std::string, std::string> 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;
|
||||
m_ID = id;
|
||||
m_Session = m_Owner.FindSession (id);
|
||||
if (m_Session)
|
||||
{
|
||||
if (!m_Session->localDestination->IsAcceptingStreams ())
|
||||
{
|
||||
m_SocketType = eSAMSocketTypeAcceptor;
|
||||
m_Session->sockets.push_back (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);
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_STREAM_STATUS_I2P_ERROR, strlen(SAM_STREAM_STATUS_I2P_ERROR), true);
|
||||
}
|
||||
else
|
||||
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
|
||||
}
|
||||
|
||||
size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data)
|
||||
{
|
||||
LogPrint (eLogDebug, "SAM datagram send: ", buf, " ", len);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, params);
|
||||
size_t size = boost::lexical_cast<int>(params[SAM_PARAM_SIZE]), offset = data - buf;
|
||||
if (offset + 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 (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer ", len - offset);
|
||||
return 0; // try to receive more
|
||||
}
|
||||
return offset + size;
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessDestGenerate ()
|
||||
{
|
||||
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,
|
||||
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 ());
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, len, false);
|
||||
}
|
||||
|
||||
void SAMSocket::ProcessNamingLookup (char * buf, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "SAM naming lookup: ", buf);
|
||||
std::map<std::string, std::string> params;
|
||||
ExtractParams (buf, params);
|
||||
std::string& name = params[SAM_PARAM_NAME];
|
||||
i2p::data::IdentityEx identity;
|
||||
i2p::data::IdentHash ident;
|
||||
if (name == "ME")
|
||||
SendNamingLookupReply (m_Session->localDestination->GetIdentity ());
|
||||
else if (context.GetAddressBook ().GetAddress (name, identity))
|
||||
SendNamingLookupReply (identity);
|
||||
else if (m_Session && m_Session->localDestination &&
|
||||
context.GetAddressBook ().GetIdentHash (name, ident))
|
||||
{
|
||||
auto leaseSet = m_Session->localDestination->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
|
||||
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, len, false);
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident)
|
||||
{
|
||||
if (leaseSet)
|
||||
{
|
||||
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 ();
|
||||
#ifdef _MSC_VER
|
||||
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
|
||||
#else
|
||||
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
|
||||
#endif
|
||||
SendMessageReply (m_Buffer, l, false);
|
||||
}
|
||||
|
||||
void SAMSocket::ExtractParams (char * buf, std::map<std::string, std::string>& params)
|
||||
{
|
||||
char * separator;
|
||||
do
|
||||
{
|
||||
separator = strchr (buf, ' ');
|
||||
if (separator) *separator = 0;
|
||||
char * value = strchr (buf, '=');
|
||||
if (value)
|
||||
{
|
||||
*value = 0;
|
||||
value++;
|
||||
params[buf] = value;
|
||||
}
|
||||
buf = separator + 1;
|
||||
}
|
||||
while (separator);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Stream)
|
||||
{
|
||||
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 ();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM stream read error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred),
|
||||
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint ("SAM socket write error: ", ecode.message ());
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Terminate ();
|
||||
}
|
||||
else
|
||||
I2PReceive ();
|
||||
}
|
||||
|
||||
void SAMSocket::HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
LogPrint ("SAM incoming I2P connection for session ", m_ID);
|
||||
m_Stream = stream;
|
||||
context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ());
|
||||
auto session = m_Owner.FindSession (m_ID);
|
||||
if (session)
|
||||
session->localDestination->StopAcceptingStreams ();
|
||||
m_SocketType = eSAMSocketTypeStream;
|
||||
if (!m_IsSilent)
|
||||
{
|
||||
// send remote peer address
|
||||
uint8_t ident[1024];
|
||||
size_t l = stream->GetRemoteIdentity ().ToBuffer (ident, 1024);
|
||||
size_t l1 = i2p::util::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
|
||||
}
|
||||
else
|
||||
I2PReceive ();
|
||||
}
|
||||
else
|
||||
LogPrint (eLogInfo, "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);
|
||||
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
|
||||
size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
|
||||
#endif
|
||||
if (len < SAM_SOCKET_BUFFER_SIZE - l)
|
||||
{
|
||||
memcpy (m_StreamBuffer + l, buf, len);
|
||||
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l),
|
||||
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "SAM received datagram size ", len," exceeds buffer");
|
||||
}
|
||||
|
||||
SAMSession::SAMSession (std::shared_ptr<ClientDestination> dest):
|
||||
localDestination (dest)
|
||||
{
|
||||
}
|
||||
|
||||
SAMSession::~SAMSession ()
|
||||
{
|
||||
for (auto it: sockets)
|
||||
it->SetSocketType (eSAMSocketTypeTerminated);
|
||||
i2p::client::context.DeleteLocalDestination (localDestination);
|
||||
}
|
||||
|
||||
void SAMSession::CloseStreams ()
|
||||
{
|
||||
for (auto it: sockets)
|
||||
{
|
||||
it->CloseStream ();
|
||||
it->SetSocketType (eSAMSocketTypeTerminated);
|
||||
}
|
||||
sockets.clear ();
|
||||
}
|
||||
|
||||
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::address::from_string(address), port)
|
||||
),
|
||||
m_DatagramEndpoint(boost::asio::ip::address::from_string(address), port - 1),
|
||||
m_DatagramSocket(m_Service, m_DatagramEndpoint)
|
||||
{
|
||||
}
|
||||
|
||||
SAMBridge::~SAMBridge ()
|
||||
{
|
||||
if (m_IsRunning)
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void SAMBridge::Start ()
|
||||
{
|
||||
Accept ();
|
||||
ReceiveDatagram ();
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&SAMBridge::Run, this));
|
||||
}
|
||||
|
||||
void SAMBridge::Stop ()
|
||||
{
|
||||
m_IsRunning = false;
|
||||
m_Acceptor.cancel ();
|
||||
for (auto it: m_Sessions)
|
||||
delete it.second;
|
||||
m_Sessions.clear ();
|
||||
m_Service.stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SAMBridge::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint ("SAM: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAMBridge::Accept ()
|
||||
{
|
||||
auto newSocket = std::make_shared<SAMSocket> (*this);
|
||||
m_Acceptor.async_accept (newSocket->GetSocket (), std::bind (&SAMBridge::HandleAccept, this,
|
||||
std::placeholders::_1, newSocket));
|
||||
}
|
||||
|
||||
void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
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 ());
|
||||
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Accept ();
|
||||
}
|
||||
|
||||
SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination,
|
||||
const std::map<std::string, std::string> * params)
|
||||
{
|
||||
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||
if (destination != "")
|
||||
{
|
||||
i2p::data::PrivateKeys keys;
|
||||
keys.FromBase64 (destination);
|
||||
localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params);
|
||||
}
|
||||
else // transient
|
||||
{
|
||||
// extract signature type
|
||||
i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1;
|
||||
if (params)
|
||||
{
|
||||
auto it = params->find (SAM_PARAM_SIGNATURE_TYPE);
|
||||
if (it != params->end ())
|
||||
// TODO: extract string values
|
||||
signatureType = boost::lexical_cast<int> (it->second);
|
||||
}
|
||||
localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, params);
|
||||
}
|
||||
if (localDestination)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto ret = m_Sessions.insert (std::pair<std::string, SAMSession *>(id, new SAMSession (localDestination)));
|
||||
if (!ret.second)
|
||||
LogPrint ("Session ", id, " already exists");
|
||||
return ret.first->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SAMBridge::CloseSession (const std::string& id)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto it = m_Sessions.find (id);
|
||||
if (it != m_Sessions.end ())
|
||||
{
|
||||
auto session = it->second;
|
||||
session->localDestination->StopAcceptingStreams ();
|
||||
session->CloseStreams ();
|
||||
m_Sessions.erase (it);
|
||||
delete session;
|
||||
}
|
||||
}
|
||||
|
||||
SAMSession * SAMBridge::FindSession (const std::string& id) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto it = m_Sessions.find (id);
|
||||
if (it != m_Sessions.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SAMBridge::ReceiveDatagram ()
|
||||
{
|
||||
m_DatagramSocket.async_receive_from (
|
||||
boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE),
|
||||
m_SenderEndpoint,
|
||||
std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
m_DatagramReceiveBuffer[bytes_transferred] = 0;
|
||||
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);
|
||||
char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' ');
|
||||
if (sessionID)
|
||||
{
|
||||
sessionID++;
|
||||
char * destination = strchr (sessionID, ' ');
|
||||
if (destination)
|
||||
{
|
||||
*destination = 0; destination++;
|
||||
auto session = FindSession (sessionID);
|
||||
if (session)
|
||||
{
|
||||
i2p::data::IdentityEx dest;
|
||||
dest.FromBase64 (destination);
|
||||
session->localDestination->GetDatagramDestination ()->
|
||||
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
|
||||
}
|
||||
else
|
||||
LogPrint ("Session ", sessionID, " not found");
|
||||
}
|
||||
else
|
||||
LogPrint ("Missing destination key");
|
||||
}
|
||||
else
|
||||
LogPrint ("Missing sessionID");
|
||||
ReceiveDatagram ();
|
||||
}
|
||||
else
|
||||
LogPrint ("SAM datagram receive error: ", ecode.message ());
|
||||
}
|
||||
}
|
||||
}
|
192
client/SAM.h
Normal file
192
client/SAM.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
#ifndef SAM_H__
|
||||
#define SAM_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
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";
|
||||
const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
|
||||
const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n";
|
||||
const char SAM_SESSION_CREATE[] = "SESSION CREATE";
|
||||
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";
|
||||
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";
|
||||
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_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";
|
||||
const char SAM_PARAM_MAX[] = "MAX";
|
||||
const char SAM_PARAM_STYLE[] = "STYLE";
|
||||
const char SAM_PARAM_ID[] = "ID";
|
||||
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_SIZE[] = "SIZE";
|
||||
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";
|
||||
|
||||
enum SAMSocketType
|
||||
{
|
||||
eSAMSocketTypeUnknown,
|
||||
eSAMSocketTypeSession,
|
||||
eSAMSocketTypeStream,
|
||||
eSAMSocketTypeAcceptor,
|
||||
eSAMSocketTypeTerminated
|
||||
};
|
||||
|
||||
class SAMBridge;
|
||||
struct SAMSession;
|
||||
class SAMSocket: public std::enable_shared_from_this<SAMSocket>
|
||||
{
|
||||
public:
|
||||
|
||||
SAMSocket (SAMBridge& owner);
|
||||
~SAMSocket ();
|
||||
void CloseStream (); // TODO: implement it better
|
||||
|
||||
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:
|
||||
|
||||
void Terminate ();
|
||||
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);
|
||||
void SendMessageReply (const char * msg, size_t len, bool close);
|
||||
void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close);
|
||||
void Receive ();
|
||||
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
|
||||
void I2PReceive ();
|
||||
void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
void HandleWriteI2PData (const boost::system::error_code& ecode);
|
||||
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);
|
||||
void ProcessStreamAccept (char * buf, size_t len);
|
||||
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<std::string, std::string>& params);
|
||||
|
||||
void Connect (std::shared_ptr<const i2p::data::LeaseSet> remote);
|
||||
void HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet);
|
||||
void SendNamingLookupReply (const i2p::data::IdentityEx& identity);
|
||||
void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident);
|
||||
void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode);
|
||||
void SendSessionCreateReplyOk ();
|
||||
|
||||
private:
|
||||
|
||||
SAMBridge& m_Owner;
|
||||
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
|
||||
bool m_IsSilent;
|
||||
std::shared_ptr<i2p::stream::Stream> m_Stream;
|
||||
SAMSession * m_Session;
|
||||
};
|
||||
|
||||
struct SAMSession
|
||||
{
|
||||
std::shared_ptr<ClientDestination> localDestination;
|
||||
std::list<std::shared_ptr<SAMSocket> > sockets;
|
||||
|
||||
SAMSession (std::shared_ptr<ClientDestination> dest);
|
||||
~SAMSession ();
|
||||
|
||||
void CloseStreams ();
|
||||
};
|
||||
|
||||
class SAMBridge
|
||||
{
|
||||
public:
|
||||
|
||||
SAMBridge(const std::string& address, int port);
|
||||
~SAMBridge ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
SAMSession * CreateSession (const std::string& id, const std::string& destination, // empty string means transient
|
||||
const std::map<std::string, std::string> * params);
|
||||
void CloseSession (const std::string& id);
|
||||
SAMSession * FindSession (const std::string& id) const;
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
|
||||
void Accept ();
|
||||
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket);
|
||||
|
||||
void ReceiveDatagram ();
|
||||
void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint;
|
||||
boost::asio::ip::udp::socket m_DatagramSocket;
|
||||
mutable std::mutex m_SessionsMutex;
|
||||
std::map<std::string, SAMSession *> m_Sessions;
|
||||
uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1];
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP
|
||||
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
577
client/SOCKS.cpp
Normal file
577
client/SOCKS.cpp
Normal file
|
@ -0,0 +1,577 @@
|
|||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include "SOCKS.h"
|
||||
#include "Identity.h"
|
||||
#include "Streaming.h"
|
||||
#include "Destination.h"
|
||||
#include "ClientContext.h"
|
||||
#include "util/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<SOCKSHandler>
|
||||
{
|
||||
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<i2p::stream::Stream> stream);
|
||||
|
||||
uint8_t m_sock_buff[socks_buffer_size];
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock;
|
||||
std::shared_ptr<i2p::stream::Stream> 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, std::shared_ptr<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); }
|
||||
~SOCKSHandler() { Terminate(); }
|
||||
void Handle() { AsyncSockRead(); }
|
||||
};
|
||||
|
||||
void SOCKSHandler::AsyncSockRead()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
void SOCKSHandler::Terminate()
|
||||
{
|
||||
if (Kill()) return;
|
||||
if (m_sock)
|
||||
{
|
||||
LogPrint(eLogDebug,"--- SOCKS close sock");
|
||||
m_sock->close();
|
||||
m_sock = nullptr;
|
||||
}
|
||||
if (m_stream)
|
||||
{
|
||||
LogPrint(eLogDebug,"--- SOCKS close stream");
|
||||
m_stream.reset ();
|
||||
}
|
||||
Done(shared_from_this());
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
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
|
||||
switch (type)
|
||||
{
|
||||
case ADDR_IPV4:
|
||||
size = 10;
|
||||
htobe32buf(m_response+4,addr.ip);
|
||||
break;
|
||||
case ADDR_IPV6:
|
||||
size = 22;
|
||||
memcpy(m_response+4,addr.ipv6, 16);
|
||||
break;
|
||||
case ADDR_DNS:
|
||||
size = 7+addr.dns.size;
|
||||
m_response[4] = addr.dns.size;
|
||||
memcpy(m_response+5,addr.dns.value, addr.dns.size);
|
||||
break;
|
||||
}
|
||||
htobe16buf(m_response+size-2,port); //Port
|
||||
return boost::asio::const_buffers_1(m_response,size);
|
||||
}
|
||||
|
||||
bool SOCKSHandler::Socks5ChooseAuth()
|
||||
{
|
||||
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,"--- SOCKS5 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);
|
||||
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse,
|
||||
shared_from_this(), std::placeholders::_1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* All hope is lost beyond this point */
|
||||
void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error)
|
||||
{
|
||||
boost::asio::const_buffers_1 response(nullptr,0);
|
||||
assert(error != SOCKS4_OK && error != SOCKS5_OK);
|
||||
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;
|
||||
case SOCKS5:
|
||||
LogPrint(eLogWarning,"--- SOCKS5 failed: ", error);
|
||||
response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port);
|
||||
break;
|
||||
}
|
||||
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
|
||||
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
|
||||
switch (m_socksv)
|
||||
{
|
||||
case SOCKS4:
|
||||
LogPrint(eLogInfo,"--- SOCKS4 connection success");
|
||||
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
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;
|
||||
case GET4A_HOST:
|
||||
case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break;
|
||||
case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break;
|
||||
default:;
|
||||
}
|
||||
m_parseleft = parseleft;
|
||||
m_state = nstate;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case SOCKS5:
|
||||
LogPrint(eLogError,"--- SOCKS5 unsupported address type: ", m_addrtype);
|
||||
break;
|
||||
case SOCKS4:
|
||||
LogPrint(eLogError,"--- SOCKS4a rejected because it's actually SOCKS4");
|
||||
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)
|
||||
{
|
||||
LogPrint(eLogError,"--- SOCKS invalid hostname: ", m_address.dns.ToString());
|
||||
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case GET_SOCKSV:
|
||||
m_socksv = (SOCKSHandler::socksVersions) *sock_buff;
|
||||
switch (*sock_buff)
|
||||
{
|
||||
case SOCKS4:
|
||||
EnterState(GET_COMMAND); //Initialize the parser at the right position
|
||||
break;
|
||||
case SOCKS5:
|
||||
EnterState(GET5_AUTHNUM); //Initialize the parser at the right position
|
||||
break;
|
||||
default:
|
||||
LogPrint(eLogError,"--- SOCKS rejected invalid version: ", ((int)*sock_buff));
|
||||
Terminate();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GET5_AUTHNUM:
|
||||
EnterState(GET5_AUTH, *sock_buff);
|
||||
break;
|
||||
case GET5_AUTH:
|
||||
m_parseleft --;
|
||||
if (*sock_buff == AUTH_NONE)
|
||||
m_authchosen = AUTH_NONE;
|
||||
if ( m_parseleft == 0 )
|
||||
{
|
||||
if (!Socks5ChooseAuth()) return false;
|
||||
EnterState(GET5_REQUESTV);
|
||||
}
|
||||
break;
|
||||
case GET_COMMAND:
|
||||
switch (*sock_buff)
|
||||
{
|
||||
case CMD_CONNECT:
|
||||
case CMD_BIND:
|
||||
break;
|
||||
case CMD_UDP:
|
||||
if (m_socksv == SOCKS5) break;
|
||||
default:
|
||||
LogPrint(eLogError,"--- SOCKS invalid command: ", ((int)*sock_buff));
|
||||
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||
return false;
|
||||
}
|
||||
m_cmd = (SOCKSHandler::cmdTypes)*sock_buff;
|
||||
switch (m_socksv)
|
||||
{
|
||||
case SOCKS5: EnterState(GET5_GETRSV); break;
|
||||
case SOCKS4: EnterState(GET_PORT); break;
|
||||
}
|
||||
break;
|
||||
case GET_PORT:
|
||||
m_port = (m_port << 8)|((uint16_t)*sock_buff);
|
||||
m_parseleft--;
|
||||
if (m_parseleft == 0)
|
||||
{
|
||||
switch (m_socksv)
|
||||
{
|
||||
case SOCKS5: EnterState(DONE); break;
|
||||
case SOCKS4: EnterState(GET_IPV4); 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)
|
||||
{
|
||||
case SOCKS5: EnterState(GET_PORT); break;
|
||||
case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GET4_IDENT:
|
||||
if (!*sock_buff)
|
||||
{
|
||||
if( m_4aip == 0 || m_4aip > 255 )
|
||||
EnterState(DONE);
|
||||
else
|
||||
EnterState(GET4A_HOST);
|
||||
}
|
||||
break;
|
||||
case GET4A_HOST:
|
||||
if (!*sock_buff)
|
||||
{
|
||||
EnterState(DONE);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
case GET5_REQUESTV:
|
||||
if (*sock_buff != SOCKS5)
|
||||
{
|
||||
LogPrint(eLogError,"--- SOCKS5 rejected unknown request version: ", ((int)*sock_buff));
|
||||
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||
return false;
|
||||
}
|
||||
EnterState(GET_COMMAND);
|
||||
break;
|
||||
case GET5_GETRSV:
|
||||
if ( *sock_buff != 0 )
|
||||
{
|
||||
LogPrint(eLogError,"--- SOCKS5 unknown reserved field: ", ((int)*sock_buff));
|
||||
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||
return false;
|
||||
}
|
||||
EnterState(GET5_GETADDRTYPE);
|
||||
break;
|
||||
case GET5_GETADDRTYPE:
|
||||
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;
|
||||
default:
|
||||
LogPrint(eLogError,"--- SOCKS5 unknown address type: ", ((int)*sock_buff));
|
||||
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GET5_IPV6:
|
||||
m_address.ipv6[16-m_parseleft] = *sock_buff;
|
||||
m_parseleft--;
|
||||
if (m_parseleft == 0) EnterState(GET_PORT);
|
||||
break;
|
||||
case GET5_HOST_SIZE:
|
||||
EnterState(GET5_HOST, *sock_buff);
|
||||
break;
|
||||
case GET5_HOST:
|
||||
m_address.dns.push_back(*sock_buff);
|
||||
m_parseleft--;
|
||||
if (m_parseleft == 0) EnterState(GET_PORT);
|
||||
break;
|
||||
default:
|
||||
LogPrint(eLogError,"--- SOCKS parse state?? ", m_state);
|
||||
Terminate();
|
||||
return false;
|
||||
}
|
||||
sock_buff++;
|
||||
len--;
|
||||
if (m_state == DONE)
|
||||
{
|
||||
m_remaining_data_len = len;
|
||||
m_remaining_data = sock_buff;
|
||||
return ValidateSOCKSRequest();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
|
||||
{
|
||||
LogPrint(eLogDebug,"--- SOCKS sock recv: ", len);
|
||||
if(ecode)
|
||||
{
|
||||
LogPrint(eLogWarning," --- SOCKS sock recv got error: ", ecode);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
AsyncSockRead();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
if (Kill()) return;
|
||||
LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection");
|
||||
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, m_stream);
|
||||
GetOwner()->AddHandler (connection);
|
||||
connection->I2PConnect (m_remaining_data,m_remaining_data_len);
|
||||
Done(shared_from_this());
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError,"--- SOCKS Closing socket after completion reply because: ", ecode.message ());
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode)
|
||||
{
|
||||
if (ecode)
|
||||
{
|
||||
LogPrint (eLogError,"--- SOCKS Closing socket after sending reply because: ", ecode.message ());
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
m_stream = stream;
|
||||
SocksRequestSuccess();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError,"--- SOCKS Issue when creating the stream, check the previous warnings for more info.");
|
||||
SocksRequestFailed(SOCKS5_HOST_UNREACH);
|
||||
}
|
||||
}
|
||||
|
||||
SOCKSServer::SOCKSServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination)
|
||||
: TCPIPAcceptor(
|
||||
address, port,
|
||||
localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination()
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::client::I2PServiceHandler> SOCKSServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||
{
|
||||
return std::make_shared<SOCKSHandler> (this, socket);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
33
client/SOCKS.h
Normal file
33
client/SOCKS.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef SOCKS_H__
|
||||
#define SOCKS_H__
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <boost/asio.hpp>
|
||||
#include <mutex>
|
||||
#include "I2PService.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace proxy
|
||||
{
|
||||
class SOCKSServer: public i2p::client::TCPIPAcceptor
|
||||
{
|
||||
public:
|
||||
|
||||
SOCKSServer(const std::string& address, int port,
|
||||
std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
|
||||
~SOCKSServer() {};
|
||||
|
||||
protected:
|
||||
// Implements TCPIPAcceptor
|
||||
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
const char* GetName() { return "SOCKS"; }
|
||||
};
|
||||
|
||||
typedef SOCKSServer SOCKSProxy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
462
client/Win32Service.cpp
Normal file
462
client/Win32Service.cpp
Normal file
|
@ -0,0 +1,462 @@
|
|||
#ifdef _WIN32
|
||||
#define _CRT_SECURE_NO_WARNINGS // to use freopen
|
||||
#endif
|
||||
|
||||
#include "Win32Service.h"
|
||||
#include <assert.h>
|
||||
#include <strsafe.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "Daemon.h"
|
||||
#include "Log.h"
|
||||
|
||||
I2PService *I2PService::s_service = NULL;
|
||||
|
||||
BOOL I2PService::isService()
|
||||
{
|
||||
BOOL bIsService = FALSE;
|
||||
|
||||
HWINSTA hWinStation = GetProcessWindowStation();
|
||||
if (hWinStation != NULL)
|
||||
{
|
||||
USEROBJECTFLAGS uof = { 0 };
|
||||
if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0))
|
||||
{
|
||||
bIsService = TRUE;
|
||||
}
|
||||
}
|
||||
return bIsService;
|
||||
}
|
||||
|
||||
BOOL I2PService::Run(I2PService &service)
|
||||
{
|
||||
s_service = &service;
|
||||
|
||||
SERVICE_TABLE_ENTRY serviceTable[] =
|
||||
{
|
||||
{ service.m_name, ServiceMain },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
return StartServiceCtrlDispatcher(serviceTable);
|
||||
}
|
||||
|
||||
|
||||
void WINAPI I2PService::ServiceMain(DWORD dwArgc, PSTR *pszArgv)
|
||||
{
|
||||
assert(s_service != NULL);
|
||||
|
||||
s_service->m_statusHandle = RegisterServiceCtrlHandler(
|
||||
s_service->m_name, ServiceCtrlHandler);
|
||||
if (s_service->m_statusHandle == NULL)
|
||||
{
|
||||
throw GetLastError();
|
||||
}
|
||||
|
||||
s_service->Start(dwArgc, pszArgv);
|
||||
}
|
||||
|
||||
|
||||
void WINAPI I2PService::ServiceCtrlHandler(DWORD dwCtrl)
|
||||
{
|
||||
switch (dwCtrl)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP: s_service->Stop(); break;
|
||||
case SERVICE_CONTROL_PAUSE: s_service->Pause(); break;
|
||||
case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break;
|
||||
case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break;
|
||||
case SERVICE_CONTROL_INTERROGATE: break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
I2PService::I2PService(PSTR pszServiceName,
|
||||
BOOL fCanStop,
|
||||
BOOL fCanShutdown,
|
||||
BOOL fCanPauseContinue)
|
||||
{
|
||||
m_name = (pszServiceName == NULL) ? "" : pszServiceName;
|
||||
|
||||
m_statusHandle = NULL;
|
||||
|
||||
m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
|
||||
m_status.dwCurrentState = SERVICE_START_PENDING;
|
||||
|
||||
DWORD dwControlsAccepted = 0;
|
||||
if (fCanStop)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_STOP;
|
||||
if (fCanShutdown)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN;
|
||||
if (fCanPauseContinue)
|
||||
dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;
|
||||
m_status.dwControlsAccepted = dwControlsAccepted;
|
||||
|
||||
m_status.dwWin32ExitCode = NO_ERROR;
|
||||
m_status.dwServiceSpecificExitCode = 0;
|
||||
m_status.dwCheckPoint = 0;
|
||||
m_status.dwWaitHint = 0;
|
||||
|
||||
m_fStopping = FALSE;
|
||||
|
||||
// Create a manual-reset event that is not signaled at first to indicate
|
||||
// the stopped signal of the service.
|
||||
m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (m_hStoppedEvent == NULL)
|
||||
{
|
||||
throw GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
I2PService::~I2PService(void)
|
||||
{
|
||||
if (m_hStoppedEvent)
|
||||
{
|
||||
CloseHandle(m_hStoppedEvent);
|
||||
m_hStoppedEvent = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::Start(DWORD dwArgc, PSTR *pszArgv)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetServiceStatus(SERVICE_START_PENDING);
|
||||
|
||||
OnStart(dwArgc, pszArgv);
|
||||
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
}
|
||||
catch (DWORD dwError)
|
||||
{
|
||||
LogPrint("Win32Service Start", dwError);
|
||||
|
||||
SetServiceStatus(SERVICE_STOPPED, dwError);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogPrint("Win32Service failed to start.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv)
|
||||
{
|
||||
LogPrint("Win32Service in OnStart",
|
||||
EVENTLOG_INFORMATION_TYPE);
|
||||
|
||||
Daemon.start();
|
||||
|
||||
//i2p::util::config::OptionParser(dwArgc, pszArgv);
|
||||
//i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
|
||||
//i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"),
|
||||
// i2p::util::config::GetArg("-port", 17070));
|
||||
|
||||
_worker = new std::thread(std::bind(&I2PService::WorkerThread, this));
|
||||
}
|
||||
|
||||
|
||||
void I2PService::WorkerThread()
|
||||
{
|
||||
while (!m_fStopping)
|
||||
{
|
||||
::Sleep(1000); // Simulate some lengthy operations.
|
||||
}
|
||||
|
||||
// Signal the stopped event.
|
||||
SetEvent(m_hStoppedEvent);
|
||||
}
|
||||
|
||||
|
||||
void I2PService::Stop()
|
||||
{
|
||||
DWORD dwOriginalState = m_status.dwCurrentState;
|
||||
try
|
||||
{
|
||||
SetServiceStatus(SERVICE_STOP_PENDING);
|
||||
|
||||
OnStop();
|
||||
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
}
|
||||
catch (DWORD dwError)
|
||||
{
|
||||
LogPrint("Win32Service Stop", dwError);
|
||||
|
||||
SetServiceStatus(dwOriginalState);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogPrint("Win32Service failed to stop.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
SetServiceStatus(dwOriginalState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::OnStop()
|
||||
{
|
||||
// Log a service stop message to the Application log.
|
||||
LogPrint("Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE);
|
||||
|
||||
Daemon.stop();
|
||||
|
||||
m_fStopping = TRUE;
|
||||
if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0)
|
||||
{
|
||||
throw GetLastError();
|
||||
}
|
||||
_worker->join();
|
||||
delete _worker;
|
||||
}
|
||||
|
||||
|
||||
void I2PService::Pause()
|
||||
{
|
||||
try
|
||||
{
|
||||
SetServiceStatus(SERVICE_PAUSE_PENDING);
|
||||
|
||||
OnPause();
|
||||
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
}
|
||||
catch (DWORD dwError)
|
||||
{
|
||||
LogPrint("Win32Service Pause", dwError);
|
||||
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogPrint("Win32Service failed to pause.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::OnPause()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void I2PService::Continue()
|
||||
{
|
||||
try
|
||||
{
|
||||
SetServiceStatus(SERVICE_CONTINUE_PENDING);
|
||||
|
||||
OnContinue();
|
||||
|
||||
SetServiceStatus(SERVICE_RUNNING);
|
||||
}
|
||||
catch (DWORD dwError)
|
||||
{
|
||||
LogPrint("Win32Service Continue", dwError);
|
||||
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogPrint("Win32Service failed to resume.", EVENTLOG_ERROR_TYPE);
|
||||
|
||||
SetServiceStatus(SERVICE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::OnContinue()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void I2PService::Shutdown()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnShutdown();
|
||||
|
||||
SetServiceStatus(SERVICE_STOPPED);
|
||||
}
|
||||
catch (DWORD dwError)
|
||||
{
|
||||
LogPrint("Win32Service Shutdown", dwError);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogPrint("Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2PService::OnShutdown()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void I2PService::SetServiceStatus(DWORD dwCurrentState,
|
||||
DWORD dwWin32ExitCode,
|
||||
DWORD dwWaitHint)
|
||||
{
|
||||
static DWORD dwCheckPoint = 1;
|
||||
|
||||
|
||||
m_status.dwCurrentState = dwCurrentState;
|
||||
m_status.dwWin32ExitCode = dwWin32ExitCode;
|
||||
m_status.dwWaitHint = dwWaitHint;
|
||||
|
||||
m_status.dwCheckPoint =
|
||||
((dwCurrentState == SERVICE_RUNNING) ||
|
||||
(dwCurrentState == SERVICE_STOPPED)) ?
|
||||
0 : dwCheckPoint++;
|
||||
|
||||
::SetServiceStatus(m_statusHandle, &m_status);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
|
||||
void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService)
|
||||
{
|
||||
if (schSCManager)
|
||||
{
|
||||
CloseServiceHandle(schSCManager);
|
||||
schSCManager = NULL;
|
||||
}
|
||||
if (schService)
|
||||
{
|
||||
CloseServiceHandle(schService);
|
||||
schService = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallService(PSTR pszServiceName,
|
||||
PSTR pszDisplayName,
|
||||
DWORD dwStartType,
|
||||
PSTR pszDependencies,
|
||||
PSTR pszAccount,
|
||||
PSTR pszPassword)
|
||||
{
|
||||
printf("Try to install Win32Service (%s).\n", pszServiceName);
|
||||
|
||||
char szPath[MAX_PATH];
|
||||
SC_HANDLE schSCManager = NULL;
|
||||
SC_HANDLE schService = NULL;
|
||||
|
||||
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0)
|
||||
{
|
||||
printf("GetModuleFileName failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the local default service control manager database
|
||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT |
|
||||
SC_MANAGER_CREATE_SERVICE);
|
||||
if (schSCManager == NULL)
|
||||
{
|
||||
printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
// Install the service into SCM by calling CreateService
|
||||
schService = CreateService(
|
||||
schSCManager, // SCManager database
|
||||
pszServiceName, // Name of service
|
||||
pszDisplayName, // Name to display
|
||||
SERVICE_QUERY_STATUS, // Desired access
|
||||
SERVICE_WIN32_OWN_PROCESS, // Service type
|
||||
dwStartType, // Service start type
|
||||
SERVICE_ERROR_NORMAL, // Error control type
|
||||
szPath, // Service's binary
|
||||
NULL, // No load ordering group
|
||||
NULL, // No tag identifier
|
||||
pszDependencies, // Dependencies
|
||||
pszAccount, // Service running account
|
||||
pszPassword // Password of the account
|
||||
);
|
||||
if (schService == NULL)
|
||||
{
|
||||
printf("CreateService failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Win32Service is installed as %s.\n", pszServiceName);
|
||||
|
||||
// Centralized cleanup for all allocated resources.
|
||||
FreeHandles(schSCManager, schService);
|
||||
}
|
||||
|
||||
void UninstallService(PSTR pszServiceName)
|
||||
{
|
||||
printf("Try to uninstall Win32Service (%s).\n", pszServiceName);
|
||||
|
||||
SC_HANDLE schSCManager = NULL;
|
||||
SC_HANDLE schService = NULL;
|
||||
SERVICE_STATUS ssSvcStatus = {};
|
||||
|
||||
// Open the local default service control manager database
|
||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (schSCManager == NULL)
|
||||
{
|
||||
printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the service with delete, stop, and query status permissions
|
||||
schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP |
|
||||
SERVICE_QUERY_STATUS | DELETE);
|
||||
if (schService == NULL)
|
||||
{
|
||||
printf("OpenService failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to stop the service
|
||||
if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus))
|
||||
{
|
||||
printf("Stopping %s.\n", pszServiceName);
|
||||
Sleep(1000);
|
||||
|
||||
while (QueryServiceStatus(schService, &ssSvcStatus))
|
||||
{
|
||||
if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING)
|
||||
{
|
||||
printf(".");
|
||||
Sleep(1000);
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED)
|
||||
{
|
||||
printf("\n%s is stopped.\n", pszServiceName);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n%s failed to stop.\n", pszServiceName);
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove the service by calling DeleteService.
|
||||
if (!DeleteService(schService))
|
||||
{
|
||||
printf("DeleteService failed w/err 0x%08lx\n", GetLastError());
|
||||
FreeHandles(schSCManager, schService);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s is removed.\n", pszServiceName);
|
||||
|
||||
// Centralized cleanup for all allocated resources.
|
||||
FreeHandles(schSCManager, schService);
|
||||
}
|
85
client/Win32Service.h
Normal file
85
client/Win32Service.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
#ifndef WIN_32_SERVICE_H__
|
||||
#define WIN_32_SERVICE_H__
|
||||
|
||||
#include <thread>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// Internal name of the service
|
||||
#define SERVICE_NAME "i2pService"
|
||||
|
||||
// Displayed name of the service
|
||||
#define SERVICE_DISPLAY_NAME "i2p router service"
|
||||
|
||||
// Service start options.
|
||||
#define SERVICE_START_TYPE SERVICE_DEMAND_START
|
||||
|
||||
// List of service dependencies - "dep1\0dep2\0\0"
|
||||
#define SERVICE_DEPENDENCIES ""
|
||||
|
||||
// The name of the account under which the service should run
|
||||
#define SERVICE_ACCOUNT "NT AUTHORITY\\LocalService"
|
||||
|
||||
// The password to the service account name
|
||||
#define SERVICE_PASSWORD NULL
|
||||
#endif
|
||||
|
||||
|
||||
class I2PService
|
||||
{
|
||||
public:
|
||||
|
||||
I2PService(PSTR pszServiceName,
|
||||
BOOL fCanStop = TRUE,
|
||||
BOOL fCanShutdown = TRUE,
|
||||
BOOL fCanPauseContinue = FALSE);
|
||||
|
||||
virtual ~I2PService(void);
|
||||
|
||||
static BOOL isService();
|
||||
static BOOL Run(I2PService &service);
|
||||
void Stop();
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
BOOL m_fStopping;
|
||||
HANDLE m_hStoppedEvent;
|
||||
|
||||
std::thread* _worker;
|
||||
};
|
||||
|
||||
void InstallService(PSTR pszServiceName,
|
||||
PSTR pszDisplayName,
|
||||
DWORD dwStartType,
|
||||
PSTR pszDependencies,
|
||||
PSTR pszAccount,
|
||||
PSTR pszPassword);
|
||||
|
||||
void UninstallService(PSTR pszServiceName);
|
||||
|
||||
#endif // WIN_32_SERVICE_H__
|
19
client/i2p.cpp
Normal file
19
client/i2p.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <thread>
|
||||
#include <stdlib.h>
|
||||
#include "Daemon.h"
|
||||
#include "Reseed.h"
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
Daemon.init(argc, argv);
|
||||
if (Daemon.start())
|
||||
{
|
||||
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;
|
||||
}
|
404
client/i2pcontrol/I2PControl.cpp
Normal file
404
client/i2pcontrol/I2PControl.cpp
Normal file
|
@ -0,0 +1,404 @@
|
|||
// 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))
|
||||
// TODO: handle this somewhere, but definitely not here
|
||||
|
||||
#include "I2PControl.h"
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <cryptopp/osrng.h>
|
||||
#include <cryptopp/hex.h>
|
||||
#include <cryptopp/filters.h>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include "util/Log.h"
|
||||
#include "util/Timestamp.h"
|
||||
#include "transport/Transports.h"
|
||||
#include "tunnel/Tunnel.h"
|
||||
#include "NetDb.h"
|
||||
#include "version.h"
|
||||
#include "Daemon.h"
|
||||
|
||||
namespace i2p {
|
||||
namespace client {
|
||||
|
||||
I2PControlSession::Response::Response(const std::string& version)
|
||||
: id(), version(version), error(ErrorCode::None), parameters()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string I2PControlSession::Response::toJsonString() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "{\"id\":" << id << ",\"result\":{";
|
||||
for(auto it = parameters.begin(); it != parameters.end(); ++it) {
|
||||
if(it != parameters.begin())
|
||||
oss << ',';
|
||||
oss << '"' << it->first << "\":" << it->second;
|
||||
}
|
||||
oss << "},\"jsonrpc\":\"" << version << '"';
|
||||
if(error != ErrorCode::None)
|
||||
oss << ",\"error\":{\"code\":" << -static_cast<int>(error)
|
||||
<< ",\"message\":\"" << getErrorMsg() << "\"" << "}";
|
||||
oss << "}";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string I2PControlSession::Response::getErrorMsg() const
|
||||
{
|
||||
switch(error) {
|
||||
case ErrorCode::MethodNotFound:
|
||||
return "Method not found.";
|
||||
case ErrorCode::InvalidParameters:
|
||||
return "Invalid parameters.";
|
||||
case ErrorCode::InvalidRequest:
|
||||
return "Invalid request.";
|
||||
case ErrorCode::ParseError:
|
||||
return "Json parse error.";
|
||||
case ErrorCode::InvalidPassword:
|
||||
return "Invalid password.";
|
||||
case ErrorCode::NoToken:
|
||||
return "No authentication token given.";
|
||||
case ErrorCode::NonexistentToken:
|
||||
return "Nonexistent authentication token given.";
|
||||
case ErrorCode::ExpiredToken:
|
||||
return "Exipred authentication token given.";
|
||||
case ErrorCode::UnspecifiedVersion:
|
||||
return "Version not specified.";
|
||||
case ErrorCode::UnsupportedVersion:
|
||||
return "Version not supported.";
|
||||
default:
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
void I2PControlSession::Response::setParam(const std::string& param, const std::string& value)
|
||||
{
|
||||
parameters[param] = value.empty() ? "null" : "\"" + value + "\"";
|
||||
}
|
||||
|
||||
void I2PControlSession::Response::setParam(const std::string& param, int value)
|
||||
{
|
||||
parameters[param] = std::to_string(value);
|
||||
}
|
||||
|
||||
void I2PControlSession::Response::setParam(const std::string& param, double value)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(2) << value;
|
||||
parameters[param] = oss.str();
|
||||
}
|
||||
|
||||
void I2PControlSession::Response::setError(ErrorCode code)
|
||||
{
|
||||
error = code;
|
||||
}
|
||||
|
||||
void I2PControlSession::Response::setId(const std::string& identifier)
|
||||
{
|
||||
id = identifier;
|
||||
}
|
||||
|
||||
I2PControlSession::I2PControlSession(boost::asio::io_service& ios, const std::string& pass)
|
||||
: password(pass), tokens(), tokensMutex(),
|
||||
service(ios), shutdownTimer(ios), expireTokensTimer(ios)
|
||||
{
|
||||
// Method handlers
|
||||
methodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate;
|
||||
methodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlSession::handleEcho;
|
||||
methodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl;
|
||||
methodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo;
|
||||
methodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager;
|
||||
methodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting;
|
||||
// RouterInfo handlers
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S;
|
||||
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S;
|
||||
|
||||
// RouterManager handlers
|
||||
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown;
|
||||
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful;
|
||||
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed;
|
||||
}
|
||||
|
||||
void I2PControlSession::start()
|
||||
{
|
||||
startExpireTokensJob();
|
||||
}
|
||||
|
||||
void I2PControlSession::stop()
|
||||
{
|
||||
boost::system::error_code e; // Make sure this doesn't throw
|
||||
shutdownTimer.cancel(e);
|
||||
expireTokensTimer.cancel(e);
|
||||
}
|
||||
|
||||
I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream& request)
|
||||
{
|
||||
boost::property_tree::ptree pt;
|
||||
boost::property_tree::read_json(request, pt);
|
||||
|
||||
Response response;
|
||||
try {
|
||||
response.setId(pt.get<std::string>(I2P_CONTROL_PROPERTY_ID));
|
||||
|
||||
std::string method = pt.get<std::string>(I2P_CONTROL_PROPERTY_METHOD);
|
||||
auto it = methodHandlers.find(method);
|
||||
if(it == methodHandlers.end()) { // Not found
|
||||
LogPrint(eLogWarning, "Unknown I2PControl method ", method);
|
||||
response.setError(ErrorCode::MethodNotFound);
|
||||
return response;
|
||||
}
|
||||
|
||||
PropertyTree params = pt.get_child(I2P_CONTROL_PROPERTY_PARAMS);
|
||||
if(method != I2P_CONTROL_METHOD_AUTHENTICATE && !authenticate(params, response)) {
|
||||
LogPrint(eLogWarning, "I2PControl invalid token presented");
|
||||
return response;
|
||||
}
|
||||
// Call the appropriate handler
|
||||
(this->*(it->second))(params, response);
|
||||
|
||||
} catch(const boost::property_tree::ptree_error& error) {
|
||||
response.setError(ErrorCode::ParseError);
|
||||
} catch(...) {
|
||||
response.setError(ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
try {
|
||||
std::string token = pt.get<std::string>(I2P_CONTROL_PARAM_TOKEN);
|
||||
|
||||
std::lock_guard<std::mutex> lock(tokensMutex);
|
||||
auto it = tokens.find(token);
|
||||
if(it == tokens.end()) {
|
||||
response.setError(ErrorCode::NonexistentToken);
|
||||
return false;
|
||||
} else if(util::GetSecondsSinceEpoch() - it->second > I2P_CONTROL_TOKEN_LIFETIME) {
|
||||
response.setError(ErrorCode::ExpiredToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch(const boost::property_tree::ptree_error& error) {
|
||||
response.setError(ErrorCode::NoToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string I2PControlSession::generateToken() const
|
||||
{
|
||||
byte random_data[I2P_CONTROL_TOKEN_SIZE] = {};
|
||||
CryptoPP::AutoSeededRandomPool rng;
|
||||
rng.GenerateBlock(random_data, I2P_CONTROL_TOKEN_SIZE);
|
||||
std::string token;
|
||||
CryptoPP::StringSource ss(
|
||||
random_data, I2P_CONTROL_TOKEN_SIZE, true,
|
||||
new CryptoPP::HexEncoder(new CryptoPP::StringSink(token))
|
||||
);
|
||||
return token;
|
||||
}
|
||||
|
||||
void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
const int api = pt.get<int>(I2P_CONTROL_PARAM_API);
|
||||
const std::string given_pass = pt.get<std::string>(I2P_CONTROL_PARAM_PASSWORD);
|
||||
LogPrint(eLogDebug, "I2PControl Authenticate API = ", api, " Password = ", given_pass);
|
||||
if(given_pass != password) {
|
||||
LogPrint(
|
||||
eLogError, "I2PControl Authenticate Invalid password ", given_pass,
|
||||
" expected ", password
|
||||
);
|
||||
response.setError(ErrorCode::InvalidPassword);
|
||||
return;
|
||||
}
|
||||
const std::string token = generateToken();
|
||||
response.setParam(I2P_CONTROL_PARAM_API, api);
|
||||
response.setParam(I2P_CONTROL_PARAM_TOKEN, token);
|
||||
|
||||
std::lock_guard<std::mutex> lock(tokensMutex);
|
||||
tokens.insert(std::make_pair(token, util::GetSecondsSinceEpoch()));
|
||||
}
|
||||
|
||||
void I2PControlSession::handleEcho(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
const std::string echo = pt.get<std::string>(I2P_CONTROL_PARAM_ECHO);
|
||||
LogPrint(eLogDebug, "I2PControl Echo Echo = ", echo);
|
||||
response.setParam(I2P_CONTROL_PARAM_RESULT, echo);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleI2PControl(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
LogPrint(eLogDebug, "I2PControl I2PControl");
|
||||
// TODO: implement
|
||||
|
||||
}
|
||||
|
||||
void I2PControlSession::handleRouterInfo(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
LogPrint(eLogDebug, "I2PControl RouterInfo");
|
||||
for(const auto& pair : pt) {
|
||||
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
|
||||
continue;
|
||||
LogPrint(eLogDebug, pair.first);
|
||||
auto it = routerInfoHandlers.find(pair.first);
|
||||
if(it != routerInfoHandlers.end()) {
|
||||
(this->*(it->second))(response);
|
||||
} else {
|
||||
LogPrint(eLogError, "I2PControl RouterInfo unknown request ", pair.first);
|
||||
response.setError(ErrorCode::InvalidRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2PControlSession::handleRouterManager(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
LogPrint(eLogDebug, "I2PControl RouterManager");
|
||||
for(const auto& pair : pt) {
|
||||
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
|
||||
continue;
|
||||
LogPrint(eLogDebug, pair.first);
|
||||
auto it = routerManagerHandlers.find(pair.first);
|
||||
if(it != routerManagerHandlers.end()) {
|
||||
(this->*(it->second))(response);
|
||||
} else {
|
||||
LogPrint(eLogError, "I2PControl RouterManager unknown request ", pair.first);
|
||||
response.setError(ErrorCode::InvalidRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2PControlSession::handleNetworkSetting(const PropertyTree& pt, Response& response)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void I2PControlSession::handleUptime(Response& response)
|
||||
{
|
||||
response.setParam(I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime()*1000);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleVersion(Response& response)
|
||||
{
|
||||
response.setParam(I2P_CONTROL_ROUTER_INFO_VERSION, VERSION);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleStatus(Response& response)
|
||||
{
|
||||
response.setParam(I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO:
|
||||
}
|
||||
|
||||
void I2PControlSession::handleNetDbKnownPeers(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleNetDbActivePeers(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS,
|
||||
(int)i2p::transport::transports.GetPeers().size()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleNetStatus(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleTunnelsParticipating(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING,
|
||||
(int)i2p::tunnel::tunnels.GetTransitTunnels().size()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleInBandwidth1S(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_BW_IB_1S,
|
||||
(double)i2p::transport::transports.GetInBandwidth()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleOutBandwidth1S(Response& response)
|
||||
{
|
||||
response.setParam(
|
||||
I2P_CONTROL_ROUTER_INFO_BW_OB_1S,
|
||||
(double)i2p::transport::transports.GetOutBandwidth()
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlSession::handleShutdown(Response& response)
|
||||
{
|
||||
LogPrint(eLogInfo, "Shutdown requested");
|
||||
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, "");
|
||||
// 1 second to make sure response has been sent
|
||||
shutdownTimer.expires_from_now(boost::posix_time::seconds(1));
|
||||
shutdownTimer.async_wait([](const boost::system::error_code& ecode) {
|
||||
Daemon.running = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void I2PControlSession::handleShutdownGraceful(Response& response)
|
||||
{
|
||||
i2p::context.SetAcceptsTunnels(false);
|
||||
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout();
|
||||
LogPrint(eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds");
|
||||
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL, "");
|
||||
shutdownTimer.expires_from_now(boost::posix_time::seconds(timeout + 1));
|
||||
shutdownTimer.async_wait([](const boost::system::error_code& ecode) {
|
||||
Daemon.running = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void I2PControlSession::handleReseed(Response& response)
|
||||
{
|
||||
LogPrint(eLogInfo, "Reseed requested");
|
||||
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, "");
|
||||
i2p::data::netdb.Reseed();
|
||||
}
|
||||
|
||||
void I2PControlSession::expireTokens(const boost::system::error_code& error)
|
||||
{
|
||||
if(error == boost::asio::error::operation_aborted)
|
||||
return; // Do not restart timer, shutting down
|
||||
|
||||
startExpireTokensJob();
|
||||
LogPrint(eLogDebug, "I2PControl is expiring tokens.");
|
||||
const uint64_t now = util::GetSecondsSinceEpoch();
|
||||
std::lock_guard<std::mutex> lock(tokensMutex);
|
||||
for(auto it = tokens.begin(); it != tokens.end(); ) {
|
||||
if(now - it->second > I2P_CONTROL_TOKEN_LIFETIME)
|
||||
it = tokens.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void I2PControlSession::startExpireTokensJob()
|
||||
{
|
||||
expireTokensTimer.expires_from_now(boost::posix_time::seconds(I2P_CONTROL_TOKEN_LIFETIME));
|
||||
expireTokensTimer.async_wait(std::bind(
|
||||
&I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
204
client/i2pcontrol/I2PControl.h
Normal file
204
client/i2pcontrol/I2PControl.h
Normal file
|
@ -0,0 +1,204 @@
|
|||
#ifndef I2PCONTROL_H__
|
||||
#define I2PCONTROL_H__
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace i2p {
|
||||
namespace client {
|
||||
|
||||
const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie";
|
||||
const uint64_t I2P_CONTROL_TOKEN_LIFETIME = 600; // Token lifetime in seconds
|
||||
const std::size_t I2P_CONTROL_TOKEN_SIZE = 8; // Token size in bytes
|
||||
|
||||
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";
|
||||
|
||||
// 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";
|
||||
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";
|
||||
|
||||
/**
|
||||
* "Null" I2P control implementation, does not do actual networking.
|
||||
* @note authentication tokens are per-session
|
||||
* @note I2PControlSession must always be used as a std::shared_ptr
|
||||
* @warning an I2PControlSession must be destroyed before its io_service
|
||||
*/
|
||||
class I2PControlSession : public std::enable_shared_from_this<I2PControlSession> {
|
||||
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
None = 0,
|
||||
// JSON-RPC2
|
||||
MethodNotFound = 32601,
|
||||
InvalidParameters = 32602,
|
||||
InvalidRequest = 32600,
|
||||
InternalError = 32603,
|
||||
ParseError = 32700,
|
||||
// I2PControl specific
|
||||
InvalidPassword = 32001,
|
||||
NoToken = 32002,
|
||||
NonexistentToken = 32003,
|
||||
ExpiredToken = 32004,
|
||||
UnspecifiedVersion = 32005,
|
||||
UnsupportedVersion = 32006
|
||||
};
|
||||
|
||||
class Response {
|
||||
std::string id;
|
||||
std::string version;
|
||||
ErrorCode error;
|
||||
std::map<std::string, std::string> parameters;
|
||||
|
||||
public:
|
||||
Response(const std::string& version = "2.0");
|
||||
std::string toJsonString() const;
|
||||
|
||||
/**
|
||||
* Set an ouptut parameter to a specified string.
|
||||
* @todo escape quotes
|
||||
*/
|
||||
void setParam(const std::string& param, const std::string& value);
|
||||
void setParam(const std::string& param, int value);
|
||||
void setParam(const std::string& param, double value);
|
||||
|
||||
void setError(ErrorCode code);
|
||||
void setId(const std::string& identifier);
|
||||
|
||||
std::string getErrorMsg() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up the appropriate handlers.
|
||||
* @param pass the password required to authenticate (i.e. obtains a token)
|
||||
* @param ios the parent io_service object, must remain valid throughout
|
||||
* the lifetime of this I2PControlSession.
|
||||
*/
|
||||
I2PControlSession(boost::asio::io_service& ios,
|
||||
const std::string& pass = I2P_CONTROL_DEFAULT_PASSWORD);
|
||||
|
||||
/**
|
||||
* Starts the I2PControlSession.
|
||||
* In essence, this starts the expireTokensTimer.
|
||||
* @note should always be called after construction
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Cancels all operations that are waiting.
|
||||
* @note it's a good idea to call this before destruction (shared_ptr reset)
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Handle a json string with I2PControl instructions.
|
||||
*/
|
||||
Response handleRequest(std::stringstream& request);
|
||||
private:
|
||||
// For convenience
|
||||
typedef boost::property_tree::ptree PropertyTree;
|
||||
// Handler types
|
||||
typedef void (I2PControlSession::*MethodHandler)(
|
||||
const PropertyTree& pt, Response& results
|
||||
);
|
||||
typedef void (I2PControlSession::*RequestHandler)(Response& results);
|
||||
|
||||
/**
|
||||
* Tries to authenticate by checking whether the given token is valid.
|
||||
* Sets the appropriate error code in the given response.
|
||||
*/
|
||||
bool authenticate(const PropertyTree& pt, Response& response);
|
||||
|
||||
/**
|
||||
* Generate a random authentication token.
|
||||
* @return 8 random bytes as a hexadecimal string
|
||||
*/
|
||||
std::string generateToken() const;
|
||||
|
||||
void startExpireTokensJob();
|
||||
|
||||
/**
|
||||
* Expire tokens that are too old.
|
||||
*/
|
||||
void expireTokens(const boost::system::error_code& error);
|
||||
|
||||
// Method handlers
|
||||
void handleAuthenticate(const PropertyTree& pt, Response& response);
|
||||
void handleEcho(const PropertyTree& pt, Response& response);
|
||||
void handleI2PControl(const PropertyTree& pt, Response& response);
|
||||
void handleRouterInfo(const PropertyTree& pt, Response& response);
|
||||
void handleRouterManager(const PropertyTree& pt, Response& response);
|
||||
void handleNetworkSetting(const PropertyTree& pt, Response& response);
|
||||
|
||||
// RouterInfo handlers
|
||||
void handleUptime(Response& response);
|
||||
void handleVersion(Response& response);
|
||||
void handleStatus(Response& response);
|
||||
void handleNetDbKnownPeers(Response& response);
|
||||
void handleNetDbActivePeers(Response& response);
|
||||
void handleNetStatus(Response& response);
|
||||
void handleTunnelsParticipating(Response& response);
|
||||
void handleInBandwidth1S(Response& response);
|
||||
void handleOutBandwidth1S(Response& response);
|
||||
|
||||
// RouterManager handlers
|
||||
void handleShutdown(Response& response);
|
||||
void handleShutdownGraceful(Response& response);
|
||||
void handleReseed(Response& response);
|
||||
|
||||
std::string password;
|
||||
std::map<std::string, uint64_t> tokens;
|
||||
std::mutex tokensMutex;
|
||||
|
||||
std::map<std::string, MethodHandler> methodHandlers;
|
||||
std::map<std::string, RequestHandler> routerInfoHandlers;
|
||||
std::map<std::string, RequestHandler> routerManagerHandlers;
|
||||
std::map<std::string, RequestHandler> networkSettingHandlers;
|
||||
|
||||
boost::asio::io_service& service;
|
||||
boost::asio::deadline_timer shutdownTimer;
|
||||
boost::asio::deadline_timer expireTokensTimer;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // I2PCONTROL_H__
|
170
client/i2pcontrol/I2PControlServer.cpp
Normal file
170
client/i2pcontrol/I2PControlServer.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include "I2PControlServer.h"
|
||||
#include <sstream>
|
||||
#include <boost/date_time/local_time/local_time.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include "util/Log.h"
|
||||
#include "util/Timestamp.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace i2p {
|
||||
namespace client {
|
||||
|
||||
I2PControlService::I2PControlService(const std::string& address, int port, const std::string& pass)
|
||||
: m_Session(std::make_shared<I2PControlSession>(m_Service, pass)),
|
||||
m_IsRunning(false), m_Thread(nullptr),
|
||||
m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint(
|
||||
boost::asio::ip::address::from_string(address), port)
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
I2PControlService::~I2PControlService()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void I2PControlService::Start()
|
||||
{
|
||||
if(!m_IsRunning) {
|
||||
Accept();
|
||||
m_Session->start();
|
||||
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_Session->stop();
|
||||
// Release ownership before the io_service is stopped and destroyed
|
||||
m_Session.reset();
|
||||
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(const std::exception& ex) {
|
||||
LogPrint(eLogError, "I2PControl: ", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2PControlService::Accept()
|
||||
{
|
||||
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket>(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<boost::asio::ip::tcp::socket> 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);
|
||||
}
|
||||
else
|
||||
LogPrint(eLogError, "I2PControl accept error: ", ecode.message());
|
||||
}
|
||||
|
||||
void I2PControlService::ReadRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||
{
|
||||
auto request = std::make_shared<I2PControlBuffer>();
|
||||
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));
|
||||
}
|
||||
|
||||
void I2PControlService::HandleRequestReceived(const boost::system::error_code& ecode,
|
||||
size_t bytes_transferred, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<I2PControlBuffer> buf)
|
||||
{
|
||||
if(ecode) {
|
||||
LogPrint(eLogError, "I2PControl read error: ", ecode.message());
|
||||
return;
|
||||
}
|
||||
|
||||
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:
|
||||
}
|
||||
}
|
||||
|
||||
I2PControlSession::Response response = m_Session->handleRequest(ss);
|
||||
SendResponse(socket, buf, response.toJsonString(), isHtml);
|
||||
} catch(const std::exception& ex) {
|
||||
LogPrint(eLogError, "I2PControl handle request: ", ex.what());
|
||||
} catch(...) {
|
||||
LogPrint(eLogError, "I2PControl handle request unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
void I2PControlService::SendResponse(std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<I2PControlBuffer> buf, const std::string& response, bool isHtml)
|
||||
{
|
||||
size_t len = response.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<std::string>(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, response.c_str(), 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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void I2PControlService::HandleResponseSent(const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf)
|
||||
{
|
||||
if(ecode)
|
||||
LogPrint(eLogError, "I2PControl write error: ", ecode.message());
|
||||
socket->close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
56
client/i2pcontrol/I2PControlServer.h
Normal file
56
client/i2pcontrol/I2PControlServer.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef I2P_CONTROL_SERVER_H__
|
||||
#define I2P_CONTROL_SERVER_H__
|
||||
|
||||
#include "I2PControl.h"
|
||||
#include <inttypes.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace i2p {
|
||||
namespace client {
|
||||
|
||||
const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024;
|
||||
typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer;
|
||||
|
||||
class I2PControlService {
|
||||
public:
|
||||
|
||||
I2PControlService(const std::string& address, int port, const std::string& pass);
|
||||
~I2PControlService();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
|
||||
void Run();
|
||||
void Accept();
|
||||
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
void ReadRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
void HandleRequestReceived(const boost::system::error_code& ecode, size_t bytes_transferred,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf);
|
||||
void SendResponse(std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||
std::shared_ptr<I2PControlBuffer> buf, const std::string& response, bool isHtml);
|
||||
void HandleResponseSent(const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||
|
||||
std::shared_ptr<I2PControlSession> m_Session;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue