Merge pull request #399 from PurpleI2P/openssl

irc tunnel and gzip
This commit is contained in:
orignal 2016-03-01 13:39:26 -05:00
commit 4c124284b6
12 changed files with 181 additions and 50 deletions

View File

@ -303,7 +303,7 @@ namespace client
LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists");
numClientTunnels++; numClientTunnels++;
} }
else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP) else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC)
{ {
// mandatory params // mandatory params
std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST); std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST);
@ -313,6 +313,7 @@ namespace client
int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0);
std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, "");
std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, "");
bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true);
i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
// I2CP // I2CP
std::map<std::string, std::string> options; std::map<std::string, std::string> options;
@ -324,9 +325,15 @@ namespace client
localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
if (!localDestination) if (!localDestination)
localDestination = CreateNewLocalDestination (k, true, &options); localDestination = CreateNewLocalDestination (k, true, &options);
I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ?
new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort) : I2PServerTunnel * serverTunnel;
new I2PServerTunnel (name, host, port, localDestination, inPort); if (type == I2P_TUNNELS_SECTION_TYPE_HTTP)
serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip);
else if (type == I2P_TUNNELS_SECTION_TYPE_IRC)
serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, inPort, gzip);
else // regular server tunnel by default
serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip);
if (accessList.length () > 0) if (accessList.length () > 0)
{ {
std::set<i2p::data::IdentHash> idents; std::set<i2p::data::IdentHash> idents;

View File

@ -21,6 +21,7 @@ namespace client
const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client";
const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server";
const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http";
const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc";
const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_PORT[] = "port";
const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address";
const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination";
@ -34,6 +35,7 @@ namespace client
const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype";
const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport";
const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist";
const char I2P_SERVER_TUNNEL_GZIP[] = "gzip";
class ClientContext class ClientContext
{ {

View File

@ -576,9 +576,9 @@ namespace client
return false; return false;
} }
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port) std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port, bool gzip)
{ {
auto dest = std::make_shared<i2p::stream::StreamingDestination> (shared_from_this (), port); auto dest = std::make_shared<i2p::stream::StreamingDestination> (shared_from_this (), port, gzip);
if (port) if (port)
m_StreamingDestinationsByPorts[port] = dest; m_StreamingDestinationsByPorts[port] = dest;
else // update default else // update default

View File

@ -82,7 +82,7 @@ namespace client
void CancelDestinationRequest (const i2p::data::IdentHash& dest); void CancelDestinationRequest (const i2p::data::IdentHash& dest);
// streaming // streaming
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port); // additional std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const; std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
// following methods operate with default streaming destination // following methods operate with default streaming destination
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);

View File

@ -234,6 +234,45 @@ namespace client
} }
} }
I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (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_From (stream->GetRemoteIdentity ())
{
}
void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len)
{
std::string line;
m_OutPacket.str ("");
m_InPacket.clear ();
m_InPacket.write ((const char *)buf, len);
while (!m_InPacket.eof () && !m_InPacket.fail ())
{
std::getline (m_InPacket, line);
if (line.length () == 0 && m_InPacket.eof ()) {
m_InPacket.str ("");
}
auto pos = line.find ("USER");
if (pos != std::string::npos && pos == 0)
{
pos = line.find (" ");
pos++;
pos = line.find (" ", pos);
pos++;
auto nextpos = line.find (" ", pos);
m_OutPacket << line.substr (0, pos);
m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ());
m_OutPacket << line.substr (nextpos) << '\n';
} else {
m_OutPacket << line << '\n';
}
}
I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ());
}
/* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ /* 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> class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this<I2PClientTunnelHandler>
{ {
@ -332,10 +371,10 @@ namespace client
} }
I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination, int inport): int port, std::shared_ptr<ClientDestination> localDestination, int inport, bool gzip):
I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false)
{ {
m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip);
} }
void I2PServerTunnel::Start () void I2PServerTunnel::Start ()
@ -423,8 +462,8 @@ namespace client
I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination, int port, std::shared_ptr<ClientDestination> localDestination,
const std::string& host, int inport): const std::string& host, int inport, bool gzip):
I2PServerTunnel (name, address, port, localDestination, inport), I2PServerTunnel (name, address, port, localDestination, inport, gzip),
m_Host (host.length () > 0 ? host : address) m_Host (host.length () > 0 ? host : address)
{ {
} }
@ -436,5 +475,18 @@ namespace client
AddHandler (conn); AddHandler (conn);
conn->Connect (); conn->Connect ();
} }
I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination, int inport, bool gzip):
I2PServerTunnel (name, address, port, localDestination, inport, gzip)
{
}
void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnectionIRC> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), GetAddress ());
AddHandler (conn);
conn->Connect ();
}
} }
} }

View File

@ -80,6 +80,26 @@ namespace client
std::shared_ptr<const i2p::data::IdentityEx> m_From; std::shared_ptr<const i2p::data::IdentityEx> m_From;
}; };
class I2PTunnelConnectionIRC: public I2PTunnelConnection
{
public:
I2PTunnelConnectionIRC (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::shared_ptr<const i2p::data::IdentityEx> m_From;
std::stringstream m_OutPacket, m_InPacket;
};
class I2PClientTunnel: public TCPIPAcceptor class I2PClientTunnel: public TCPIPAcceptor
{ {
protected: protected:
@ -114,7 +134,7 @@ namespace client
public: public:
I2PServerTunnel (const std::string& name, const std::string& address, int port, I2PServerTunnel (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport = 0); std::shared_ptr<ClientDestination> localDestination, int inport = 0, bool gzip = true);
void Start (); void Start ();
void Stop (); void Stop ();
@ -153,7 +173,7 @@ namespace client
I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, const std::string& host, std::shared_ptr<ClientDestination> localDestination, const std::string& host,
int inport = 0); int inport = 0, bool gzip = true);
private: private:
@ -163,6 +183,19 @@ namespace client
std::string m_Host; std::string m_Host;
}; };
class I2PServerTunnelIRC: public I2PServerTunnel
{
public:
I2PServerTunnelIRC (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport = 0, bool gzip = true);
private:
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
};
} }
} }

View File

@ -17,7 +17,7 @@ namespace tunnel
namespace data namespace data
{ {
const int LEASE_ENDDATE_THRESHOLD = 31000; // in milliseconds const int LEASE_ENDDATE_THRESHOLD = 51000; // in milliseconds
struct Lease struct Lease
{ {
IdentHash tunnelGateway; IdentHash tunnelGateway;

View File

@ -44,10 +44,12 @@ void Log::Flush ()
m_LogStream->flush(); m_LogStream->flush();
} }
void Log::SetLogFile (const std::string& fullFilePath) void Log::SetLogFile (const std::string& fullFilePath, bool truncate)
{ {
m_FullFilePath = fullFilePath; m_FullFilePath = fullFilePath;
auto logFile = std::make_shared<std::ofstream> (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); auto mode = std::ofstream::out | std::ofstream::binary;
mode |= truncate ? std::ofstream::trunc : std::ofstream::app;
auto logFile = std::make_shared<std::ofstream> (fullFilePath, mode);
if (logFile->is_open ()) if (logFile->is_open ())
{ {
SetLogStream (logFile); SetLogStream (logFile);
@ -59,7 +61,7 @@ void Log::ReopenLogFile ()
{ {
if (m_FullFilePath.length () > 0) if (m_FullFilePath.length () > 0)
{ {
SetLogFile (m_FullFilePath); SetLogFile (m_FullFilePath, false); // don't truncate
LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen"); LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen");
} }
} }

2
Log.h
View File

@ -38,7 +38,7 @@ class Log: public i2p::util::MsgQueue<LogMsg>
Log () { SetOnEmpty (std::bind (&Log::Flush, this)); }; Log () { SetOnEmpty (std::bind (&Log::Flush, this)); };
~Log () {}; ~Log () {};
void SetLogFile (const std::string& fullFilePath); void SetLogFile (const std::string& fullFilePath, bool truncate = true);
void ReopenLogFile (); void ReopenLogFile ();
void SetLogLevel (const std::string& level); void SetLogLevel (const std::string& level);
void SetLogStream (std::shared_ptr<std::ostream> logStream); void SetLogStream (std::shared_ptr<std::ostream> logStream);

View File

@ -628,7 +628,7 @@ namespace stream
std::vector<i2p::tunnel::TunnelMessageBlock> msgs; std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
for (auto it: packets) for (auto it: packets)
{ {
auto msg = m_RoutingSession->WrapSingleMessage (CreateDataMessage (it->GetBuffer (), it->GetLength ())); auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port));
msgs.push_back (i2p::tunnel::TunnelMessageBlock msgs.push_back (i2p::tunnel::TunnelMessageBlock
{ {
i2p::tunnel::eDeliveryTypeTunnel, i2p::tunnel::eDeliveryTypeTunnel,
@ -779,33 +779,9 @@ namespace stream
m_CurrentRemoteLease = nullptr; m_CurrentRemoteLease = nullptr;
} }
std::shared_ptr<I2NPMessage> Stream::CreateDataMessage (const uint8_t * payload, size_t len) StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
{ m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
auto msg = NewI2NPShortMessage (); m_PendingIncomingTimer (m_Owner->GetService ())
if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)
m_LocalDestination.m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION);
else
m_LocalDestination.m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION);
uint8_t * buf = msg->GetPayload ();
buf += 4; // reserve for lengthlength
msg->len += 4;
size_t size = m_LocalDestination.m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
if (size)
{
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port
htobe16buf (buf + 6, m_Port); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol
msg->len += size;
msg->FillI2NPMessageHeader (eI2NPData);
}
else
msg = nullptr;
return msg;
}
StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort):
m_Owner (owner), m_LocalPort (localPort), m_PendingIncomingTimer (m_Owner->GetService ())
{ {
} }
@ -993,5 +969,30 @@ namespace stream
else else
delete uncompressed; delete uncompressed;
} }
std::shared_ptr<I2NPMessage> StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort)
{
auto msg = NewI2NPShortMessage ();
if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)
m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION);
else
m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION);
uint8_t * buf = msg->GetPayload ();
buf += 4; // reserve for lengthlength
msg->len += 4;
size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
if (size)
{
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, m_LocalPort); // source port
htobe16buf (buf + 6, toPort); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol
msg->len += size;
msg->FillI2NPMessageHeader (eI2NPData);
}
else
msg = nullptr;
return msg;
}
} }
} }

View File

@ -159,8 +159,6 @@ namespace stream
void HandleResendTimer (const boost::system::error_code& ecode); void HandleResendTimer (const boost::system::error_code& ecode);
void HandleAckSendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len);
private: private:
boost::asio::io_service& m_Service; boost::asio::io_service& m_Service;
@ -195,7 +193,7 @@ namespace stream
typedef std::function<void (std::shared_ptr<Stream>)> Acceptor; typedef std::function<void (std::shared_ptr<Stream>)> Acceptor;
StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort = 0); StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort = 0, bool gzip = true);
~StreamingDestination (); ~StreamingDestination ();
void Start (); void Start ();
@ -210,6 +208,7 @@ namespace stream
uint16_t GetLocalPort () const { return m_LocalPort; }; uint16_t GetLocalPort () const { return m_LocalPort; };
void HandleDataMessagePayload (const uint8_t * buf, size_t len); void HandleDataMessagePayload (const uint8_t * buf, size_t len);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort);
private: private:
@ -221,6 +220,7 @@ namespace stream
std::shared_ptr<i2p::client::ClientDestination> m_Owner; std::shared_ptr<i2p::client::ClientDestination> m_Owner;
uint16_t m_LocalPort; uint16_t m_LocalPort;
bool m_Gzip; // gzip compression of data messages
std::mutex m_StreamsMutex; std::mutex m_StreamsMutex;
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream
Acceptor m_Acceptor; Acceptor m_Acceptor;

View File

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
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
fNr8P7NIRyZ8
-----END CERTIFICATE-----