Browse Source

SSL support for server tunnels

pull/1794/head
orignal 2 years ago
parent
commit
e82662b389
  1. 2
      libi2pd_client/BOB.cpp
  2. 7
      libi2pd_client/ClientContext.cpp
  3. 4
      libi2pd_client/ClientContext.h
  4. 140
      libi2pd_client/I2PTunnel.cpp
  5. 33
      libi2pd_client/I2PTunnel.h

2
libi2pd_client/BOB.cpp

@ -156,7 +156,7 @@ namespace client
{ {
if (stream) if (stream)
{ {
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), m_Endpoint, m_IsQuiet); auto conn = std::make_shared<I2PTunnelConnection> (this, stream, m_Endpoint, m_IsQuiet);
AddHandler (conn); AddHandler (conn);
conn->Connect (); conn->Connect ();
} }

7
libi2pd_client/ClientContext.cpp

@ -726,7 +726,8 @@ namespace client
std::string address = section.second.get<std::string> (I2P_SERVER_TUNNEL_ADDRESS, ""); std::string address = section.second.get<std::string> (I2P_SERVER_TUNNEL_ADDRESS, "");
bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true);
bool ssl = section.second.get(I2P_SERVER_TUNNEL_SSL, false);
// I2CP // I2CP
std::map<std::string, std::string> options; std::map<std::string, std::string> options;
ReadI2CPOptions (section, true, options); ReadI2CPOptions (section, true, options);
@ -799,11 +800,13 @@ namespace client
if (!address.empty ()) if (!address.empty ())
serverTunnel->SetLocalAddress (address); serverTunnel->SetLocalAddress (address);
if(!isUniqueLocal) if (!isUniqueLocal)
{ {
LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); LogPrint(eLogInfo, "Clients: Disabling loopback address mapping");
serverTunnel->SetUniqueLocal(isUniqueLocal); serverTunnel->SetUniqueLocal(isUniqueLocal);
} }
if (ssl)
serverTunnel->SetSSL (true);
if (accessList.length () > 0) if (accessList.length () > 0)
{ {
std::set<i2p::data::IdentHash> idents; std::set<i2p::data::IdentHash> idents;

4
libi2pd_client/ClientContext.h

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2021, The PurpleI2P Project * Copyright (c) 2013-2022, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -61,7 +61,7 @@ namespace client
const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword";
const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address";
const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal";
const char I2P_SERVER_TUNNEL_SSL[] = "ssl";
class ClientContext class ClientContext
{ {

140
libi2pd_client/I2PTunnel.cpp

@ -21,7 +21,7 @@ namespace client
{ {
/** set standard socket options */ /** set standard socket options */
static void I2PTunnelSetSocketOptions(std::shared_ptr<boost::asio::ip::tcp::socket> socket) static void I2PTunnelSetSocketOptions (std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{ {
if (socket && socket->is_open()) if (socket && socket->is_open())
{ {
@ -46,10 +46,13 @@ namespace client
} }
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, 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): const boost::asio::ip::tcp::endpoint& target, bool quiet,
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), std::shared_ptr<boost::asio::ssl::context> sslCtx):
m_RemoteEndpoint (target), m_IsQuiet (quiet) I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet)
{ {
m_Socket = std::make_shared<boost::asio::ip::tcp::socket> (owner->GetService ());
if (sslCtx)
m_SSL = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > (*m_Socket, *sslCtx);
} }
I2PTunnelConnection::~I2PTunnelConnection () I2PTunnelConnection::~I2PTunnelConnection ()
@ -80,24 +83,26 @@ namespace client
} }
#ifdef __linux__ #ifdef __linux__
static void MapToLoopback(const std::shared_ptr<boost::asio::ip::tcp::socket> & sock, const i2p::data::IdentHash & addr) static void MapToLoopback(std::shared_ptr<boost::asio::ip::tcp::socket> sock, const i2p::data::IdentHash & addr)
{ {
// bind to 127.x.x.x address if (sock)
// where x.x.x are first three bytes from ident {
auto ourIP = GetLoopbackAddressFor(addr); // bind to 127.x.x.x address
boost::system::error_code ec; // where x.x.x are first three bytes from ident
sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); auto ourIP = GetLoopbackAddressFor(addr);
if (ec) boost::system::error_code ec;
LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec);
if (ec)
LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ());
}
} }
#endif #endif
void I2PTunnelConnection::Connect (bool isUniqueLocal) void I2PTunnelConnection::Connect (bool isUniqueLocal)
{ {
I2PTunnelSetSocketOptions(m_Socket);
if (m_Socket) if (m_Socket)
{ {
I2PTunnelSetSocketOptions (m_Socket);
#ifdef __linux__ #ifdef __linux__
if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () &&
m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127)
@ -127,10 +132,11 @@ namespace client
} }
Connect (false); Connect (false);
} }
void I2PTunnelConnection::Terminate () void I2PTunnelConnection::Terminate ()
{ {
if (Kill()) return; if (Kill()) return;
if (m_SSL) m_SSL = nullptr;
if (m_Stream) if (m_Stream)
{ {
m_Stream->Close (); m_Stream->Close ();
@ -145,12 +151,17 @@ namespace client
void I2PTunnelConnection::Receive () void I2PTunnelConnection::Receive ()
{ {
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), if (m_SSL)
std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::placeholders::_1, std::placeholders::_2)); std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
else
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
} }
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{ {
if (ecode) if (ecode)
{ {
@ -239,8 +250,12 @@ namespace client
void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) void I2PTunnelConnection::Write (const uint8_t * buf, size_t len)
{ {
boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), if (m_SSL)
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); boost::asio::async_write (*m_SSL, boost::asio::buffer (buf, len), boost::asio::transfer_all (),
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1));
else
boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (),
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1));
} }
void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode)
@ -253,22 +268,45 @@ namespace client
else else
{ {
LogPrint (eLogDebug, "I2PTunnel: Connected"); LogPrint (eLogDebug, "I2PTunnel: Connected");
if (m_IsQuiet) if (m_SSL)
StreamReceive (); m_SSL->async_handshake (boost::asio::ssl::stream_base::client,
else std::bind (&I2PTunnelConnection::HandleHandshake, shared_from_this (), std::placeholders::_1));
{ else
// send destination first like received from I2P Established ();
std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
dest += "\n";
if(sizeof(m_StreamBuffer) >= dest.size()) {
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
}
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive ();
} }
} }
void I2PTunnelConnection::HandleHandshake (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint (eLogError, "I2PTunnel: Handshake error: ", ecode.message ());
Terminate ();
}
else
{
LogPrint (eLogDebug, "I2PTunnel: SSL connected");
Established ();
}
}
void I2PTunnelConnection::Established ()
{
if (m_IsQuiet)
StreamReceive ();
else
{
// send destination first like received from I2P
std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
dest += "\n";
if(sizeof(m_StreamBuffer) >= dest.size()) {
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
}
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive ();
}
void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
{ {
if (m_HeaderSent) if (m_HeaderSent)
@ -332,11 +370,13 @@ namespace client
} }
I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (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,
const boost::asio::ip::tcp::endpoint& target, const std::string& host): std::shared_ptr<boost::asio::ssl::context> sslCtx):
I2PTunnelConnection (owner, stream, socket, target), m_Host (host), I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host),
m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ())
{ {
if (sslCtx)
SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ());
} }
void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
@ -474,9 +514,8 @@ namespace client
} }
I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, 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& webircpass): const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass):
I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), I2PTunnelConnection (owner, stream, target), m_From (stream->GetRemoteIdentity ()),
m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass)
{ {
} }
@ -487,7 +526,8 @@ namespace client
if (m_NeedsWebIrc) if (m_NeedsWebIrc)
{ {
m_NeedsWebIrc = false; m_NeedsWebIrc = false;
m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ())
<< " " << GetSocket ()->local_endpoint ().address () << std::endl;
} }
m_InPacket.clear (); m_InPacket.clear ();
@ -753,6 +793,17 @@ namespace client
LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress); LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress);
} }
void I2PServerTunnel::SetSSL (bool ssl)
{
if (ssl)
{
m_SSLCtx = std::make_shared<boost::asio::ssl::context> (boost::asio::ssl::context::sslv23);
m_SSLCtx->set_verify_mode(boost::asio::ssl::context::verify_none);
}
else
m_SSLCtx = nullptr;
}
void I2PServerTunnel::Accept () void I2PServerTunnel::Accept ()
{ {
if (m_PortDestination) if (m_PortDestination)
@ -793,7 +844,7 @@ namespace client
std::shared_ptr<I2PTunnelConnection> I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream) std::shared_ptr<I2PTunnelConnection> I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{ {
return std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint ()); return std::make_shared<I2PTunnelConnection> (this, stream, GetEndpoint (), true, m_SSLCtx);
} }
@ -807,8 +858,7 @@ namespace client
std::shared_ptr<I2PTunnelConnection> I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream) std::shared_ptr<I2PTunnelConnection> I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{ {
return std::make_shared<I2PServerTunnelConnectionHTTP> (this, stream, return std::make_shared<I2PServerTunnelConnectionHTTP> (this, stream, GetEndpoint (), m_Host, GetSSLCtx ());
std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), m_Host);
} }
I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address,
@ -821,7 +871,7 @@ namespace client
std::shared_ptr<I2PTunnelConnection> I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream) std::shared_ptr<I2PTunnelConnection> I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{ {
return std::make_shared<I2PTunnelConnectionIRC> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), this->m_WebircPass); return std::make_shared<I2PTunnelConnectionIRC> (this, stream, GetEndpoint (), m_WebircPass);
} }
void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)

33
libi2pd_client/I2PTunnel.h

@ -16,6 +16,7 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include "Identity.h" #include "Identity.h"
#include "Destination.h" #include "Destination.h"
#include "Datagram.h" #include "Datagram.h"
@ -44,8 +45,9 @@ namespace client
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port = 0); // to I2P 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, I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API 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, I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P const boost::asio::ip::tcp::endpoint& target, bool quiet = true,
std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr); // from I2P
~I2PTunnelConnection (); ~I2PTunnelConnection ();
void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
void Connect (bool isUniqueLocal = true); void Connect (bool isUniqueLocal = true);
@ -56,21 +58,27 @@ namespace client
void Terminate (); void Terminate ();
void Receive (); void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void StreamReceive ();
virtual void Write (const uint8_t * buf, size_t len); // can be overloaded virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
void HandleWrite (const boost::system::error_code& ecode);
virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded
void StreamReceive (); std::shared_ptr<boost::asio::ip::tcp::socket> GetSocket () const { return m_Socket; };
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > GetSSL () const { return m_SSL; };
void HandleConnect (const boost::system::error_code& ecode);
std::shared_ptr<const boost::asio::ip::tcp::socket> GetSocket () const { return m_Socket; }; private:
void HandleConnect (const boost::system::error_code& ecode);
void HandleHandshake (const boost::system::error_code& ecode);
void Established ();
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleWrite (const boost::system::error_code& ecode);
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
private: private:
uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; 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<boost::asio::ip::tcp::socket> m_Socket;
std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> > m_SSL;
std::shared_ptr<i2p::stream::Stream> m_Stream; std::shared_ptr<i2p::stream::Stream> m_Stream;
boost::asio::ip::tcp::endpoint m_RemoteEndpoint; boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
bool m_IsQuiet; // don't send destination bool m_IsQuiet; // don't send destination
@ -100,8 +108,8 @@ namespace client
public: public:
I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, I2PServerTunnelConnectionHTTP (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,
const boost::asio::ip::tcp::endpoint& target, const std::string& host); std::shared_ptr<boost::asio::ssl::context> sslCtx = nullptr);
protected: protected:
@ -121,7 +129,6 @@ namespace client
public: public:
I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, 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& m_WebircPass); const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass);
protected: protected:
@ -343,6 +350,9 @@ namespace client
void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; }
bool IsUniqueLocal () const { return m_IsUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; }
void SetSSL (bool ssl);
std::shared_ptr<boost::asio::ssl::context> GetSSLCtx () const { return m_SSLCtx; };
void SetLocalAddress (const std::string& localAddress); void SetLocalAddress (const std::string& localAddress);
const std::string& GetAddress() const { return m_Address; } const std::string& GetAddress() const { return m_Address; }
@ -371,6 +381,7 @@ namespace client
std::set<i2p::data::IdentHash> m_AccessList; std::set<i2p::data::IdentHash> m_AccessList;
bool m_IsAccessList; bool m_IsAccessList;
std::unique_ptr<boost::asio::ip::address> m_LocalAddress; std::unique_ptr<boost::asio::ip::address> m_LocalAddress;
std::shared_ptr<boost::asio::ssl::context> m_SSLCtx;
}; };
class I2PServerTunnelHTTP: public I2PServerTunnel class I2PServerTunnelHTTP: public I2PServerTunnel

Loading…
Cancel
Save