Implement #243, separate core/client (PCH support dropped for now)

This commit is contained in:
EinMByte 2015-08-21 22:22:34 +02:00
parent bdaf2c16aa
commit 8ac9520dfd
153 changed files with 360 additions and 20020 deletions

582
core/AddressBook.cpp Normal file
View file

@ -0,0 +1,582 @@
#include <string.h>
#include <inttypes.h>
#include <string>
#include <map>
#include <fstream>
#include <chrono>
#include <condition_variable>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <cryptopp/osrng.h>
#include "util/base64.h"
#include "util/util.h"
#include "Identity.h"
#include "util/Log.h"
#include "NetDb.h"
#include "AddressBook.h"
#include "Destination.h"
namespace i2p
{
namespace client
{
class AddressBookFilesystemStorage: public AddressBookStorage
{
public:
AddressBookFilesystemStorage ();
bool GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const;
void AddAddress (const i2p::data::IdentityEx& address);
void RemoveAddress (const i2p::data::IdentHash& ident);
int Load (std::map<std::string, i2p::data::IdentHash>& addresses);
int Save (const std::map<std::string, i2p::data::IdentHash>& addresses);
private:
boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / "addressbook"; };
};
AddressBookFilesystemStorage::AddressBookFilesystemStorage ()
{
auto path = GetPath ();
if (!boost::filesystem::exists (path))
{
// Create directory is necessary
if (!boost::filesystem::create_directory (path))
LogPrint (eLogError, "Failed to create addressbook directory");
}
}
bool AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const
{
auto filename = GetPath () / (ident.ToBase32() + ".b32");
std::ifstream f(filename.c_str (), std::ifstream::binary);
if (f.is_open ())
{
f.seekg (0,std::ios::end);
size_t len = f.tellg ();
if (len < i2p::data::DEFAULT_IDENTITY_SIZE)
{
LogPrint (eLogError, "File ", filename, " is too short. ", len);
return false;
}
f.seekg(0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
f.read((char *)buf, len);
address.FromBuffer (buf, len);
delete[] buf;
return true;
}
else
return false;
}
void AddressBookFilesystemStorage::AddAddress (const i2p::data::IdentityEx& address)
{
auto filename = GetPath () / (address.GetIdentHash ().ToBase32() + ".b32");
std::ofstream f (filename.c_str (), std::ofstream::binary | std::ofstream::out);
if (f.is_open ())
{
size_t len = address.GetFullLen ();
uint8_t * buf = new uint8_t[len];
address.ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
}
else
LogPrint (eLogError, "Can't open file ", filename);
}
void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident)
{
auto filename = GetPath () / (ident.ToBase32() + ".b32");
if (boost::filesystem::exists (filename))
boost::filesystem::remove (filename);
}
int AddressBookFilesystemStorage::Load (std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = 0;
auto filename = GetPath () / "addresses.csv";
std::ifstream f (filename.c_str (), std::ofstream::in); // in text mode
if (f.is_open ())
{
addresses.clear ();
while (!f.eof ())
{
std::string s;
getline(f, s);
if (!s.length())
continue; // skip empty line
size_t pos = s.find(',');
if (pos != std::string::npos)
{
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
i2p::data::IdentHash ident;
ident.FromBase32 (addr);
addresses[name] = ident;
num++;
}
}
LogPrint (eLogInfo, num, " addresses loaded");
}
else
LogPrint (eLogWarning, filename, " not found");
return num;
}
int AddressBookFilesystemStorage::Save (const std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = 0;
auto filename = GetPath () / "addresses.csv";
std::ofstream f (filename.c_str (), std::ofstream::out); // in text mode
if (f.is_open ())
{
for (auto it: addresses)
{
f << it.first << "," << it.second.ToBase32 () << std::endl;
num++;
}
LogPrint (eLogInfo, num, " addresses saved");
}
else
LogPrint (eLogError, "Can't open file ", filename);
return num;
}
//---------------------------------------------------------------------
AddressBook::AddressBook ()
: m_Storage (nullptr), m_IsLoaded (false), m_IsDownloading (false),
m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr),
m_SharedLocalDestination(nullptr)
{
}
AddressBook::~AddressBook ()
{
Stop ();
}
void AddressBook::Start (ClientDestination* local_destination)
{
m_SharedLocalDestination = local_destination;
StartSubscriptions ();
}
void AddressBook::Stop ()
{
StopSubscriptions ();
if (m_SubscriptionsUpdateTimer)
{
delete m_SubscriptionsUpdateTimer;
m_SubscriptionsUpdateTimer = nullptr;
}
if (m_IsDownloading)
{
LogPrint (eLogInfo, "Subscription is downloading. Waiting for temination...");
for (int i = 0; i < 30; i++)
{
if (!m_IsDownloading)
{
LogPrint (eLogInfo, "Subscription download complete");
break;
}
std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds
}
LogPrint (eLogError, "Subscription download hangs");
m_IsDownloading = false;
}
if (m_Storage)
{
m_Storage->Save (m_Addresses);
delete m_Storage;
m_Storage = nullptr;
}
if (m_DefaultSubscription)
{
delete m_DefaultSubscription;
m_DefaultSubscription = nullptr;
}
for (auto it: m_Subscriptions)
delete it;
m_Subscriptions.clear ();
}
AddressBookStorage * AddressBook::CreateStorage ()
{
return new AddressBookFilesystemStorage ();
}
bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident)
{
auto pos = address.find(".b32.i2p");
if (pos != std::string::npos)
{
i2p::util::Base32ToByteStream (address.c_str(), pos, ident, 32);
return true;
}
else
{
pos = address.find (".i2p");
if (pos != std::string::npos)
{
auto identHash = FindAddress (address);
if (identHash)
{
ident = *identHash;
return true;
}
else
return false;
}
}
// if not .b32 we assume full base64 address
i2p::data::IdentityEx dest;
if (!dest.FromBase64 (address))
return false;
ident = dest.GetIdentHash ();
return true;
}
const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address)
{
if (!m_IsLoaded)
LoadHosts ();
if (m_IsLoaded)
{
auto it = m_Addresses.find (address);
if (it != m_Addresses.end ())
return &it->second;
}
return nullptr;
}
ClientDestination* AddressBook::getSharedLocalDestination() const
{
return m_SharedLocalDestination;
}
void AddressBook::InsertAddress (const std::string& address, const std::string& base64)
{
i2p::data::IdentityEx ident;
ident.FromBase64 (base64);
if (!m_Storage)
m_Storage = CreateStorage ();
m_Storage->AddAddress (ident);
m_Addresses[address] = ident.GetIdentHash ();
LogPrint (address,"->", ToAddress(ident.GetIdentHash ()), " added");
}
void AddressBook::InsertAddress (const i2p::data::IdentityEx& address)
{
if (!m_Storage)
m_Storage = CreateStorage ();
m_Storage->AddAddress (address);
}
bool AddressBook::GetAddress (const std::string& address, i2p::data::IdentityEx& identity)
{
if (!m_Storage)
m_Storage = CreateStorage ();
i2p::data::IdentHash ident;
if (!GetIdentHash (address, ident)) return false;
return m_Storage->GetAddress (ident, identity);
}
void AddressBook::LoadHosts ()
{
if (!m_Storage)
m_Storage = CreateStorage ();
if (m_Storage->Load (m_Addresses) > 0)
{
m_IsLoaded = true;
return;
}
// try hosts.txt first
std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ofstream::in); // in text mode
if (f.is_open ())
{
LoadHostsFromStream (f);
m_IsLoaded = true;
}
else
{
// if not found download it from http://i2p-projekt.i2p/hosts.txt
LogPrint (eLogInfo, "hosts.txt not found. Try to download it from default subscription...");
if (!m_IsDownloading)
{
m_IsDownloading = true;
if (!m_DefaultSubscription)
m_DefaultSubscription = new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS);
m_DefaultSubscription->CheckSubscription ();
}
}
}
void AddressBook::LoadHostsFromStream (std::istream& f)
{
std::unique_lock<std::mutex> l(m_AddressBookMutex);
int numAddresses = 0;
std::string s;
while (!f.eof ())
{
getline(f, s);
if (!s.length())
continue; // skip empty line
size_t pos = s.find('=');
if (pos != std::string::npos)
{
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
i2p::data::IdentityEx ident;
if (ident.FromBase64(addr))
{
m_Addresses[name] = ident.GetIdentHash ();
m_Storage->AddAddress (ident);
numAddresses++;
}
else
LogPrint (eLogError, "Malformed address ", addr, " for ", name);
}
}
LogPrint (eLogInfo, numAddresses, " addresses processed");
if (numAddresses > 0)
{
m_IsLoaded = true;
m_Storage->Save (m_Addresses);
}
}
void AddressBook::LoadSubscriptions ()
{
if (!m_Subscriptions.size ())
{
std::ifstream f (i2p::util::filesystem::GetFullPath ("subscriptions.txt").c_str (), std::ofstream::in); // in text mode
if (f.is_open ())
{
std::string s;
while (!f.eof ())
{
getline(f, s);
if (!s.length()) continue; // skip empty line
m_Subscriptions.push_back (new AddressBookSubscription (*this, s));
}
LogPrint (eLogInfo, m_Subscriptions.size (), " subscriptions loaded");
}
else
LogPrint (eLogWarning, "subscriptions.txt not found");
}
else
LogPrint (eLogError, "Subscriptions already loaded");
}
void AddressBook::DownloadComplete (bool success)
{
m_IsDownloading = false;
if (m_SubscriptionsUpdateTimer)
{
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(
success ? CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT : CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT));
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1));
}
}
void AddressBook::StartSubscriptions ()
{
LoadSubscriptions ();
if (!m_Subscriptions.size ()) return;
if (m_SharedLocalDestination)
{
m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer(
m_SharedLocalDestination->GetService()
);
m_SubscriptionsUpdateTimer->expires_from_now(
boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)
);
m_SubscriptionsUpdateTimer->async_wait(std::bind(
&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1
));
}
else
LogPrint (eLogError, "Can't start subscriptions: missing shared local destination");
}
void AddressBook::StopSubscriptions ()
{
if (m_SubscriptionsUpdateTimer)
m_SubscriptionsUpdateTimer->cancel ();
}
void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (!m_SharedLocalDestination)
return; // TODO: error handling
if (m_IsLoaded && !m_IsDownloading && m_SharedLocalDestination->IsReady ())
{
// pick random subscription
CryptoPP::AutoSeededRandomPool rnd;
auto ind = rnd.GenerateWord32 (0, m_Subscriptions.size() - 1);
m_IsDownloading = true;
m_Subscriptions[ind]->CheckSubscription ();
}
else
{
if (!m_IsLoaded)
LoadHosts ();
// try it again later
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT));
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1));
}
}
}
AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link):
m_Book (book), m_Link (link)
{
}
void AddressBookSubscription::CheckSubscription ()
{
std::thread load_hosts(&AddressBookSubscription::Request, this);
load_hosts.detach(); // TODO: use join
}
void AddressBookSubscription::Request ()
{
// must be run in separate thread
LogPrint (eLogInfo, "Downloading hosts from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
bool success = false;
i2p::util::http::url u (m_Link);
i2p::data::IdentHash ident;
if (m_Book.GetIdentHash (u.host_, ident))
{
std::condition_variable newDataReceived;
std::mutex newDataReceivedMutex;
auto leaseSet = m_Book.getSharedLocalDestination()->FindLeaseSet (ident);
if (!leaseSet)
{
std::unique_lock<std::mutex> l(newDataReceivedMutex);
m_Book.getSharedLocalDestination()->RequestDestination (ident,
[&newDataReceived, &leaseSet](std::shared_ptr<i2p::data::LeaseSet> ls)
{
leaseSet = ls;
newDataReceived.notify_all ();
});
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
LogPrint (eLogError, "Subscription LeseseSet request timeout expired");
}
if (leaseSet)
{
std::stringstream request, response;
// standard header
request << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n";
if (m_Etag.length () > 0) // etag
request << i2p::util::http::IF_NONE_MATCH << ": \"" << m_Etag << "\"\r\n";
if (m_LastModified.length () > 0) // if-modfief-since
request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n";
request << "\r\n"; // end of header
auto stream = m_Book.getSharedLocalDestination()->CreateStream (leaseSet, u.port_);
stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ());
uint8_t buf[4096];
bool end = false;
while (!end)
{
stream->AsyncReceive (boost::asio::buffer (buf, 4096),
[&](const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (bytes_transferred)
response.write ((char *)buf, bytes_transferred);
if (ecode == boost::asio::error::timed_out || !stream->IsOpen ())
end = true;
newDataReceived.notify_all ();
},
30); // wait for 30 seconds
std::unique_lock<std::mutex> l(newDataReceivedMutex);
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
LogPrint (eLogError, "Subscription timeout expired");
}
// process remaining buffer
while (size_t len = stream->ReadSome (buf, 4096))
response.write ((char *)buf, len);
// parse response
std::string version;
response >> version; // HTTP version
int status = 0;
response >> status; // status
if (status == 200) // OK
{
bool isChunked = false;
std::string header, statusMessage;
std::getline (response, statusMessage);
// read until new line meaning end of header
while (!response.eof () && header != "\r")
{
std::getline (response, header);
auto colon = header.find (':');
if (colon != std::string::npos)
{
std::string field = header.substr (0, colon);
header.resize (header.length () - 1); // delete \r
if (field == i2p::util::http::ETAG)
m_Etag = header.substr (colon + 1);
else if (field == i2p::util::http::LAST_MODIFIED)
m_LastModified = header.substr (colon + 1);
else if (field == i2p::util::http::TRANSFER_ENCODING)
isChunked = !header.compare (colon + 1, std::string::npos, "chunked");
}
}
LogPrint (eLogInfo, m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
if (!response.eof ())
{
success = true;
if (!isChunked)
m_Book.LoadHostsFromStream (response);
else
{
// merge chunks
std::stringstream merged;
i2p::util::http::MergeChunkedResponse (response, merged);
m_Book.LoadHostsFromStream (merged);
}
}
}
else if (status == 304)
{
success = true;
LogPrint (eLogInfo, "No updates from ", m_Link);
}
else
LogPrint (eLogWarning, "Adressbook HTTP response ", status);
}
else
LogPrint (eLogError, "Address ", u.host_, " not found");
}
else
LogPrint (eLogError, "Can't resolve ", u.host_);
LogPrint (eLogInfo, "Download complete ", success ? "Success" : "Failed");
m_Book.DownloadComplete (success);
}
}
}

110
core/AddressBook.h Normal file
View file

@ -0,0 +1,110 @@
#ifndef ADDRESS_BOOK_H__
#define ADDRESS_BOOK_H__
#include <string.h>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <mutex>
#include <boost/asio.hpp>
#include "util/base64.h"
#include "util/util.h"
#include "Identity.h"
#include "util/Log.h"
namespace i2p
{
namespace client
{
const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p/hosts.txt";
const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes
const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes
const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)
const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes
const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second
inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); }
class AddressBookStorage // interface for storage
{
public:
virtual ~AddressBookStorage () {};
virtual bool GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const = 0;
virtual void AddAddress (const i2p::data::IdentityEx& address) = 0;
virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0;
virtual int Load (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
virtual int Save (const std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
};
class ClientDestination;
class AddressBookSubscription;
class AddressBook
{
public:
AddressBook ();
~AddressBook ();
void Start (ClientDestination* local_destination);
void Stop ();
bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident);
bool GetAddress (const std::string& address, i2p::data::IdentityEx& identity);
const i2p::data::IdentHash * FindAddress (const std::string& address);
ClientDestination* getSharedLocalDestination() const;
void InsertAddress (const std::string& address, const std::string& base64); // for jump service
void InsertAddress (const i2p::data::IdentityEx& address);
void LoadHostsFromStream (std::istream& f);
void DownloadComplete (bool success);
//This method returns the ".b32.i2p" address
std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); }
std::string ToAddress(const i2p::data::IdentityEx& ident) { return ToAddress(ident.GetIdentHash ()); }
private:
void StartSubscriptions ();
void StopSubscriptions ();
AddressBookStorage * CreateStorage ();
void LoadHosts ();
void LoadSubscriptions ();
void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode);
private:
std::mutex m_AddressBookMutex;
std::map<std::string, i2p::data::IdentHash> m_Addresses;
AddressBookStorage * m_Storage;
volatile bool m_IsLoaded, m_IsDownloading;
std::vector<AddressBookSubscription *> m_Subscriptions;
AddressBookSubscription * m_DefaultSubscription; // in case if we don't know any addresses yet
boost::asio::deadline_timer * m_SubscriptionsUpdateTimer;
ClientDestination* m_SharedLocalDestination;
};
class AddressBookSubscription
{
public:
AddressBookSubscription (AddressBook& book, const std::string& link);
void CheckSubscription ();
private:
void Request ();
private:
AddressBook& m_Book;
std::string m_Link, m_Etag, m_LastModified;
};
}
}
#endif

56
core/CMakeLists.txt Normal file
View file

@ -0,0 +1,56 @@
set(CORE_SRC
"transport/NTCPSession.cpp"
"transport/SSU.cpp"
"transport/SSUData.cpp"
"transport/SSUSession.cpp"
"transport/Transports.cpp"
"crypto/CryptoConst.cpp"
"crypto/aes.cpp"
"crypto/Signature.cpp"
"util/base64.cpp"
"util/util.cpp"
"util/Log.cpp"
"tunnel/TransitTunnel.cpp"
"tunnel/Tunnel.cpp"
"tunnel/TunnelGateway.cpp"
"tunnel/TunnelEndpoint.cpp"
"tunnel/TunnelPool.cpp"
"tunnel/TunnelCrypto.cpp"
"AddressBook.cpp"
"Garlic.cpp"
"I2NPProtocol.cpp"
"Identity.cpp"
"LeaseSet.cpp"
"NetDbRequests.cpp"
"NetDb.cpp"
"Profiling.cpp"
"RouterContext.cpp"
"RouterInfo.cpp"
"Streaming.cpp"
"Destination.cpp"
"Datagram.cpp"
"UPnP.cpp"
"Reseed.cpp"
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
list(APPEND CORE_SRC "util/I2PEndian.cpp")
endif()
include_directories(".")
# Library building
if(WITH_LIBRARY)
add_library(${CORE_NAME} ${CORE_SRC})
target_link_libraries(
${CORE_NAME} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}
)
install(
TARGETS ${CORE_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
endif()

151
core/Datagram.cpp Normal file
View file

@ -0,0 +1,151 @@
#include <string.h>
#include <vector>
#include <cryptopp/sha.h>
#include <cryptopp/gzip.h>
#include "util/Log.h"
#include "tunnel/TunnelBase.h"
#include "RouterContext.h"
#include "Destination.h"
#include "Datagram.h"
namespace i2p
{
namespace datagram
{
DatagramDestination::DatagramDestination (i2p::client::ClientDestination& owner):
m_Owner (owner), m_Receiver (nullptr)
{
}
void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort)
{
uint8_t buf[MAX_DATAGRAM_SIZE];
auto identityLen = m_Owner.GetIdentity ().ToBuffer (buf, MAX_DATAGRAM_SIZE);
uint8_t * signature = buf + identityLen;
auto signatureLen = m_Owner.GetIdentity ().GetSignatureLen ();
uint8_t * buf1 = signature + signatureLen;
size_t headerLen = identityLen + signatureLen;
memcpy (buf1, payload, len);
if (m_Owner.GetIdentity ().GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
CryptoPP::SHA256().CalculateDigest (hash, buf1, len);
m_Owner.Sign (hash, 32, signature);
}
else
m_Owner.Sign (buf1, len, signature);
auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort);
auto remote = m_Owner.FindLeaseSet (ident);
if (remote)
m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote));
else
m_Owner.RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg));
}
void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> remote, I2NPMessage * msg)
{
if (remote)
SendMsg (msg, remote);
else
DeleteI2NPMessage (msg);
}
void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr<const i2p::data::LeaseSet> remote)
{
auto outboundTunnel = m_Owner.GetTunnelPool ()->GetNextOutboundTunnel ();
auto leases = remote->GetNonExpiredLeases ();
if (!leases.empty () && outboundTunnel)
{
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1);
auto garlic = m_Owner.WrapMessage (remote, ToSharedI2NPMessage (msg), true);
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeTunnel,
leases[i].tunnelGateway, leases[i].tunnelID,
garlic
});
outboundTunnel->SendTunnelDataMsg (msgs);
}
else
{
if (outboundTunnel)
LogPrint (eLogWarning, "Failed to send datagram. All leases expired");
else
LogPrint (eLogWarning, "Failed to send datagram. No outbound tunnels");
DeleteI2NPMessage (msg);
}
}
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
i2p::data::IdentityEx identity;
size_t identityLen = identity.FromBuffer (buf, len);
const uint8_t * signature = buf + identityLen;
size_t headerLen = identityLen + identity.GetSignatureLen ();
bool verified = false;
if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
CryptoPP::SHA256().CalculateDigest (hash, buf + headerLen, len - headerLen);
verified = identity.Verify (hash, 32, signature);
}
else
verified = identity.Verify (buf + headerLen, len - headerLen, signature);
if (verified)
{
auto it = m_ReceiversByPorts.find (toPort);
if (it != m_ReceiversByPorts.end ())
it->second (identity, fromPort, toPort, buf + headerLen, len -headerLen);
else if (m_Receiver != nullptr)
m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen);
else
LogPrint (eLogWarning, "Receiver for datagram is not set");
}
else
LogPrint (eLogWarning, "Datagram signature verification failed");
}
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
// unzip it
CryptoPP::Gunzip decompressor;
decompressor.Put (buf, len);
decompressor.MessageEnd();
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
auto uncompressedLen = decompressor.MaxRetrievable ();
if (uncompressedLen <= MAX_DATAGRAM_SIZE)
{
decompressor.Get (uncompressed, uncompressedLen);
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
}
else
LogPrint ("Received datagram size ", uncompressedLen, " exceeds max size");
}
I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
{
I2NPMessage * msg = NewI2NPMessage ();
CryptoPP::Gzip compressor; // default level
compressor.Put (payload, len);
compressor.MessageEnd();
int size = compressor.MaxRetrievable ();
uint8_t * buf = msg->GetPayload ();
htobe32buf (buf, size); // length
buf += 4;
compressor.Get (buf, size);
htobe16buf (buf + 4, fromPort); // source port
htobe16buf (buf + 6, toPort); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData);
return msg;
}
}
}

57
core/Datagram.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef DATAGRAM_H__
#define DATAGRAM_H__
#include <inttypes.h>
#include <memory>
#include <functional>
#include <map>
#include "Identity.h"
#include "LeaseSet.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace client
{
class ClientDestination;
}
namespace datagram
{
const size_t MAX_DATAGRAM_SIZE = 32768;
class DatagramDestination
{
typedef std::function<void (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
public:
DatagramDestination (i2p::client::ClientDestination& owner);
~DatagramDestination () {};
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
void ResetReceiver () { m_Receiver = nullptr; };
void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; };
void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); };
private:
void HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, I2NPMessage * msg);
I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
void SendMsg (I2NPMessage * msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
private:
i2p::client::ClientDestination& m_Owner;
Receiver m_Receiver; // default
std::map<uint16_t, Receiver> m_ReceiversByPorts;
};
}
}
#endif

667
core/Destination.cpp Normal file
View file

@ -0,0 +1,667 @@
#include <algorithm>
#include <cassert>
#include <boost/lexical_cast.hpp>
#include "util/Log.h"
#include "util/util.h"
#include "crypto/ElGamal.h"
#include "util/Timestamp.h"
#include "NetDb.h"
#include "Destination.h"
#include "AddressBook.h"
namespace i2p
{
namespace client
{
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
const std::map<std::string, std::string> * params):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0),
m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service)
{
i2p::crypto::GenerateElGamalKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey);
int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH;
int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH;
int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY;
int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY;
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
if (params)
{
auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH);
if (it != params->end ())
{
int len = boost::lexical_cast<int>(it->second);
if (len > 0)
{
inboundTunnelLen = len;
LogPrint (eLogInfo, "Inbound tunnel length set to ", len);
}
}
it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH);
if (it != params->end ())
{
int len = boost::lexical_cast<int>(it->second);
if (len > 0)
{
outboundTunnelLen = len;
LogPrint (eLogInfo, "Outbound tunnel length set to ", len);
}
}
it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY);
if (it != params->end ())
{
int quantity = boost::lexical_cast<int>(it->second);
if (quantity > 0)
{
inboundTunnelsQuantity = quantity;
LogPrint (eLogInfo, "Inbound tunnels quantity set to ", quantity);
}
}
it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY);
if (it != params->end ())
{
int quantity = boost::lexical_cast<int>(it->second);
if (quantity > 0)
{
outboundTunnelsQuantity = quantity;
LogPrint (eLogInfo, "Outbound tunnels quantity set to ", quantity);
}
}
it = params->find (I2CP_PARAM_EXPLICIT_PEERS);
if (it != params->end ())
{
explicitPeers = std::make_shared<std::vector<i2p::data::IdentHash> >();
std::stringstream ss(it->second);
std::string b64;
while (std::getline (ss, b64, ','))
{
i2p::data::IdentHash ident;
ident.FromBase64 (b64);
explicitPeers->push_back (ident);
}
LogPrint (eLogInfo, "Explicit peers set to ", it->second);
}
}
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity);
if (explicitPeers)
m_Pool->SetExplicitPeers (explicitPeers);
if (m_IsPublic)
LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created");
m_StreamingDestination = std::make_shared<i2p::stream::StreamingDestination> (*this); // TODO:
}
ClientDestination::~ClientDestination ()
{
if (m_IsRunning)
Stop ();
for (auto it: m_LeaseSetRequests)
delete it.second;
if (m_Pool)
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
if (m_DatagramDestination)
delete m_DatagramDestination;
}
void ClientDestination::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint ("Destination: ", ex.what ());
}
}
}
void ClientDestination::Start ()
{
if (!m_IsRunning)
{
m_IsRunning = true;
m_Pool->SetLocalDestination (this);
m_Pool->SetActive (true);
m_Thread = new std::thread (std::bind (&ClientDestination::Run, this));
m_StreamingDestination->Start ();
for (auto it: m_StreamingDestinationsByPorts)
it.second->Start ();
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer,
this, std::placeholders::_1));
}
}
void ClientDestination::Stop ()
{
if (m_IsRunning)
{
m_CleanupTimer.cancel ();
m_IsRunning = false;
m_StreamingDestination->Stop ();
for (auto it: m_StreamingDestinationsByPorts)
it.second->Stop ();
if (m_DatagramDestination)
{
auto d = m_DatagramDestination;
m_DatagramDestination = nullptr;
delete d;
}
if (m_Pool)
{
m_Pool->SetLocalDestination (nullptr);
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
}
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
}
}
std::shared_ptr<const i2p::data::LeaseSet> ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
{
auto it = m_RemoteLeaseSets.find (ident);
if (it != m_RemoteLeaseSets.end ())
{
if (it->second->HasNonExpiredLeases ())
return it->second;
else
LogPrint ("All leases of remote LeaseSet expired");
}
else
{
auto ls = i2p::data::netdb.FindLeaseSet (ident);
if (ls)
{
m_RemoteLeaseSets[ident] = ls;
return ls;
}
}
return nullptr;
}
std::shared_ptr<const i2p::data::LeaseSet> ClientDestination::GetLeaseSet ()
{
if (!m_Pool) return nullptr;
if (!m_LeaseSet)
UpdateLeaseSet ();
return m_LeaseSet;
}
void ClientDestination::UpdateLeaseSet ()
{
m_LeaseSet.reset (new i2p::data::LeaseSet (*m_Pool));
}
bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
{
struct
{
uint8_t k[32], t[32];
} data;
memcpy (data.k, key, 32);
memcpy (data.t, tag, 32);
m_Service.post ([this,data](void)
{
this->AddSessionKey (data.k, data.t);
});
return true;
}
void ClientDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, this, msg));
}
void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, this, msg));
}
void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET];
switch (typeID)
{
case eI2NPData:
HandleDataMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
case eI2NPDeliveryStatus:
// we assume tunnel tests non-encrypted
HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
break;
case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
default:
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
}
}
void ClientDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
{
uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET);
size_t offset = DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
LogPrint (eLogInfo, "Reply token is ignored for DatabaseStore");
offset += 36;
}
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet
{
LogPrint (eLogDebug, "Remote LeaseSet");
auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET);
if (it != m_RemoteLeaseSets.end ())
{
leaseSet = it->second;
leaseSet->Update (buf + offset, len - offset);
if (leaseSet->IsValid ())
LogPrint (eLogDebug, "Remote LeaseSet updated");
else
{
LogPrint (eLogDebug, "Remote LeaseSet update failed");
m_RemoteLeaseSets.erase (it);
leaseSet = nullptr;
}
}
else
{
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset);
if (leaseSet->IsValid ())
{
LogPrint (eLogDebug, "New remote LeaseSet added");
m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet;
}
else
{
LogPrint (eLogError, "New remote LeaseSet verification failed");
leaseSet = nullptr;
}
}
}
else
LogPrint (eLogError, "Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ". Dropped");
auto it1 = m_LeaseSetRequests.find (buf + DATABASE_STORE_KEY_OFFSET);
if (it1 != m_LeaseSetRequests.end ())
{
it1->second->requestTimeoutTimer.cancel ();
if (it1->second->requestComplete) it1->second->requestComplete (leaseSet);
delete it1->second;
m_LeaseSetRequests.erase (it1);
}
}
void ClientDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len)
{
i2p::data::IdentHash key (buf);
int num = buf[32]; // num
LogPrint ("DatabaseSearchReply for ", key.ToBase64 (), " num=", num);
auto it = m_LeaseSetRequests.find (key);
if (it != m_LeaseSetRequests.end ())
{
LeaseSetRequest * request = it->second;
bool found = false;
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
{
for (int i = 0; i < num; i++)
{
i2p::data::IdentHash peerHash (buf + 33 + i*32);
auto floodfill = i2p::data::netdb.FindRouter (peerHash);
if (floodfill)
{
LogPrint (eLogInfo, "Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ());
if (SendLeaseSetRequest (key, floodfill, request))
found = true;
}
else
{
LogPrint (eLogInfo, "Found new floodfill. Request it");
i2p::data::netdb.RequestDestination (peerHash);
}
}
if (!found)
LogPrint (eLogError, "Suggested floodfills are not presented in netDb");
}
else
LogPrint (eLogInfo, key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST," floodfills");
if (!found)
{
if (request->requestComplete) request->requestComplete (nullptr);
delete request;
m_LeaseSetRequests.erase (key);
}
}
else
LogPrint ("Request for ", key.ToBase64 (), " not found");
}
void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
if (msgID == m_PublishReplyToken)
{
LogPrint (eLogDebug, "Publishing confirmed");
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0;
}
else
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg);
}
void ClientDestination::SetLeaseSetUpdated ()
{
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
UpdateLeaseSet ();
if (m_IsPublic)
Publish ();
}
void ClientDestination::Publish ()
{
if (!m_LeaseSet || !m_Pool)
{
LogPrint (eLogError, "Can't publish non-existing LeaseSet");
return;
}
if (m_PublishReplyToken)
{
LogPrint (eLogInfo, "Publishing is pending");
return;
}
auto outbound = m_Pool->GetNextOutboundTunnel ();
if (!outbound)
{
LogPrint ("Can't publish LeaseSet. No outbound tunnels");
return;
}
std::set<i2p::data::IdentHash> excluded;
auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills);
if (!floodfill)
{
LogPrint ("Can't publish LeaseSet. No more floodfills found");
m_ExcludedFloodfills.clear ();
return;
}
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
LogPrint (eLogDebug, "Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
m_PublishReplyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken));
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer,
this, std::placeholders::_1));
outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg);
}
void ClientDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (m_PublishReplyToken)
{
LogPrint (eLogWarning, "Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, "seconds. Try again");
m_PublishReplyToken = 0;
Publish ();
}
}
}
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
{
uint32_t length = bufbe32toh (buf);
buf += 4;
// we assume I2CP payload
uint16_t fromPort = bufbe16toh (buf + 4), // source
toPort = bufbe16toh (buf + 6); // destination
switch (buf[9])
{
case PROTOCOL_TYPE_STREAMING:
{
// streaming protocol
auto dest = GetStreamingDestination (toPort);
if (dest)
dest->HandleDataMessagePayload (buf, length);
else
LogPrint ("Missing streaming destination");
}
break;
case PROTOCOL_TYPE_DATAGRAM:
// datagram protocol
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length);
else
LogPrint ("Missing streaming destination");
break;
default:
LogPrint ("Data: unexpected protocol ", buf[9]);
}
}
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) {
assert(streamRequestComplete);
auto leaseSet = FindLeaseSet (dest);
if (leaseSet)
streamRequestComplete(CreateStream (leaseSet, port));
else
{
RequestDestination (dest,
[this, streamRequestComplete, port](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
streamRequestComplete(CreateStream (ls, port));
else
streamRequestComplete (nullptr);
});
}
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
{
if (m_StreamingDestination)
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
else
return nullptr;
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::GetStreamingDestination (int port) const
{
if (port)
{
auto it = m_StreamingDestinationsByPorts.find (port);
if (it != m_StreamingDestinationsByPorts.end ())
return it->second;
}
// if port is zero or not found, use default destination
return m_StreamingDestination;
}
void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor)
{
if (m_StreamingDestination)
m_StreamingDestination->SetAcceptor (acceptor);
}
void ClientDestination::StopAcceptingStreams ()
{
if (m_StreamingDestination)
m_StreamingDestination->ResetAcceptor ();
}
bool ClientDestination::IsAcceptingStreams () const
{
if (m_StreamingDestination)
return m_StreamingDestination->IsAcceptorSet ();
return false;
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port)
{
auto dest = std::make_shared<i2p::stream::StreamingDestination> (*this, port);
if (port)
m_StreamingDestinationsByPorts[port] = dest;
else // update default
m_StreamingDestination = dest;
return dest;
}
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination ()
{
if (!m_DatagramDestination)
m_DatagramDestination = new i2p::datagram::DatagramDestination (*this);
return m_DatagramDestination;
}
bool ClientDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
{
if (!m_Pool || !IsReady ())
{
if (requestComplete) requestComplete (nullptr);
return false;
}
m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, this, dest, requestComplete));
return true;
}
void ClientDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
{
std::set<i2p::data::IdentHash> excluded;
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
if (floodfill)
{
LeaseSetRequest * request = new LeaseSetRequest (m_Service);
request->requestComplete = requestComplete;
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, LeaseSetRequest *>(dest,request));
if (ret.second) // inserted
{
if (!SendLeaseSetRequest (dest, floodfill, request))
{
// request failed
if (request->requestComplete) request->requestComplete (nullptr);
delete request;
m_LeaseSetRequests.erase (dest);
}
}
else // duplicate
{
LogPrint (eLogError, "Request of ", dest.ToBase64 (), " is pending already");
// TODO: queue up requests
if (request->requestComplete) request->requestComplete (nullptr);
delete request;
}
}
else
LogPrint (eLogError, "No floodfills found");
}
bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest,
std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, LeaseSetRequest * request)
{
auto replyTunnel = m_Pool->GetNextInboundTunnel ();
if (!replyTunnel) LogPrint (eLogError, "No inbound tunnels found");
auto outboundTunnel = m_Pool->GetNextOutboundTunnel ();
if (!outboundTunnel) LogPrint (eLogError, "No outbound tunnels found");
if (replyTunnel && outboundTunnel)
{
request->excluded.insert (nextFloodfill->GetIdentHash ());
request->requestTime = i2p::util::GetSecondsSinceEpoch ();
request->requestTimeoutTimer.cancel ();
CryptoPP::AutoSeededRandomPool rnd;
uint8_t replyKey[32], replyTag[32];
rnd.GenerateBlock (replyKey, 32); // random session key
rnd.GenerateBlock (replyTag, 32); // random session tag
AddSessionKey (replyKey, replyTag);
auto msg = WrapMessage (nextFloodfill,
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded,
replyTunnel.get (), replyKey, replyTag));
outboundTunnel->SendTunnelDataMsg (
{
i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeRouter,
nextFloodfill->GetIdentHash (), 0, msg
}
});
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.async_wait (std::bind (&ClientDestination::HandleRequestTimoutTimer,
this, std::placeholders::_1, dest));
}
else
return false;
return true;
}
void ClientDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto it = m_LeaseSetRequests.find (dest);
if (it != m_LeaseSetRequests.end ())
{
bool done = false;
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
if (floodfill)
done = !SendLeaseSetRequest (dest, floodfill, it->second);
else
done = true;
}
else
{
LogPrint (eLogInfo, dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds");
done = true;
}
if (done)
{
if (it->second->requestComplete) it->second->requestComplete (nullptr);
delete it->second;
m_LeaseSetRequests.erase (it);
}
}
}
}
void ClientDestination::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
CleanupRoutingSessions ();
CleanupRemoteLeaseSets ();
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer,
this, std::placeholders::_1));
}
}
void ClientDestination::CleanupRemoteLeaseSets ()
{
for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();)
{
if (!it->second->HasNonExpiredLeases ()) // all leases expired
{
LogPrint ("Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
it = m_RemoteLeaseSets.erase (it);
}
else
it++;
}
}
}
}

156
core/Destination.h Normal file
View file

@ -0,0 +1,156 @@
#ifndef DESTINATION_H__
#define DESTINATION_H__
#include <thread>
#include <mutex>
#include <memory>
#include <map>
#include <set>
#include <string>
#include <functional>
#include <boost/asio.hpp>
#include "Identity.h"
#include "tunnel/TunnelPool.h"
#include "crypto/CryptoConst.h"
#include "LeaseSet.h"
#include "Garlic.h"
#include "NetDb.h"
#include "Streaming.h"
#include "Datagram.h"
namespace i2p
{
namespace client
{
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
const uint8_t PROTOCOL_TYPE_RAW = 18;
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
const int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
const int DESTINATION_CLEANUP_TIMEOUT = 20; // in minutes
// I2CP
const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length";
const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3;
const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length";
const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3;
const char I2CP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity";
const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5;
const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity";
const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5;
const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers";
const int STREAM_REQUEST_TIMEOUT = 60; //in seconds
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
class ClientDestination: public i2p::garlic::GarlicDestination
{
typedef std::function<void (std::shared_ptr<i2p::data::LeaseSet> leaseSet)> RequestComplete;
// leaseSet = nullptr means not found
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
std::set<i2p::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
RequestComplete requestComplete;
};
public:
ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination ();
virtual void Start ();
virtual void Stop ();
bool IsRunning () const { return m_IsRunning; };
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases () && m_Pool->GetOutboundTunnels ().size () > 0; };
std::shared_ptr<const i2p::data::LeaseSet> FindLeaseSet (const i2p::data::IdentHash& ident);
bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr);
// streaming
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port); // additional
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
// following methods operate with default streaming destination
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor);
void StopAcceptingStreams ();
bool IsAcceptingStreams () const;
// datagram
i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; };
i2p::datagram::DatagramDestination * CreateDatagramDestination ();
// implements LocalDestination
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; };
// implements GarlicDestination
std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet ();
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
// override GarlicDestination
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void SetLeaseSetUpdated ();
// I2CP
void HandleDataMessage (const uint8_t * buf, size_t len);
private:
void Run ();
void UpdateLeaseSet ();
void Publish ();
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete);
bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, LeaseSetRequest * request);
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets ();
private:
volatile bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
i2p::data::PrivateKeys m_Keys;
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
std::map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
std::map<i2p::data::IdentHash, LeaseSetRequest *> m_LeaseSetRequests;
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
std::shared_ptr<i2p::data::LeaseSet> m_LeaseSet;
bool m_IsPublic;
uint32_t m_PublishReplyToken;
std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
i2p::datagram::DatagramDestination * m_DatagramDestination;
boost::asio::deadline_timer m_PublishConfirmationTimer, m_CleanupTimer;
public:
// for HTTP only
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
};
}
}
#endif

617
core/Garlic.cpp Normal file
View file

@ -0,0 +1,617 @@
#include <inttypes.h>
#include "util/I2PEndian.h"
#include <map>
#include <string>
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "tunnel/Tunnel.h"
#include "tunnel/TunnelPool.h"
#include "util/Timestamp.h"
#include "Destination.h"
#include "Garlic.h"
namespace i2p
{
namespace garlic
{
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
std::shared_ptr<const i2p::data::RoutingDestination> destination, int numTags, bool attachLeaseSet):
m_Owner (owner), m_Destination (destination), m_NumTags (numTags),
m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend)
{
// create new session tags and session key
m_Rnd.GenerateBlock (m_SessionKey, 32);
m_Encryption.SetKey (m_SessionKey);
}
GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag):
m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend)
{
memcpy (m_SessionKey, sessionKey, 32);
m_Encryption.SetKey (m_SessionKey);
m_SessionTags.push_back (sessionTag);
m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch ();
}
GarlicRoutingSession::~GarlicRoutingSession ()
{
for (auto it: m_UnconfirmedTagsMsgs)
delete it.second;
m_UnconfirmedTagsMsgs.clear ();
}
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
{
auto tags = new UnconfirmedTags (m_NumTags);
tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch ();
for (int i = 0; i < m_NumTags; i++)
{
m_Rnd.GenerateBlock (tags->sessionTags[i], 32);
tags->sessionTags[i].creationTime = tags->tagsCreationTime;
}
return tags;
}
void GarlicRoutingSession::MessageConfirmed (uint32_t msgID)
{
TagsConfirmed (msgID);
if (msgID == m_LeaseSetUpdateMsgID)
{
m_LeaseSetUpdateStatus = eLeaseSetUpToDate;
LogPrint (eLogInfo, "LeaseSet update confirmed");
}
else
CleanupExpiredTags ();
}
void GarlicRoutingSession::TagsConfirmed (uint32_t msgID)
{
auto it = m_UnconfirmedTagsMsgs.find (msgID);
if (it != m_UnconfirmedTagsMsgs.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
UnconfirmedTags * tags = it->second;
if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
{
for (int i = 0; i < tags->numTags; i++)
m_SessionTags.push_back (tags->sessionTags[i]);
}
m_UnconfirmedTagsMsgs.erase (it);
delete tags;
}
}
bool GarlicRoutingSession::CleanupExpiredTags ()
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();)
{
if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
it = m_SessionTags.erase (it);
else
it++;
}
// delete expired unconfirmed tags
for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();)
{
if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
{
if (m_Owner)
m_Owner->RemoveCreatedSession (it->first);
delete it->second;
it = m_UnconfirmedTagsMsgs.erase (it);
}
else
it++;
}
return !m_SessionTags.empty () || m_UnconfirmedTagsMsgs.empty ();
}
std::shared_ptr<I2NPMessage> GarlicRoutingSession::WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg)
{
auto m = ToSharedI2NPMessage(NewI2NPMessage ());
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
size_t len = 0;
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
// find non-expired tag
bool tagFound = false;
SessionTag tag;
if (m_NumTags > 0)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
while (!m_SessionTags.empty ())
{
if (ts < m_SessionTags.front ().creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
{
tag = m_SessionTags.front ();
m_SessionTags.pop_front (); // use same tag only once
tagFound = true;
break;
}
else
m_SessionTags.pop_front (); // remove expired tag
}
}
// create message
if (!tagFound) // new session
{
LogPrint ("No garlic tags available. Use ElGamal");
if (!m_Destination)
{
LogPrint ("Can't use ElGamal for unknown destination");
return nullptr;
}
// create ElGamal block
ElGamalBlock elGamal;
memcpy (elGamal.sessionKey, m_SessionKey, 32);
m_Rnd.GenerateBlock (elGamal.preIV, 32); // Pre-IV
uint8_t iv[32]; // IV is first 16 bytes
CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32);
m_Destination->GetElGamalEncryption ()->Encrypt ((uint8_t *)&elGamal, sizeof(elGamal), buf, true);
m_Encryption.SetIV (iv);
buf += 514;
len += 514;
}
else // existing session
{
// session tag
memcpy (buf, tag, 32);
uint8_t iv[32]; // IV is first 16 bytes
CryptoPP::SHA256().CalculateDigest(iv, tag, 32);
m_Encryption.SetIV (iv);
buf += 32;
len += 32;
}
// AES block
len += CreateAESBlock (buf, msg);
htobe32buf (m->GetPayload (), len);
m->len += len + 4;
m->FillI2NPMessageHeader (eI2NPGarlic);
return m;
}
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg)
{
size_t blockSize = 0;
bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3);
UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr;
htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count
blockSize += 2;
if (newTags) // session tags recreated
{
for (int i = 0; i < newTags->numTags; i++)
{
memcpy (buf + blockSize, newTags->sessionTags[i], 32); // tags
blockSize += 32;
}
}
uint32_t * payloadSize = (uint32_t *)(buf + blockSize);
blockSize += 4;
uint8_t * payloadHash = buf + blockSize;
blockSize += 32;
buf[blockSize] = 0; // flag
blockSize++;
size_t len = CreateGarlicPayload (buf + blockSize, msg, newTags);
htobe32buf (payloadSize, len);
CryptoPP::SHA256().CalculateDigest(payloadHash, buf + blockSize, len);
blockSize += len;
size_t rem = blockSize % 16;
if (rem)
blockSize += (16-rem); //padding
m_Encryption.Encrypt(buf, blockSize, buf);
return blockSize;
}
size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec
uint32_t msgID = m_Rnd.GenerateWord32 ();
size_t size = 0;
uint8_t * numCloves = payload + size;
*numCloves = 0;
size++;
if (m_Owner)
{
// resubmit non-confirmed LeaseSet
if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted &&
i2p::util::GetMillisecondsSinceEpoch () > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
m_LeaseSetUpdateStatus = eLeaseSetUpdated;
// attach DeviveryStatus if necessary
if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated
{
// clove is DeliveryStatus
auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID);
if (cloveSize > 0) // successive?
{
size += cloveSize;
(*numCloves)++;
if (newTags) // new tags created
m_UnconfirmedTagsMsgs[msgID] = newTags;
m_Owner->DeliveryStatusSent (shared_from_this (), msgID);
}
else
LogPrint ("DeliveryStatus clove was not created");
}
// attach LeaseSet
if (m_LeaseSetUpdateStatus == eLeaseSetUpdated)
{
m_LeaseSetUpdateStatus = eLeaseSetSubmitted;
m_LeaseSetUpdateMsgID = msgID;
m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch ();
// clove if our leaseSet must be attached
auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ());
size += CreateGarlicClove (payload + size, leaseSet, false);
(*numCloves)++;
}
}
if (msg) // clove message ifself if presented
{
size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false);
(*numCloves)++;
}
memset (payload + size, 0, 3); // certificate of message
size += 3;
htobe32buf (payload + size, msgID); // MessageID
size += 4;
htobe64buf (payload + size, ts); // Expiration of message
size += 8;
return size;
}
size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec
size_t size = 0;
if (isDestination && m_Destination)
{
buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination
size++;
memcpy (buf + size, m_Destination->GetIdentHash (), 32);
size += 32;
}
else
{
buf[size] = 0;// delivery instructions flag local
size++;
}
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
size += msg->GetLength ();
htobe32buf (buf + size, m_Rnd.GenerateWord32 ()); // CloveID
size += 4;
htobe64buf (buf + size, ts); // Expiration of clove
size += 8;
memset (buf + size, 0, 3); // certificate of clove
size += 3;
return size;
}
size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID)
{
size_t size = 0;
if (m_Owner)
{
auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel ();
if (inboundTunnel)
{
buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel
size++;
// hash and tunnelID sequence is reversed for Garlic
memcpy (buf + size, inboundTunnel->GetNextIdentHash (), 32); // To Hash
size += 32;
htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID
size += 4;
// create msg
auto msg = CreateDeliveryStatusMsg (msgID);
if (m_Owner)
{
//encrypt
uint8_t key[32], tag[32];
m_Rnd.GenerateBlock (key, 32); // random session key
m_Rnd.GenerateBlock (tag, 32); // random session tag
m_Owner->SubmitSessionKey (key, tag);
GarlicRoutingSession garlic (key, tag);
msg = garlic.WrapSingleMessage (msg);
}
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
size += msg->GetLength ();
// fill clove
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec
htobe32buf (buf + size, m_Rnd.GenerateWord32 ()); // CloveID
size += 4;
htobe64buf (buf + size, ts); // Expiration of clove
size += 8;
memset (buf + size, 0, 3); // certificate of clove
size += 3;
}
else
LogPrint (eLogError, "No inbound tunnels in the pool for DeliveryStatus");
}
else
LogPrint ("Missing local LeaseSet");
return size;
}
GarlicDestination::~GarlicDestination ()
{
}
void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag)
{
if (key)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
decryption->SetKey (key);
m_Tags[SessionTag(tag, ts)] = decryption;
}
}
bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
{
AddSessionKey (key, tag);
return true;
}
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
uint8_t * buf = msg->GetPayload ();
uint32_t length = bufbe32toh (buf);
if (length > msg->GetLength ())
{
LogPrint (eLogError, "Garlic message length ", length, " exceeds I2NP message length ", msg->GetLength ());
return;
}
buf += 4; // length
auto it = m_Tags.find (SessionTag(buf));
if (it != m_Tags.end ())
{
// tag found. Use AES
if (length >= 32)
{
uint8_t iv[32]; // IV is first 16 bytes
CryptoPP::SHA256().CalculateDigest(iv, buf, 32);
it->second->SetIV (iv);
it->second->Decrypt (buf + 32, length - 32, buf + 32);
HandleAESBlock (buf + 32, length - 32, it->second, msg->from);
}
else
LogPrint (eLogError, "Garlic message length ", length, " is less than 32 bytes");
m_Tags.erase (it); // tag might be used only once
}
else
{
// tag not found. Use ElGamal
ElGamalBlock elGamal;
if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true))
{
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
decryption->SetKey (elGamal.sessionKey);
uint8_t iv[32]; // IV is first 16 bytes
CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32);
decryption->SetIV (iv);
decryption->Decrypt(buf + 514, length - 514, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
}
else
LogPrint (eLogError, "Failed to decrypt garlic");
}
// cleanup expired tags
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts > m_LastTagsCleanupTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
{
if (m_LastTagsCleanupTime)
{
int numExpiredTags = 0;
for (auto it = m_Tags.begin (); it != m_Tags.end ();)
{
if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
{
numExpiredTags++;
it = m_Tags.erase (it);
}
else
it++;
}
LogPrint (numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ());
}
m_LastTagsCleanupTime = ts;
}
}
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
uint16_t tagCount = bufbe16toh (buf);
buf += 2; len -= 2;
if (tagCount > 0)
{
if (tagCount*32 > len)
{
LogPrint (eLogError, "Tag count ", tagCount, " exceeds length ", len);
return ;
}
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (int i = 0; i < tagCount; i++)
m_Tags[SessionTag(buf + i*32, ts)] = decryption;
}
buf += tagCount*32;
len -= tagCount*32;
uint32_t payloadSize = bufbe32toh (buf);
if (payloadSize > len)
{
LogPrint (eLogError, "Unexpected payload size ", payloadSize);
return;
}
buf += 4;
uint8_t * payloadHash = buf;
buf += 32;// payload hash.
if (*buf) // session key?
buf += 32; // new session key
buf++; // flag
// payload
if (!CryptoPP::SHA256().VerifyDigest (payloadHash, buf, payloadSize)) // payload hash doesn't match
{
LogPrint ("Wrong payload hash");
return;
}
HandleGarlicPayload (buf, payloadSize, from);
}
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
const uint8_t * buf1 = buf;
int numCloves = buf[0];
LogPrint (numCloves," cloves");
buf++;
for (int i = 0; i < numCloves; i++)
{
// delivery instructions
uint8_t flag = buf[0];
buf++; // flag
if (flag & 0x80) // encrypted?
{
// TODO: implement
LogPrint ("Clove encrypted");
buf += 32;
}
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
switch (deliveryType)
{
case eGarlicDeliveryTypeLocal:
LogPrint ("Garlic type local");
HandleI2NPMessage (buf, len, from);
break;
case eGarlicDeliveryTypeDestination:
LogPrint ("Garlic type destination");
buf += 32; // destination. check it later or for multiple destinations
HandleI2NPMessage (buf, len, from);
break;
case eGarlicDeliveryTypeTunnel:
{
LogPrint ("Garlic type tunnel");
// gwHash and gwTunnel sequence is reverted
uint8_t * gwHash = buf;
buf += 32;
uint32_t gwTunnel = bufbe32toh (buf);
buf += 4;
std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel;
if (from && from->GetTunnelPool ())
tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel ();
if (tunnel) // we have send it through an outbound tunnel
{
auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from);
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
}
else
LogPrint ("No outbound tunnels available for garlic clove");
break;
}
case eGarlicDeliveryTypeRouter:
LogPrint ("Garlic type router not supported");
buf += 32;
break;
default:
LogPrint ("Unknow garlic delivery type ", (int)deliveryType);
}
buf += GetI2NPMessageLength (buf); // I2NP
buf += 4; // CloveID
buf += 8; // Date
buf += 3; // Certificate
if (buf - buf1 > (int)len)
{
LogPrint (eLogError, "Garlic clove is too long");
break;
}
}
}
std::shared_ptr<I2NPMessage> GarlicDestination::WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet)
{
auto session = GetRoutingSession (destination, attachLeaseSet); // 32 tags by default
return session->WrapSingleMessage (msg);
}
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
{
auto it = m_Sessions.find (destination->GetIdentHash ());
std::shared_ptr<GarlicRoutingSession> session;
if (it != m_Sessions.end ())
session = it->second;
if (!session)
{
session = std::make_shared<GarlicRoutingSession> (this, destination,
attachLeaseSet ? 40 : 4, attachLeaseSet); // 40 tags for connections and 4 for LS requests
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions[destination->GetIdentHash ()] = session;
}
return session;
}
void GarlicDestination::CleanupRoutingSessions ()
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto it = m_Sessions.begin (); it != m_Sessions.end ();)
{
if (!it->second->CleanupExpiredTags ())
{
LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted");
it = m_Sessions.erase (it);
}
else
it++;
}
}
void GarlicDestination::RemoveCreatedSession (uint32_t msgID)
{
m_CreatedSessions.erase (msgID);
}
void GarlicDestination::DeliveryStatusSent (std::shared_ptr<GarlicRoutingSession> session, uint32_t msgID)
{
m_CreatedSessions[msgID] = session;
}
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload ());
{
auto it = m_CreatedSessions.find (msgID);
if (it != m_CreatedSessions.end ())
{
it->second->MessageConfirmed (msgID);
m_CreatedSessions.erase (it);
LogPrint (eLogInfo, "Garlic message ", msgID, " acknowledged");
}
}
}
void GarlicDestination::SetLeaseSetUpdated ()
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto it: m_Sessions)
it.second->SetLeaseSetUpdated ();
}
void GarlicDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
HandleGarlicMessage (msg);
}
void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
HandleDeliveryStatusMessage (msg);
}
}
}

169
core/Garlic.h Normal file
View file

@ -0,0 +1,169 @@
#ifndef GARLIC_H__
#define GARLIC_H__
#include <inttypes.h>
#include <map>
#include <list>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include <cryptopp/osrng.h>
#include "crypto/aes.h"
#include "I2NPProtocol.h"
#include "LeaseSet.h"
#include "util/Queue.h"
#include "Identity.h"
namespace i2p
{
namespace garlic
{
enum GarlicDeliveryType
{
eGarlicDeliveryTypeLocal = 0,
eGarlicDeliveryTypeDestination = 1,
eGarlicDeliveryTypeRouter = 2,
eGarlicDeliveryTypeTunnel = 3
};
#pragma pack(1)
struct ElGamalBlock
{
uint8_t sessionKey[32];
uint8_t preIV[32];
uint8_t padding[158];
};
#pragma pack()
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
struct SessionTag: public i2p::data::Tag<32>
{
SessionTag (const uint8_t * buf, uint32_t ts = 0): Tag<32>(buf), creationTime (ts) {};
SessionTag () = default;
SessionTag (const SessionTag& ) = default;
SessionTag& operator= (const SessionTag& ) = default;
#ifndef _WIN32
SessionTag (SessionTag&& ) = default;
SessionTag& operator= (SessionTag&& ) = default;
#endif
uint32_t creationTime; // seconds since epoch
};
class GarlicDestination;
class GarlicRoutingSession: public std::enable_shared_from_this<GarlicRoutingSession>
{
enum LeaseSetUpdateStatus
{
eLeaseSetUpToDate = 0,
eLeaseSetUpdated,
eLeaseSetSubmitted,
eLeaseSetDoNotSend
};
struct UnconfirmedTags
{
UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; };
~UnconfirmedTags () { delete[] sessionTags; };
int numTags;
SessionTag * sessionTags;
uint32_t tagsCreationTime;
};
public:
GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr<const i2p::data::RoutingDestination> destination,
int numTags, bool attachLeaseSet);
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
~GarlicRoutingSession ();
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
void MessageConfirmed (uint32_t msgID);
bool CleanupExpiredTags (); // returns true if something left
void SetLeaseSetUpdated ()
{
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
};
private:
size_t CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg);
size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags);
size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination);
size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID);
void TagsConfirmed (uint32_t msgID);
UnconfirmedTags * GenerateSessionTags ();
private:
GarlicDestination * m_Owner;
std::shared_ptr<const i2p::data::RoutingDestination> m_Destination;
i2p::crypto::AESKey m_SessionKey;
std::list<SessionTag> m_SessionTags;
int m_NumTags;
std::map<uint32_t, UnconfirmedTags *> m_UnconfirmedTagsMsgs;
LeaseSetUpdateStatus m_LeaseSetUpdateStatus;
uint32_t m_LeaseSetUpdateMsgID;
uint64_t m_LeaseSetSubmissionTime; // in milliseconds
i2p::crypto::CBCEncryption m_Encryption;
CryptoPP::AutoSeededRandomPool m_Rnd;
};
class GarlicDestination: public i2p::data::LocalDestination
{
public:
GarlicDestination (): m_LastTagsCleanupTime (0) {};
~GarlicDestination ();
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
void CleanupRoutingSessions ();
void RemoveCreatedSession (uint32_t msgID);
std::shared_ptr<I2NPMessage> WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet = false);
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread
void DeliveryStatusSent (std::shared_ptr<GarlicRoutingSession> session, uint32_t msgID);
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
virtual void SetLeaseSetUpdated ();
virtual std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) = 0;
protected:
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
private:
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
private:
// outgoing sessions
std::mutex m_SessionsMutex;
std::map<i2p::data::IdentHash, std::shared_ptr<GarlicRoutingSession> > m_Sessions;
// incoming
std::map<SessionTag, std::shared_ptr<i2p::crypto::CBCDecryption>> m_Tags;
uint32_t m_LastTagsCleanupTime;
// DeliveryStatus
std::map<uint32_t, std::shared_ptr<GarlicRoutingSession> > m_CreatedSessions; // msgID -> session
};
}
}
#endif

624
core/I2NPProtocol.cpp Normal file
View file

@ -0,0 +1,624 @@
#include <string.h>
#include <atomic>
#include "util/I2PEndian.h"
#include <cryptopp/gzip.h>
#include "crypto/ElGamal.h"
#include "util/Timestamp.h"
#include "RouterContext.h"
#include "NetDb.h"
#include "tunnel/Tunnel.h"
#include "transport/Transports.h"
#include "Garlic.h"
#include "I2NPProtocol.h"
using namespace i2p::transport;
namespace i2p
{
I2NPMessage * NewI2NPMessage ()
{
return new I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE>();
}
I2NPMessage * NewI2NPShortMessage ()
{
return new I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE>();
}
I2NPMessage * NewI2NPMessage (size_t len)
{
return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage ();
}
void DeleteI2NPMessage (I2NPMessage * msg)
{
delete msg;
}
std::shared_ptr<I2NPMessage> ToSharedI2NPMessage (I2NPMessage * msg)
{
return std::shared_ptr<I2NPMessage>(msg, DeleteI2NPMessage);
}
void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID)
{
SetTypeID (msgType);
if (replyMsgID) // for tunnel creation
SetMsgID (replyMsgID);
else
SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ());
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number
UpdateSize ();
UpdateChks ();
}
void I2NPMessage::RenewI2NPMessageHeader ()
{
SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ());
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000);
}
I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID)
{
I2NPMessage * msg = NewI2NPMessage (len);
if (msg->len + len < msg->maxLen)
{
memcpy (msg->GetPayload (), buf, len);
msg->len += len;
}
else
LogPrint (eLogError, "I2NP message length ", len, " exceeds max length");
msg->FillI2NPMessageHeader (msgType, replyMsgID);
return msg;
}
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
I2NPMessage * msg = NewI2NPMessage ();
if (msg->offset + len < msg->maxLen)
{
memcpy (msg->GetBuffer (), buf, len);
msg->len = msg->offset + len;
msg->from = from;
}
else
LogPrint (eLogError, "I2NP message length ", len, " exceeds max length");
return ToSharedI2NPMessage(msg);
}
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
{
I2NPMessage * m = NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
if (msgID)
{
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::util::GetMillisecondsSinceEpoch ());
}
else // for SSU establishment
{
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, i2p::context.GetRandomNumberGenerator ().GenerateWord32 ());
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2
}
m->len += DELIVERY_STATUS_SIZE;
m->FillI2NPMessageHeader (eI2NPDeliveryStatus);
return ToSharedI2NPMessage (m);
}
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
{
auto m = ToSharedI2NPMessage (excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ());
uint8_t * buf = m->GetPayload ();
memcpy (buf, key, 32); // key
buf += 32;
memcpy (buf, from, 32); // from
buf += 32;
uint8_t flag = exploratory ? DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP : DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP;
if (replyTunnelID)
{
*buf = flag | DATABASE_LOOKUP_DELIVERY_FLAG; // set delivery flag
htobe32buf (buf+1, replyTunnelID);
buf += 5;
}
else
{
*buf = flag; // flag
buf++;
}
if (excludedPeers)
{
int cnt = excludedPeers->size ();
htobe16buf (buf, cnt);
buf += 2;
for (auto& it: *excludedPeers)
{
memcpy (buf, it, 32);
buf += 32;
}
}
else
{
// nothing to exclude
htobuf16 (buf, 0);
buf += 2;
}
m->len += (buf - m->GetPayload ());
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
return m;
}
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag)
{
int cnt = excludedFloodfills.size ();
auto m = ToSharedI2NPMessage (cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ());
uint8_t * buf = m->GetPayload ();
memcpy (buf, dest, 32); // key
buf += 32;
memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW
buf += 32;
*buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags
htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID
buf += 5;
// excluded
htobe16buf (buf, cnt);
buf += 2;
if (cnt > 0)
{
for (auto& it: excludedFloodfills)
{
memcpy (buf, it, 32);
buf += 32;
}
}
// encryption
memcpy (buf, replyKey, 32);
buf[32] = 1; // 1 tag
memcpy (buf + 33, replyTag, 32);
buf += 65;
m->len += (buf - m->GetPayload ());
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident,
std::vector<i2p::data::IdentHash> routers)
{
auto m = ToSharedI2NPMessage (NewI2NPShortMessage ());
uint8_t * buf = m->GetPayload ();
size_t len = 0;
memcpy (buf, ident, 32);
len += 32;
buf[len] = routers.size ();
len++;
for (auto it: routers)
{
memcpy (buf + len, it, 32);
len += 32;
}
memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32);
len += 32;
m->len += len;
m->FillI2NPMessageHeader (eI2NPDatabaseSearchReply);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router, uint32_t replyToken)
{
if (!router) // we send own RouterInfo
router = context.GetSharedRouterInfo ();
auto m = ToSharedI2NPMessage (NewI2NPShortMessage ());
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = 0; // RouterInfo
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
memset (buf, 0, 4); // zero tunnelID means direct reply
buf += 4;
memcpy (buf, router->GetIdentHash (), 32);
buf += 32;
}
CryptoPP::Gzip compressor;
compressor.Put (router->GetBuffer (), router->GetBufferLen ());
compressor.MessageEnd();
auto size = compressor.MaxRetrievable ();
htobe16buf (buf, size); // size
buf += 2;
m->len += (buf - payload); // payload size
if (m->len + size > m->maxLen)
{
LogPrint (eLogInfo, "DatabaseStore message size is not enough for ", m->len + size);
auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ());
*newMsg = *m;
m = newMsg;
buf = m->buf + m->len;
}
compressor.Get (buf, size);
m->len += size;
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint32_t replyToken)
{
if (!leaseSet) return nullptr;
auto m = ToSharedI2NPMessage (NewI2NPShortMessage ());
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
size_t size = DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
auto leases = leaseSet->GetNonExpiredLeases ();
if (leases.size () > 0)
{
htobe32buf (payload + size, leases[0].tunnelID);
size += 4; // reply tunnelID
memcpy (payload + size, leases[0].tunnelGateway, 32);
size += 32; // reply tunnel gateway
}
else
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
}
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
size += leaseSet->GetBufferLen ();
m->len += size;
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
return m;
}
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
{
for (int i = 0; i < num; i++)
{
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint ("Record ",i," is ours");
i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText);
// replace record to reply
if (i2p::context.AcceptsTunnels () &&
i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS &&
!i2p::transport::transports.IsBandwidthExceeded ())
{
i2p::tunnel::TransitTunnel * transitTunnel =
i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80,
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40);
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0;
}
else
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30)
//TODO: fill filler
CryptoPP::SHA256().CalculateDigest(record + BUILD_RESPONSE_RECORD_HASH_OFFSET,
record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1); // + 1 byte of ret
// encrypt reply
i2p::crypto::CBCEncryption encryption;
for (int j = 0; j < num; j++)
{
encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
}
return true;
}
}
return false;
}
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint ("VariableTunnelBuild ", num, " records");
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint ("VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
{
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE] = {};
if (HandleBuildRequestRecords (num, buf + 1, clearText))
{
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
ToSharedI2NPMessage (CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))));
}
}
}
void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
{
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText))
{
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
ToSharedI2NPMessage (CreateI2NPMessage (eI2NPTunnelBuild, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))));
}
}
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
LogPrint ("VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID);
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
if (tunnel)
{
// reply for outbound tunnel
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
}
else
{
LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
LogPrint ("Pending tunnel for message ", replyMsgID, " not found");
}
I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf)
{
I2NPMessage * msg = NewI2NPShortMessage ();
memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE);
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
msg->FillI2NPMessageHeader (eI2NPTunnelData);
return msg;
}
I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
{
I2NPMessage * msg = NewI2NPShortMessage ();
memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
htobe32buf (msg->GetPayload (), tunnelID);
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
msg->FillI2NPMessageHeader (eI2NPTunnelData);
return msg;
}
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ()
{
I2NPMessage * msg = NewI2NPShortMessage ();
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
return ToSharedI2NPMessage (msg);
}
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
{
I2NPMessage * msg = NewI2NPMessage (len);
uint8_t * payload = msg->GetPayload ();
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
memcpy (payload + TUNNEL_GATEWAY_HEADER_SIZE, buf, len);
msg->len += TUNNEL_GATEWAY_HEADER_SIZE + len;
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
return msg;
}
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg)
{
if (msg->offset >= I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE)
{
// message is capable to be used without copying
uint8_t * payload = msg->GetBuffer () - TUNNEL_GATEWAY_HEADER_SIZE;
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
int len = msg->GetLength ();
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE);
msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len;
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
return msg;
}
else
{
I2NPMessage * msg1 = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
return ToSharedI2NPMessage (msg1);
}
}
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID)
{
I2NPMessage * msg = NewI2NPMessage (len);
size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
msg->offset += gatewayMsgOffset;
msg->len += gatewayMsgOffset;
memcpy (msg->GetPayload (), buf, len);
msg->len += len;
msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message
len = msg->GetLength ();
msg->offset -= gatewayMsgOffset;
uint8_t * payload = msg->GetPayload ();
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
msg->FillI2NPMessageHeader (eI2NPTunnelGateway); // gateway message
return msg;
}
size_t GetI2NPMessageLength (const uint8_t * msg)
{
return bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE;
}
void HandleI2NPMessage (uint8_t * msg, size_t len)
{
uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET];
uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET);
LogPrint ("I2NP msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
uint8_t * buf = msg + I2NP_HEADER_SIZE;
int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET);
switch (typeID)
{
case eI2NPVariableTunnelBuild:
LogPrint ("VariableTunnelBuild");
HandleVariableTunnelBuildMsg (msgID, buf, size);
break;
case eI2NPVariableTunnelBuildReply:
LogPrint ("VariableTunnelBuildReply");
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
break;
case eI2NPTunnelBuild:
LogPrint ("TunnelBuild");
HandleTunnelBuildMsg (buf, size);
break;
case eI2NPTunnelBuildReply:
LogPrint ("TunnelBuildReply");
// TODO:
break;
default:
LogPrint ("Unexpected message ", (int)typeID);
}
}
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
switch (msg->GetTypeID ())
{
case eI2NPTunnelData:
LogPrint ("TunnelData");
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
case eI2NPTunnelGateway:
LogPrint ("TunnelGateway");
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
case eI2NPGarlic:
{
LogPrint ("Garlic");
if (msg->from)
{
if (msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg);
else
LogPrint (eLogInfo, "Local destination for garlic doesn't exist anymore");
}
else
i2p::context.ProcessGarlicMessage (msg);
break;
}
case eI2NPDatabaseStore:
case eI2NPDatabaseSearchReply:
case eI2NPDatabaseLookup:
// forward to netDb
i2p::data::netdb.PostI2NPMsg (msg);
break;
case eI2NPDeliveryStatus:
{
LogPrint ("DeliveryStatus");
if (msg->from && msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
else
i2p::context.ProcessDeliveryStatusMessage (msg);
break;
}
case eI2NPVariableTunnelBuild:
case eI2NPVariableTunnelBuildReply:
case eI2NPTunnelBuild:
case eI2NPTunnelBuildReply:
// forward to tunnel thread
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
default:
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
}
}
}
I2NPMessagesHandler::~I2NPMessagesHandler ()
{
Flush ();
}
void I2NPMessagesHandler::PutNextMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
switch (msg->GetTypeID ())
{
case eI2NPTunnelData:
m_TunnelMsgs.push_back (msg);
break;
case eI2NPTunnelGateway:
m_TunnelGatewayMsgs.push_back (msg);
break;
default:
HandleI2NPMessage (msg);
}
}
}
void I2NPMessagesHandler::Flush ()
{
if (!m_TunnelMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
m_TunnelMsgs.clear ();
}
if (!m_TunnelGatewayMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
m_TunnelGatewayMsgs.clear ();
}
}
}

252
core/I2NPProtocol.h Normal file
View file

@ -0,0 +1,252 @@
#ifndef I2NP_PROTOCOL_H__
#define I2NP_PROTOCOL_H__
#include <inttypes.h>
#include <string.h>
#include <set>
#include <memory>
#include <cryptopp/sha.h>
#include "util/I2PEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
namespace i2p
{
// I2NP header
const size_t I2NP_HEADER_TYPEID_OFFSET = 0;
const size_t I2NP_HEADER_MSGID_OFFSET = I2NP_HEADER_TYPEID_OFFSET + 1;
const size_t I2NP_HEADER_EXPIRATION_OFFSET = I2NP_HEADER_MSGID_OFFSET + 4;
const size_t I2NP_HEADER_SIZE_OFFSET = I2NP_HEADER_EXPIRATION_OFFSET + 8;
const size_t I2NP_HEADER_CHKS_OFFSET = I2NP_HEADER_SIZE_OFFSET + 2;
const size_t I2NP_HEADER_SIZE = I2NP_HEADER_CHKS_OFFSET + 1;
// I2NP short header
const size_t I2NP_SHORT_HEADER_TYPEID_OFFSET = 0;
const size_t I2NP_SHORT_HEADER_EXPIRATION_OFFSET = I2NP_SHORT_HEADER_TYPEID_OFFSET + 1;
const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4;
// Tunnel Gateway header
const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0;
const size_t TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET = TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET + 4;
const size_t TUNNEL_GATEWAY_HEADER_SIZE = TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET + 2;
// DeliveryStatus
const size_t DELIVERY_STATUS_MSGID_OFFSET = 0;
const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
// DatabaseStore
const size_t DATABASE_STORE_KEY_OFFSET = 0;
const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
const size_t DATABASE_STORE_REPLY_TOKEN_OFFSET = DATABASE_STORE_TYPE_OFFSET + 1;
const size_t DATABASE_STORE_HEADER_SIZE = DATABASE_STORE_REPLY_TOKEN_OFFSET + 4;
// TunnelBuild
const size_t TUNNEL_BUILD_RECORD_SIZE = 528;
//BuildRequestRecordClearText
const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0;
const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16;
const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1;
const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222;
// BuildRequestRecordEncrypted
const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0;
const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16;
// BuildResponseRecord
const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0;
const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32;
const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495;
const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE;
enum I2NPMessageType
{
eI2NPDatabaseStore = 1,
eI2NPDatabaseLookup = 2,
eI2NPDatabaseSearchReply = 3,
eI2NPDeliveryStatus = 10,
eI2NPGarlic = 11,
eI2NPTunnelData = 18,
eI2NPTunnelGateway = 19,
eI2NPData = 20,
eI2NPTunnelBuild = 21,
eI2NPTunnelBuildReply = 22,
eI2NPVariableTunnelBuild = 23,
eI2NPVariableTunnelBuildReply = 24
};
const int NUM_TUNNEL_BUILD_RECORDS = 8;
// DatabaseLookup flags
const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01;
const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02;
const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C;
const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0;
const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100
const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000
const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100
const int MAX_NUM_TRANSIT_TUNNELS = 2500;
namespace tunnel
{
class InboundTunnel;
class TunnelPool;
}
const size_t I2NP_MAX_MESSAGE_SIZE = 32768;
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
struct I2NPMessage
{
uint8_t * buf;
size_t len, offset, maxLen;
std::shared_ptr<i2p::tunnel::InboundTunnel> from;
I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
// header accessors
uint8_t * GetHeader () { return GetBuffer (); };
const uint8_t * GetHeader () const { return GetBuffer (); };
void SetTypeID (uint8_t typeID) { GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = typeID; };
uint8_t GetTypeID () const { return GetHeader ()[I2NP_HEADER_TYPEID_OFFSET]; };
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
void UpdateSize () { SetSize (GetPayloadLength ()); };
void SetChks (uint8_t chks) { GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = chks; };
void UpdateChks ()
{
uint8_t hash[32];
CryptoPP::SHA256().CalculateDigest(hash, GetPayload (), GetPayloadLength ());
GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = hash[0];
}
// payload
uint8_t * GetPayload () { return GetBuffer () + I2NP_HEADER_SIZE; };
const uint8_t * GetPayload () const { return GetBuffer () + I2NP_HEADER_SIZE; };
uint8_t * GetBuffer () { return buf + offset; };
const uint8_t * GetBuffer () const { return buf + offset; };
size_t GetLength () const { return len - offset; };
size_t GetPayloadLength () const { return GetLength () - I2NP_HEADER_SIZE; };
void Align (size_t alignment)
{
if (len + alignment > maxLen) return;
size_t rem = ((size_t)GetBuffer ()) % alignment;
if (rem)
{
offset += (alignment - rem);
len += (alignment - rem);
}
}
I2NPMessage& operator=(const I2NPMessage& other)
{
memcpy (buf + offset, other.buf + other.offset, other.GetLength ());
len = offset + other.GetLength ();
from = other.from;
maxLen = other.maxLen;
return *this;
}
// for SSU only
uint8_t * GetSSUHeader () { return buf + offset + I2NP_HEADER_SIZE - I2NP_SHORT_HEADER_SIZE; };
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
{
const uint8_t * ssu = GetSSUHeader ();
GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET]; // typeid
SetMsgID (msgID);
SetExpiration (bufbe32toh (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET)*1000LL);
SetSize (len - offset - I2NP_HEADER_SIZE);
SetChks (0);
}
uint32_t ToSSU () // return msgID
{
uint8_t header[I2NP_HEADER_SIZE];
memcpy (header, GetHeader (), I2NP_HEADER_SIZE);
uint8_t * ssu = GetSSUHeader ();
ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET] = header[I2NP_HEADER_TYPEID_OFFSET]; // typeid
htobe32buf (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET, bufbe64toh (header + I2NP_HEADER_EXPIRATION_OFFSET)/1000LL);
len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET);
return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET);
}
void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0);
void RenewI2NPMessageHeader ();
};
template<int sz>
struct I2NPMessageBuffer: public I2NPMessage
{
I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
uint8_t m_Buffer[sz + 16] = {};
};
I2NPMessage * NewI2NPMessage ();
I2NPMessage * NewI2NPShortMessage ();
I2NPMessage * NewI2NPMessage (size_t len);
void DeleteI2NPMessage (I2NPMessage * msg);
std::shared_ptr<I2NPMessage> ToSharedI2NPMessage (I2NPMessage * msg);
I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0);
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag);
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint32_t replyToken = 0);
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
void HandleTunnelBuildMsg (uint8_t * buf, size_t len);
I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf);
I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ();
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
size_t GetI2NPMessageLength (const uint8_t * msg);
void HandleI2NPMessage (uint8_t * msg, size_t len);
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
class I2NPMessagesHandler
{
public:
~I2NPMessagesHandler ();
void PutNextMessage (std::shared_ptr<I2NPMessage> msg);
void Flush ();
private:
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
}
#endif

566
core/Identity.cpp Normal file
View file

@ -0,0 +1,566 @@
#include <time.h>
#include <stdio.h>
#include <cryptopp/sha.h>
#include <cryptopp/osrng.h>
#include <cryptopp/dsa.h>
#include "util/base64.h"
#include "crypto/CryptoConst.h"
#include "crypto/ElGamal.h"
#include "RouterContext.h"
#include "Identity.h"
#include "util/I2PEndian.h"
namespace i2p
{
namespace data
{
Identity& Identity::operator=(const Keys& keys)
{
// copy public and signing keys together
memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey));
memset (&certificate, 0, sizeof (certificate));
return *this;
}
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE;
}
IdentHash Identity::Hash () const
{
IdentHash hash;
CryptoPP::SHA256().CalculateDigest(hash, publicKey, DEFAULT_IDENTITY_SIZE);
return hash;
}
IdentityEx::IdentityEx ():
m_Verifier (nullptr), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
}
IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type)
{
memcpy (m_StandardIdentity.publicKey, publicKey, sizeof (m_StandardIdentity.publicKey));
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
size_t excessLen = 0;
uint8_t * excessBuf = nullptr;
switch (type)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
{
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
{
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
break;
}
default:
LogPrint ("Signing key type ", (int)type, " is not supported");
}
m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length
// fill certificate
m_StandardIdentity.certificate.type = CERTIFICATE_TYPE_KEY;
m_StandardIdentity.certificate.length = htobe16 (m_ExtendedLen);
// fill extended buffer
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
htobe16buf (m_ExtendedBuffer, type);
htobe16buf (m_ExtendedBuffer + 2, CRYPTO_KEY_TYPE_ELGAMAL);
if (excessLen && excessBuf)
{
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
delete[] excessBuf;
}
// calculate ident hash
uint8_t * buf = new uint8_t[GetFullLen ()];
ToBuffer (buf, GetFullLen ());
CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ());
delete[] buf;
}
else // DSA-SHA1
{
memcpy (m_StandardIdentity.signingKey, signingKey, sizeof (m_StandardIdentity.signingKey));
memset (&m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate));
m_IdentHash = m_StandardIdentity.Hash ();
m_ExtendedLen = 0;
m_ExtendedBuffer = nullptr;
}
CreateVerifier ();
}
IdentityEx::IdentityEx (const uint8_t * buf, size_t len):
m_Verifier (nullptr), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
FromBuffer (buf, len);
}
IdentityEx::IdentityEx (const IdentityEx& other):
m_Verifier (nullptr), m_ExtendedBuffer (nullptr)
{
*this = other;
}
IdentityEx::~IdentityEx ()
{
delete m_Verifier;
delete[] m_ExtendedBuffer;
}
IdentityEx& IdentityEx::operator=(const IdentityEx& other)
{
memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
m_IdentHash = other.m_IdentHash;
delete[] m_ExtendedBuffer;
m_ExtendedLen = other.m_ExtendedLen;
if (m_ExtendedLen > 0)
{
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
}
else
m_ExtendedBuffer = nullptr;
delete m_Verifier;
m_Verifier = nullptr;
return *this;
}
IdentityEx& IdentityEx::operator=(const Identity& standard)
{
m_StandardIdentity = standard;
m_IdentHash = m_StandardIdentity.Hash ();
delete[] m_ExtendedBuffer;
m_ExtendedBuffer = nullptr;
m_ExtendedLen = 0;
delete m_Verifier;
m_Verifier = nullptr;
return *this;
}
size_t IdentityEx::FromBuffer (const uint8_t * buf, size_t len)
{
if (len < DEFAULT_IDENTITY_SIZE)
{
LogPrint (eLogError, "Identity buffer length ", len, " is too small");
return 0;
}
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
delete[] m_ExtendedBuffer;
if (m_StandardIdentity.certificate.length)
{
m_ExtendedLen = be16toh (m_StandardIdentity.certificate.length);
if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
{
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
}
else
{
LogPrint (eLogError, "Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE);
return 0;
}
}
else
{
m_ExtendedLen = 0;
m_ExtendedBuffer = nullptr;
}
CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ());
delete m_Verifier;
m_Verifier = nullptr;
return GetFullLen ();
}
size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const
{
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
if (m_ExtendedLen > 0 && m_ExtendedBuffer)
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
return GetFullLen ();
}
size_t IdentityEx::FromBase64(const std::string& s)
{
uint8_t buf[1024];
auto len = i2p::util::Base64ToByteStream (s.c_str(), s.length(), buf, 1024);
return FromBuffer (buf, len);
}
std::string IdentityEx::ToBase64 () const
{
uint8_t buf[1024];
char str[1536];
size_t l = ToBuffer (buf, 1024);
size_t l1 = i2p::util::ByteStreamToBase64 (buf, l, str, 1536);
str[l1] = 0;
return std::string (str);
}
size_t IdentityEx::GetSigningPublicKeyLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetPublicKeyLen ();
return 128;
}
size_t IdentityEx::GetSigningPrivateKeyLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetPrivateKeyLen ();
return GetSignatureLen ()/2;
}
size_t IdentityEx::GetSignatureLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetSignatureLen ();
return 40;
}
bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->Verify (buf, len, signature);
return false;
}
SigningKeyType IdentityEx::GetSigningKeyType () const
{
if (m_StandardIdentity.certificate.type == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
return bufbe16toh (m_ExtendedBuffer); // signing key
return SIGNING_KEY_TYPE_DSA_SHA1;
}
CryptoKeyType IdentityEx::GetCryptoKeyType () const
{
if (m_StandardIdentity.certificate.type == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
return CRYPTO_KEY_TYPE_ELGAMAL;
}
void IdentityEx::CreateVerifier () const
{
auto keyType = GetSigningKeyType ();
switch (keyType)
{
case SIGNING_KEY_TYPE_DSA_SHA1:
m_Verifier = new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
{
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
m_Verifier = new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
m_Verifier = new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
uint8_t signingKey[i2p::crypto::ECDSAP521_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
m_Verifier = new i2p::crypto::ECDSAP521Verifier (signingKey);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
{
uint8_t signingKey[i2p::crypto::RSASHA2562048_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
m_Verifier = new i2p::crypto:: RSASHA2562048Verifier (signingKey);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
{
uint8_t signingKey[i2p::crypto::RSASHA3843072_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
m_Verifier = new i2p::crypto:: RSASHA3843072Verifier (signingKey);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
{
uint8_t signingKey[i2p::crypto::RSASHA5124096_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
m_Verifier = new i2p::crypto:: RSASHA5124096Verifier (signingKey);
break;
}
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
{
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
m_Verifier = new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding);
break;
}
default:
LogPrint ("Signing key type ", (int)keyType, " is not supported");
}
}
void IdentityEx::DropVerifier ()
{
auto verifier = m_Verifier;
m_Verifier = nullptr; // TODO: make this atomic
delete verifier;
}
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
{
m_Public = Identity (keys);
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public.GetSigningPrivateKeyLen ());
delete m_Signer;
m_Signer = nullptr;
CreateSigner ();
return *this;
}
PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other)
{
m_Public = other.m_Public;
memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public.GetSigningPrivateKeyLen ());
delete m_Signer;
m_Signer = nullptr;
CreateSigner ();
return *this;
}
size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len)
{
size_t ret = m_Public.FromBuffer (buf, len);
memcpy (m_PrivateKey, buf + ret, 256); // private key always 256
ret += 256;
size_t signingPrivateKeySize = m_Public.GetSigningPrivateKeyLen ();
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
ret += signingPrivateKeySize;
delete m_Signer;
m_Signer = nullptr;
CreateSigner ();
return ret;
}
size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const
{
size_t ret = m_Public.ToBuffer (buf, len);
memcpy (buf + ret, m_PrivateKey, 256); // private key always 256
ret += 256;
size_t signingPrivateKeySize = m_Public.GetSigningPrivateKeyLen ();
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
ret += signingPrivateKeySize;
return ret;
}
size_t PrivateKeys::FromBase64(const std::string& s)
{
uint8_t * buf = new uint8_t[s.length ()];
size_t l = i2p::util::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
size_t ret = FromBuffer (buf, l);
delete[] buf;
return ret;
}
std::string PrivateKeys::ToBase64 () const
{
uint8_t * buf = new uint8_t[GetFullLen ()];
char * str = new char[GetFullLen ()*2];
size_t l = ToBuffer (buf, GetFullLen ());
size_t l1 = i2p::util::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
str[l1] = 0;
delete[] buf;
std::string ret(str);
delete[] str;
return ret;
}
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Signer)
m_Signer->Sign (i2p::context.GetRandomNumberGenerator (), buf, len, signature);
}
void PrivateKeys::CreateSigner ()
{
switch (m_Public.GetSigningKeyType ())
{
case SIGNING_KEY_TYPE_DSA_SHA1:
m_Signer = new i2p::crypto::DSASigner (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
m_Signer = new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
m_Signer = new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
m_Signer = new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
m_Signer = new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
m_Signer = new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
m_Signer = new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey);
break;
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
m_Signer = new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey);
break;
default:
LogPrint ("Signing key type ", (int)m_Public.GetSigningKeyType (), " is not supported");
}
}
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type)
{
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
PrivateKeys keys;
auto& rnd = i2p::context.GetRandomNumberGenerator ();
// signature
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
switch (type)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
i2p::crypto::CreateECDSAP256RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
i2p::crypto::CreateECDSAP384RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
i2p::crypto::CreateECDSAP521RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
default:
LogPrint ("Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
}
// encryption
uint8_t publicKey[256];
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
dh.GenerateKeyPair(rnd, keys.m_PrivateKey, publicKey);
// identity
keys.m_Public = IdentityEx (publicKey, signingPublicKey, type);
keys.CreateSigner ();
return keys;
}
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
}
Keys CreateRandomKeys ()
{
Keys keys;
auto& rnd = i2p::context.GetRandomNumberGenerator ();
// encryption
i2p::crypto::GenerateElGamalKeyPair(rnd, keys.privateKey, keys.publicKey);
// signing
i2p::crypto::CreateDSARandomKeys (rnd, keys.signingPrivateKey, keys.signingKey);
return keys;
}
IdentHash CreateRoutingKey (const IdentHash& ident)
{
uint8_t buf[41]; // ident + yyyymmdd
memcpy (buf, (const uint8_t *)ident, 32);
time_t t = time (nullptr);
struct tm tm;
#ifdef _WIN32
gmtime_s(&tm, &t);
sprintf_s((char *)(buf + 32), 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#else
gmtime_r(&t, &tm);
sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#endif
IdentHash key;
CryptoPP::SHA256().CalculateDigest((uint8_t *)key, buf, 40);
return key;
}
XORMetric operator^(const IdentHash& key1, const IdentHash& key2)
{
XORMetric m;
const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL ();
m.metric_ll[0] = hash1[0] ^ hash2[0];
m.metric_ll[1] = hash1[1] ^ hash2[1];
m.metric_ll[2] = hash1[2] ^ hash2[2];
m.metric_ll[3] = hash1[3] ^ hash2[3];
return m;
}
}
}

275
core/Identity.h Normal file
View file

@ -0,0 +1,275 @@
#ifndef IDENTITY_H__
#define IDENTITY_H__
#include <inttypes.h>
#include <string.h>
#include <string>
#include <memory>
#include "util/base64.h"
#include "crypto/ElGamal.h"
#include "crypto/Signature.h"
namespace i2p
{
namespace data
{
template<int sz>
class Tag
{
public:
Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); };
Tag (const Tag<sz>& ) = default;
#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it
Tag (Tag<sz>&& ) = default;
#endif
Tag () = default;
Tag<sz>& operator= (const Tag<sz>& ) = default;
#ifndef _WIN32
Tag<sz>& operator= (Tag<sz>&& ) = default;
#endif
uint8_t * operator()() { return m_Buf; };
const uint8_t * operator()() const { return m_Buf; };
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
const uint64_t * GetLL () const { return ll; };
bool operator== (const Tag<sz>& other) const { return !memcmp (m_Buf, other.m_Buf, sz); };
bool operator< (const Tag<sz>& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; };
bool IsZero () const
{
for (int i = 0; i < sz/8; i++)
if (ll[i]) return false;
return true;
}
std::string ToBase64 () const
{
char str[sz*2];
int l = i2p::util::ByteStreamToBase64 (m_Buf, sz, str, sz*2);
str[l] = 0;
return std::string (str);
}
std::string ToBase32 () const
{
char str[sz*2];
int l = i2p::util::ByteStreamToBase32 (m_Buf, sz, str, sz*2);
str[l] = 0;
return std::string (str);
}
void FromBase32 (const std::string& s)
{
i2p::util::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
void FromBase64 (const std::string& s)
{
i2p::util::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
private:
union // 8 bytes alignment
{
uint8_t m_Buf[sz];
uint64_t ll[sz/8];
};
};
typedef Tag<32> IdentHash;
#pragma pack(1)
struct Keys
{
uint8_t privateKey[256];
uint8_t signingPrivateKey[20];
uint8_t publicKey[256];
uint8_t signingKey[128];
};
const uint8_t CERTIFICATE_TYPE_NULL = 0;
const uint8_t CERTIFICATE_TYPE_HASHCASH = 1;
const uint8_t CERTIFICATE_TYPE_HIDDEN = 2;
const uint8_t CERTIFICATE_TYPE_SIGNED = 3;
const uint8_t CERTIFICATE_TYPE_MULTIPLE = 4;
const uint8_t CERTIFICATE_TYPE_KEY = 5;
struct Identity
{
uint8_t publicKey[256];
uint8_t signingKey[128];
struct
{
uint8_t type;
uint16_t length;
} certificate;
Identity () = default;
Identity (const Keys& keys) { *this = keys; };
Identity& operator=(const Keys& keys);
size_t FromBuffer (const uint8_t * buf, size_t len);
IdentHash Hash () const;
};
#pragma pack()
Keys CreateRandomKeys ();
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA512_P521 = 3;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
typedef uint16_t SigningKeyType;
typedef uint16_t CryptoKeyType;
class IdentityEx
{
public:
IdentityEx ();
IdentityEx (const uint8_t * publicKey, const uint8_t * signingKey,
SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
IdentityEx (const uint8_t * buf, size_t len);
IdentityEx (const IdentityEx& other);
~IdentityEx ();
IdentityEx& operator=(const IdentityEx& other);
IdentityEx& operator=(const Identity& standard);
size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(const std::string& s);
std::string ToBase64 () const;
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
const IdentHash& GetIdentHash () const { return m_IdentHash; };
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
size_t GetSigningPublicKeyLen () const;
size_t GetSigningPrivateKeyLen () const;
size_t GetSignatureLen () const;
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
SigningKeyType GetSigningKeyType () const;
CryptoKeyType GetCryptoKeyType () const;
void DropVerifier (); // to save memory
private:
void CreateVerifier () const;
private:
Identity m_StandardIdentity;
IdentHash m_IdentHash;
mutable i2p::crypto::Verifier * m_Verifier;
size_t m_ExtendedLen;
uint8_t * m_ExtendedBuffer;
};
class PrivateKeys // for eepsites
{
public:
PrivateKeys (): m_Signer (nullptr) {};
PrivateKeys (const PrivateKeys& other): m_Signer (nullptr) { *this = other; };
PrivateKeys (const Keys& keys): m_Signer (nullptr) { *this = keys; };
PrivateKeys& operator=(const Keys& keys);
PrivateKeys& operator=(const PrivateKeys& other);
~PrivateKeys () { delete m_Signer; };
const IdentityEx& GetPublic () const { return m_Public; };
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
size_t GetFullLen () const { return m_Public.GetFullLen () + 256 + m_Public.GetSigningPrivateKeyLen (); };
size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(const std::string& s);
std::string ToBase64 () const;
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
private:
void CreateSigner ();
private:
IdentityEx m_Public;
uint8_t m_PrivateKey[256];
uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes
i2p::crypto::Signer * m_Signer;
};
// kademlia
struct XORMetric
{
union
{
uint8_t metric[32];
uint64_t metric_ll[4];
};
void SetMin () { memset (metric, 0, 32); };
void SetMax () { memset (metric, 0xFF, 32); };
bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
};
IdentHash CreateRoutingKey (const IdentHash& ident);
XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
// destination for delivery instuctions
class RoutingDestination
{
public:
RoutingDestination () {};
virtual ~RoutingDestination () {};
virtual const IdentHash& GetIdentHash () const = 0;
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
virtual bool IsDestination () const = 0; // for garlic
std::unique_ptr<const i2p::crypto::ElGamalEncryption>& GetElGamalEncryption () const
{
if (!m_ElGamalEncryption)
m_ElGamalEncryption.reset (new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ()));
return m_ElGamalEncryption;
}
private:
mutable std::unique_ptr<const i2p::crypto::ElGamalEncryption> m_ElGamalEncryption; // use lazy initialization
};
class LocalDestination
{
public:
virtual ~LocalDestination() {};
virtual const PrivateKeys& GetPrivateKeys () const = 0;
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
const IdentityEx& GetIdentity () const { return GetPrivateKeys ().GetPublic (); };
const IdentHash& GetIdentHash () const { return GetIdentity ().GetIdentHash (); };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
GetPrivateKeys ().Sign (buf, len, signature);
};
};
}
}
#endif

157
core/LeaseSet.cpp Normal file
View file

@ -0,0 +1,157 @@
#include <string.h>
#include "util/I2PEndian.h"
#include <cryptopp/dsa.h>
#include <cryptopp/osrng.h>
#include "crypto/CryptoConst.h"
#include "util/Log.h"
#include "util/Timestamp.h"
#include "NetDb.h"
#include "tunnel/TunnelPool.h"
#include "LeaseSet.h"
namespace i2p
{
namespace data
{
LeaseSet::LeaseSet (const uint8_t * buf, size_t len):
m_IsValid (true)
{
m_Buffer = new uint8_t[len];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer ();
}
LeaseSet::LeaseSet (const i2p::tunnel::TunnelPool& pool):
m_IsValid (true)
{
// header
const i2p::data::LocalDestination * localDestination = pool.GetLocalDestination ();
if (!localDestination)
{
m_Buffer = nullptr;
m_BufferLen = 0;
m_IsValid = false;
LogPrint (eLogError, "Destination for local LeaseSet doesn't exist");
return;
}
m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE];
m_BufferLen = localDestination->GetIdentity ().ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE);
memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256);
m_BufferLen += 256;
auto signingKeyLen = localDestination->GetIdentity ().GetSigningPublicKeyLen ();
memset (m_Buffer + m_BufferLen, 0, signingKeyLen);
m_BufferLen += signingKeyLen;
auto tunnels = pool.GetInboundTunnels (5); // 5 tunnels maximum
m_Buffer[m_BufferLen] = tunnels.size (); // num leases
m_BufferLen++;
// leases
CryptoPP::AutoSeededRandomPool rnd;
for (auto it: tunnels)
{
memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32);
m_BufferLen += 32; // gateway id
htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ());
m_BufferLen += 4; // tunnel id
uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
ts += rnd.GenerateWord32 (0, 5); // + random milliseconds
htobe64buf (m_Buffer + m_BufferLen, ts);
m_BufferLen += 8; // end date
}
// signature
localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen);
m_BufferLen += localDestination->GetIdentity ().GetSignatureLen ();
LogPrint ("Local LeaseSet of ", tunnels.size (), " leases created");
ReadFromBuffer ();
}
void LeaseSet::Update (const uint8_t * buf, size_t len)
{
m_Leases.clear ();
if (len > m_BufferLen)
{
auto oldBuffer = m_Buffer;
m_Buffer = new uint8_t[len];
delete[] oldBuffer;
}
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer ();
}
void LeaseSet::ReadFromBuffer ()
{
size_t size = m_Identity.FromBuffer (m_Buffer, m_BufferLen);
memcpy (m_EncryptionKey, m_Buffer + size, 256);
size += 256; // encryption key
size += m_Identity.GetSigningPublicKeyLen (); // unused signing key
uint8_t num = m_Buffer[size];
size++; // num
LogPrint ("LeaseSet num=", (int)num);
if (!num) m_IsValid = false;
// process leases
const uint8_t * leases = m_Buffer + size;
for (int i = 0; i < num; i++)
{
Lease lease;
lease.tunnelGateway = leases;
leases += 32; // gateway
lease.tunnelID = bufbe32toh (leases);
leases += 4; // tunnel ID
lease.endDate = bufbe64toh (leases);
leases += 8; // end date
m_Leases.push_back (lease);
// check if lease's gateway is in our netDb
if (!netdb.FindRouter (lease.tunnelGateway))
{
// if not found request it
LogPrint (eLogInfo, "Lease's tunnel gateway not found. Requested");
netdb.RequestDestination (lease.tunnelGateway);
}
}
// verify
if (!m_Identity.Verify (m_Buffer, leases - m_Buffer, leases))
{
LogPrint (eLogWarning, "LeaseSet verification failed");
m_IsValid = false;
}
}
const std::vector<Lease> LeaseSet::GetNonExpiredLeases (bool withThreshold) const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
std::vector<Lease> leases;
for (auto& it: m_Leases)
{
auto endDate = it.endDate;
if (!withThreshold)
endDate -= i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000;
if (ts < endDate)
leases.push_back (it);
}
return leases;
}
bool LeaseSet::HasExpiredLeases () const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto& it: m_Leases)
if (ts >= it.endDate) return true;
return false;
}
bool LeaseSet::HasNonExpiredLeases () const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto& it: m_Leases)
if (ts < it.endDate) return true;
return false;
}
}
}

74
core/LeaseSet.h Normal file
View file

@ -0,0 +1,74 @@
#ifndef LEASE_SET_H__
#define LEASE_SET_H__
#include <inttypes.h>
#include <string.h>
#include <vector>
#include "Identity.h"
namespace i2p
{
namespace tunnel
{
class TunnelPool;
}
namespace data
{
struct Lease
{
IdentHash tunnelGateway;
uint32_t tunnelID;
uint64_t endDate;
bool operator< (const Lease& other) const
{
if (endDate != other.endDate)
return endDate > other.endDate;
else
return tunnelID < other.tunnelID;
}
};
const int MAX_LS_BUFFER_SIZE = 3072;
class LeaseSet: public RoutingDestination
{
public:
LeaseSet (const uint8_t * buf, size_t len);
LeaseSet (const i2p::tunnel::TunnelPool& pool);
~LeaseSet () { delete[] m_Buffer; };
void Update (const uint8_t * buf, size_t len);
const IdentityEx& GetIdentity () const { return m_Identity; };
const uint8_t * GetBuffer () const { return m_Buffer; };
size_t GetBufferLen () const { return m_BufferLen; };
bool IsValid () const { return m_IsValid; };
// implements RoutingDestination
const IdentHash& GetIdentHash () const { return m_Identity.GetIdentHash (); };
const std::vector<Lease>& GetLeases () const { return m_Leases; };
const std::vector<Lease> GetNonExpiredLeases (bool withThreshold = true) const;
bool HasExpiredLeases () const;
bool HasNonExpiredLeases () const;
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
bool IsDestination () const { return true; };
private:
void ReadFromBuffer ();
private:
bool m_IsValid;
std::vector<Lease> m_Leases;
IdentityEx m_Identity;
uint8_t m_EncryptionKey[256];
uint8_t * m_Buffer;
size_t m_BufferLen;
};
}
}
#endif

1007
core/NetDb.cpp Normal file

File diff suppressed because it is too large Load diff

106
core/NetDb.h Normal file
View file

@ -0,0 +1,106 @@
#ifndef NETDB_H__
#define NETDB_H__
#include <inttypes.h>
#include <set>
#include <map>
#include <list>
#include <string>
#include <thread>
#include <mutex>
#include <boost/filesystem.hpp>
#include "util/Queue.h"
#include "I2NPProtocol.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
#include "tunnel/Tunnel.h"
#include "tunnel/TunnelPool.h"
#include "Reseed.h"
#include "NetDbRequests.h"
namespace i2p
{
namespace data
{
class NetDb
{
public:
NetDb ();
~NetDb ();
void Start ();
void Stop ();
void AddRouterInfo (const uint8_t * buf, int len);
void AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len);
void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
std::shared_ptr<RouterInfo> FindRouter (const IdentHash& ident) const;
std::shared_ptr<LeaseSet> FindLeaseSet (const IdentHash& destination) const;
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr);
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter () const;
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
void SetUnreachable (const IdentHash& ident, bool unreachable);
void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
void Reseed ();
// for web interface
int GetNumRouters () const { return m_RouterInfos.size (); };
int GetNumFloodfills () const { return m_Floodfills.size (); };
int GetNumLeaseSets () const { return m_LeaseSets.size (); };
private:
bool CreateNetDb(boost::filesystem::path directory);
void Load ();
void SaveUpdated ();
void Run (); // exploratory thread
void Explore (int numDestinations);
void Publish ();
void ManageLeaseSets ();
void ManageRequests ();
template<typename Filter>
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
private:
std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets;
mutable std::mutex m_RouterInfosMutex;
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;
mutable std::mutex m_FloodfillsMutex;
std::list<std::shared_ptr<RouterInfo> > m_Floodfills;
bool m_IsRunning;
std::thread * m_Thread;
i2p::util::Queue<std::shared_ptr<const I2NPMessage> > m_Queue; // of I2NPDatabaseStoreMsg
Reseeder * m_Reseeder;
friend class NetDbRequests;
NetDbRequests m_Requests;
static const char m_NetDbPath[];
};
extern NetDb netdb;
}
}
#endif

149
core/NetDbRequests.cpp Normal file
View file

@ -0,0 +1,149 @@
#include "util/Log.h"
#include "I2NPProtocol.h"
#include "transport/Transports.h"
#include "NetDb.h"
#include "NetDbRequests.h"
namespace i2p
{
namespace data
{
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
{
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
&m_ExcludedPeers);
m_ExcludedPeers.insert (router->GetIdentHash ());
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
{
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
void RequestedDestination::ClearExcludedPeers ()
{
m_ExcludedPeers.clear ();
}
void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
{
if (m_RequestComplete)
{
m_RequestComplete (r);
m_RequestComplete = nullptr;
}
}
void RequestedDestination::Fail ()
{
if (m_RequestComplete)
{
m_RequestComplete (nullptr);
m_RequestComplete = nullptr;
}
}
void NetDbRequests::Start ()
{
}
void NetDbRequests::Stop ()
{
m_RequestedDestinations.clear ();
}
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
{
// request RouterInfo directly
auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
dest->SetRequestComplete (requestComplete);
{
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
if (!m_RequestedDestinations.insert (std::make_pair (destination,
std::shared_ptr<RequestedDestination> (dest))).second) // not inserted
return nullptr;
}
return dest;
}
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
{
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
{
if (r)
it->second->Success (r);
else
it->second->Fail ();
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
m_RequestedDestinations.erase (it);
}
}
std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
{
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
return it->second;
return nullptr;
}
void NetDbRequests::ManageRequests ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
{
auto& dest = it->second;
bool done = false;
if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
{
if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
{
auto count = dest->GetExcludedPeers ().size ();
if (!dest->IsExploratory () && count < 7)
{
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
auto outbound = pool->GetNextOutboundTunnel ();
auto inbound = pool->GetNextInboundTunnel ();
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
if (nextFloodfill && outbound && inbound)
outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0,
dest->CreateRequestMessage (nextFloodfill, inbound));
else
{
done = true;
if (!inbound) LogPrint (eLogWarning, "No inbound tunnels");
if (!outbound) LogPrint (eLogWarning, "No outbound tunnels");
if (!nextFloodfill) LogPrint (eLogWarning, "No more floodfills");
}
}
else
{
if (!dest->IsExploratory ())
LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
done = true;
}
}
}
else // delete obsolete request
done = true;
if (done)
it = m_RequestedDestinations.erase (it);
else
it++;
}
}
}
}

69
core/NetDbRequests.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef NETDB_REQUESTS_H__
#define NETDB_REQUESTS_H__
#include <memory>
#include <set>
#include <map>
#include "Identity.h"
#include "RouterInfo.h"
namespace i2p
{
namespace data
{
class RequestedDestination
{
public:
typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
RequestedDestination (const IdentHash& destination, bool isExploratory = false):
m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
const IdentHash& GetDestination () const { return m_Destination; };
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
void ClearExcludedPeers ();
bool IsExploratory () const { return m_IsExploratory; };
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
uint64_t GetCreationTime () const { return m_CreationTime; };
std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
void Success (std::shared_ptr<RouterInfo> r);
void Fail ();
private:
IdentHash m_Destination;
bool m_IsExploratory;
std::set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime;
RequestComplete m_RequestComplete;
};
class NetDbRequests
{
public:
void Start ();
void Stop ();
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
void ManageRequests ();
private:
std::mutex m_RequestedDestinationsMutex;
std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
};
}
}
#endif

213
core/Profiling.cpp Normal file
View file

@ -0,0 +1,213 @@
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "util/base64.h"
#include "util/util.h"
#include "Profiling.h"
namespace i2p
{
namespace data
{
RouterProfile::RouterProfile (const IdentHash& identHash):
m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTimesTaken (0), m_NumTimesRejected (0)
{
}
boost::posix_time::ptime RouterProfile::GetTime () const
{
return boost::posix_time::second_clock::local_time();
}
void RouterProfile::UpdateTime ()
{
m_LastUpdateTime = GetTime ();
}
void RouterProfile::Save ()
{
// fill sections
boost::property_tree::ptree participation;
participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed);
participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined);
participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied);
boost::property_tree::ptree usage;
usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
// fill property tree
boost::property_tree::ptree pt;
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
// save to file
auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY;
if (!boost::filesystem::exists (path))
{
// Create directory is necessary
if (!boost::filesystem::create_directory (path))
{
LogPrint (eLogError, "Failed to create directory ", path);
return;
}
const char * chars = i2p::util::GetBase64SubstitutionTable (); // 64 bytes
for (int i = 0; i < 64; i++)
{
auto path1 = path / (std::string ("p") + chars[i]);
if (!boost::filesystem::create_directory (path1))
{
LogPrint (eLogError, "Failed to create directory ", path1);
return;
}
}
}
std::string base64 = m_IdentHash.ToBase64 ();
path = path / (std::string ("p") + base64[0]);
auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt");
try
{
boost::property_tree::write_ini (filename.string (), pt);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ());
}
}
void RouterProfile::Load ()
{
std::string base64 = m_IdentHash.ToBase64 ();
auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY;
path /= std::string ("p") + base64[0];
auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt");
if (boost::filesystem::exists (filename))
{
boost::property_tree::ptree pt;
try
{
boost::property_tree::read_ini (filename.string (), pt);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ());
return;
}
try
{
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (t.length () > 0)
m_LastUpdateTime = boost::posix_time::time_from_string (t);
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT)
{
try
{
// read participations
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
}
catch (boost::property_tree::ptree_bad_path& ex)
{
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_PARTICIPATION);
}
try
{
// read usage
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
}
catch (boost::property_tree::ptree_bad_path& ex)
{
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE);
}
}
else
*this = RouterProfile (m_IdentHash);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't read profile ", base64, " :", ex.what ());
}
}
}
void RouterProfile::TunnelBuildResponse (uint8_t ret)
{
UpdateTime ();
if (ret > 0)
m_NumTunnelsDeclined++;
else
m_NumTunnelsAgreed++;
}
void RouterProfile::TunnelNonReplied ()
{
m_NumTunnelsNonReplied++;
UpdateTime ();
}
bool RouterProfile::IsLowPartcipationRate () const
{
return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
}
bool RouterProfile::IsLowReplyRate () const
{
auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined;
return m_NumTunnelsNonReplied > 10*(total + 1);
}
bool RouterProfile::IsBad ()
{
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
{
// reset profile
m_NumTunnelsAgreed = 0;
m_NumTunnelsDeclined = 0;
m_NumTunnelsNonReplied = 0;
isBad = false;
}
if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++;
return isBad;
}
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash)
{
auto profile = std::make_shared<RouterProfile> (identHash);
profile->Load (); // if possible
return profile;
}
void DeleteObsoleteProfiles ()
{
int num = 0;
auto ts = boost::posix_time::second_clock::local_time();
boost::filesystem::path p (i2p::util::filesystem::GetDataDir()/PEER_PROFILES_DIRECTORY);
if (boost::filesystem::exists (p))
{
boost::filesystem::directory_iterator end;
for (boost::filesystem::directory_iterator it (p); it != end; ++it)
{
if (boost::filesystem::is_directory (it->status()))
{
for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1)
{
auto lastModified = boost::posix_time::from_time_t (boost::filesystem::last_write_time (it1->path ()));
if ((ts - lastModified).hours () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
{
boost::filesystem::remove (it1->path ());
num++;
}
}
}
}
}
LogPrint (eLogInfo, num, " obsolete profiles deleted");
}
}
}

69
core/Profiling.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef PROFILING_H__
#define PROFILING_H__
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Identity.h"
namespace i2p
{
namespace data
{
const char PEER_PROFILES_DIRECTORY[] = "peerProfiles";
const char PEER_PROFILE_PREFIX[] = "profile-";
// sections
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
// params
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied";
const char PEER_PROFILE_USAGE_TAKEN[] = "taken";
const char PEER_PROFILE_USAGE_REJECTED[] = "rejected";
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
class RouterProfile
{
public:
RouterProfile (const IdentHash& identHash);
RouterProfile& operator= (const RouterProfile& ) = default;
void Save ();
void Load ();
bool IsBad ();
void TunnelBuildResponse (uint8_t ret);
void TunnelNonReplied ();
private:
boost::posix_time::ptime GetTime () const;
void UpdateTime ();
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
bool IsLowPartcipationRate () const;
bool IsLowReplyRate () const;
private:
IdentHash m_IdentHash;
boost::posix_time::ptime m_LastUpdateTime;
// participation
uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined;
uint32_t m_NumTunnelsNonReplied;
// usage
uint32_t m_NumTimesTaken;
uint32_t m_NumTimesRejected;
};
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
void DeleteObsoleteProfiles ();
}
}
#endif

932
core/Reseed.cpp Normal file
View file

@ -0,0 +1,932 @@
#include <string.h>
#include <fstream>
#include <sstream>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <cryptopp/asn.h>
#include <cryptopp/base64.h>
#include <cryptopp/crc.h>
#include <cryptopp/hmac.h>
#include <cryptopp/zinflate.h>
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <cryptopp/arc4.h>
#include "util/I2PEndian.h"
#include "Reseed.h"
#include "util/Log.h"
#include "Identity.h"
#include "crypto/CryptoConst.h"
#include "NetDb.h"
#include "util/util.h"
namespace i2p
{
namespace data
{
static std::vector<std::string> httpReseedHostList = {
"http://netdb.i2p2.no/", // only SU3 (v2) support
"http://i2p-netdb.innovatio.no/",
"http://193.150.121.66/netDb/"
};
static std::vector<std::string> httpsReseedHostList = {
// "https://193.150.121.66/netDb/", // unstable
// "https://i2p-netdb.innovatio.no/",// Vuln to POODLE
"https://netdb.i2p2.no/", // Only SU3 (v2) support
"https://reseed.i2p-projekt.de/", // Only HTTPS
//"https://cowpuncher.drollette.com/netdb/", // returns error
"https://netdb.rows.io:444/",
"https://uk.reseed.i2p2.no:444/"
// following hosts are fine but don't support AES256
/*"https://i2p.mooo.com/netDb/",
"https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support
"https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support
"https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support*/
};
Reseeder::Reseeder()
{
}
Reseeder::~Reseeder()
{
}
bool Reseeder::reseedNow()
{
// This method is deprecated
try
{
std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())];
LogPrint("Reseeding from ", reseedHost);
std::string content = i2p::util::http::httpRequest(reseedHost);
if (content == "")
{
LogPrint("Reseed failed");
return false;
}
boost::regex e("<\\s*A\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"", boost::regex::normal | boost::regbase::icase);
boost::sregex_token_iterator i(content.begin(), content.end(), e, 1);
boost::sregex_token_iterator j;
//TODO: Ugly code, try to clean up.
//TODO: Try to reduce N number of variables
std::string name;
std::string routerInfo;
std::string tmpUrl;
std::string filename;
std::string ignoreFileSuffix = ".su3";
boost::filesystem::path root = i2p::util::filesystem::GetDataDir();
while (i != j)
{
name = *i++;
if (name.find(ignoreFileSuffix)!=std::string::npos)
continue;
LogPrint("Downloading ", name);
tmpUrl = reseedHost;
tmpUrl.append(name);
routerInfo = i2p::util::http::httpRequest(tmpUrl);
if (routerInfo.size()==0)
continue;
filename = root.string();
#ifndef _WIN32
filename += "/netDb/r";
#else
filename += "\\netDb\\r";
#endif
filename += name.at(11); // first char in id
#ifndef _WIN32
filename.append("/");
#else
filename.append("\\");
#endif
filename.append(name.c_str());
std::ofstream outfile (filename, std::ios::binary);
outfile << routerInfo;
outfile.close();
}
return true;
}
catch (std::exception& ex)
{
//TODO: error reporting
return false;
}
return false;
}
int Reseeder::ReseedNowSU3 ()
{
CryptoPP::AutoSeededRandomPool rnd;
auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1 + httpsReseedHostList.size () - 1);
std::string reseedHost = (ind < httpReseedHostList.size()) ? httpReseedHostList[ind] :
httpsReseedHostList[ind - httpReseedHostList.size()];
return ReseedFromSU3 (reseedHost, ind >= httpReseedHostList.size());
}
int Reseeder::ReseedFromSU3 (const std::string& host, bool https)
{
std::string url = host + "i2pseeds.su3";
LogPrint (eLogInfo, "Dowloading SU3 from ", host);
std::string su3 = https ? HttpsRequest (url) : i2p::util::http::httpRequest (url);
if (su3.length () > 0)
{
std::stringstream s(su3);
return ProcessSU3Stream (s);
}
else
{
LogPrint (eLogWarning, "SU3 download failed");
return 0;
}
}
int Reseeder::ProcessSU3File (const char * filename)
{
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open ())
return ProcessSU3Stream (s);
else
{
LogPrint (eLogError, "Can't open file ", filename);
return 0;
}
}
const char SU3_MAGIC_NUMBER[]="I2Psu3";
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
int Reseeder::ProcessSU3Stream (std::istream& s)
{
char magicNumber[7];
s.read (magicNumber, 7); // magic number and zero byte 6
if (strcmp (magicNumber, SU3_MAGIC_NUMBER))
{
LogPrint (eLogError, "Unexpected SU3 magic number");
return 0;
}
s.seekg (1, std::ios::cur); // su3 file format version
SigningKeyType signatureType;
s.read ((char *)&signatureType, 2); // signature type
signatureType = be16toh (signatureType);
uint16_t signatureLength;
s.read ((char *)&signatureLength, 2); // signature length
signatureLength = be16toh (signatureLength);
s.seekg (1, std::ios::cur); // unused
uint8_t versionLength;
s.read ((char *)&versionLength, 1); // version length
s.seekg (1, std::ios::cur); // unused
uint8_t signerIDLength;
s.read ((char *)&signerIDLength, 1); // signer ID length
uint64_t contentLength;
s.read ((char *)&contentLength, 8); // content length
contentLength = be64toh (contentLength);
s.seekg (1, std::ios::cur); // unused
uint8_t fileType;
s.read ((char *)&fileType, 1); // file type
if (fileType != 0x00) // zip file
{
LogPrint (eLogError, "Can't handle file type ", (int)fileType);
return 0;
}
s.seekg (1, std::ios::cur); // unused
uint8_t contentType;
s.read ((char *)&contentType, 1); // content type
if (contentType != 0x03) // reseed data
{
LogPrint (eLogError, "Unexpected content type ", (int)contentType);
return 0;
}
s.seekg (12, std::ios::cur); // unused
s.seekg (versionLength, std::ios::cur); // skip version
char signerID[256];
s.read (signerID, signerIDLength); // signerID
signerID[signerIDLength] = 0;
//try to verify signature
auto it = m_SigningKeys.find (signerID);
if (it != m_SigningKeys.end ())
{
// TODO: implement all signature types
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096)
{
size_t pos = s.tellg ();
size_t tbsLen = pos + contentLength;
uint8_t * tbs = new uint8_t[tbsLen];
s.seekg (0, std::ios::beg);
s.read ((char *)tbs, tbsLen);
uint8_t * signature = new uint8_t[signatureLength];
s.read ((char *)signature, signatureLength);
// RSA-raw
i2p::crypto::RSASHA5124096RawVerifier verifier(it->second);
verifier.Update (tbs, tbsLen);
if (!verifier.Verify (signature))
LogPrint (eLogWarning, "SU3 signature verification failed");
delete[] signature;
delete[] tbs;
s.seekg (pos, std::ios::beg);
}
else
LogPrint (eLogWarning, "Signature type ", signatureType, " is not supported");
}
else
LogPrint (eLogWarning, "Certificate for ", signerID, " not loaded");
// handle content
int numFiles = 0;
size_t contentPos = s.tellg ();
while (!s.eof ())
{
uint32_t signature;
s.read ((char *)&signature, 4);
signature = le32toh (signature);
if (signature == ZIP_HEADER_SIGNATURE)
{
// next local file
s.seekg (2, std::ios::cur); // version
uint16_t bitFlag;
s.read ((char *)&bitFlag, 2);
bitFlag = le16toh (bitFlag);
uint16_t compressionMethod;
s.read ((char *)&compressionMethod, 2);
compressionMethod = le16toh (compressionMethod);
s.seekg (4, std::ios::cur); // skip fields we don't care about
uint32_t compressedSize, uncompressedSize;
uint8_t crc32[4];
s.read ((char *)crc32, 4);
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize);
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
uint16_t fileNameLength, extraFieldLength;
s.read ((char *)&fileNameLength, 2);
fileNameLength = le16toh (fileNameLength);
s.read ((char *)&extraFieldLength, 2);
extraFieldLength = le16toh (extraFieldLength);
char localFileName[255];
s.read (localFileName, fileNameLength);
localFileName[fileNameLength] = 0;
s.seekg (extraFieldLength, std::ios::cur);
// take care about data desriptor if presented
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
{
size_t pos = s.tellg ();
if (!FindZipDataDescriptor (s))
{
LogPrint (eLogError, "SU3 archive data descriptor not found");
return numFiles;
}
s.read ((char *)crc32, 4);
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
// now we know compressed and uncompressed size
s.seekg (pos, std::ios::beg); // back to compressed data
}
LogPrint (eLogDebug, "Proccessing file ", localFileName, " ", compressedSize, " bytes");
if (!compressedSize)
{
LogPrint (eLogWarning, "Unexpected size 0. Skipped");
continue;
}
uint8_t * compressed = new uint8_t[compressedSize];
s.read ((char *)compressed, compressedSize);
if (compressionMethod) // we assume Deflate
{
CryptoPP::Inflator decompressor;
decompressor.Put (compressed, compressedSize);
decompressor.MessageEnd();
if (decompressor.MaxRetrievable () <= uncompressedSize)
{
uint8_t * uncompressed = new uint8_t[uncompressedSize];
decompressor.Get (uncompressed, uncompressedSize);
if (CryptoPP::CRC32().VerifyDigest (crc32, uncompressed, uncompressedSize))
{
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
numFiles++;
}
else
LogPrint (eLogError, "CRC32 verification failed");
delete[] uncompressed;
}
else
LogPrint (eLogError, "Actual uncompressed size ", decompressor.MaxRetrievable (), " exceed ", uncompressedSize, " from header");
}
else // no compression
{
i2p::data::netdb.AddRouterInfo (compressed, compressedSize);
numFiles++;
}
delete[] compressed;
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
}
else
{
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
LogPrint (eLogWarning, "Missing zip central directory header");
break; // no more files
}
size_t end = s.tellg ();
if (end - contentPos >= contentLength)
break; // we are beyond contentLength
}
return numFiles;
}
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 };
bool Reseeder::FindZipDataDescriptor (std::istream& s)
{
size_t nextInd = 0;
while (!s.eof ())
{
uint8_t nextByte;
s.read ((char *)&nextByte, 1);
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd])
{
nextInd++;
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE))
return true;
}
else
nextInd = 0;
}
return false;
}
const char CERTIFICATE_HEADER[] = "-----BEGIN CERTIFICATE-----";
const char CERTIFICATE_FOOTER[] = "-----END CERTIFICATE-----";
void Reseeder::LoadCertificate (const std::string& filename)
{
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0, std::ios::end);
size_t len = s.tellg ();
s.seekg (0, std::ios::beg);
char buf[2048];
s.read (buf, len);
std::string cert (buf, len);
// assume file in pem format
auto pos1 = cert.find (CERTIFICATE_HEADER);
auto pos2 = cert.find (CERTIFICATE_FOOTER);
if (pos1 == std::string::npos || pos2 == std::string::npos)
{
LogPrint (eLogError, "Malformed certificate file");
return;
}
pos1 += strlen (CERTIFICATE_HEADER);
pos2 -= pos1;
std::string base64 = cert.substr (pos1, pos2);
CryptoPP::ByteQueue queue;
CryptoPP::Base64Decoder decoder; // regular base64 rather than I2P
decoder.Attach (new CryptoPP::Redirector (queue));
decoder.Put ((const uint8_t *)base64.data(), base64.length());
decoder.MessageEnd ();
LoadCertificate (queue);
}
else
LogPrint (eLogError, "Can't open certificate file ", filename);
}
std::string Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue)
{
// extract X.509
CryptoPP::BERSequenceDecoder x509Cert (queue);
CryptoPP::BERSequenceDecoder tbsCert (x509Cert);
// version
uint32_t ver;
CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED);
CryptoPP::BERDecodeUnsigned<uint32_t>(context, ver, CryptoPP::INTEGER);
// serial
CryptoPP::Integer serial;
serial.BERDecode(tbsCert);
// signature
CryptoPP::BERSequenceDecoder signature (tbsCert);
signature.SkipAll();
// issuer
std::string name;
CryptoPP::BERSequenceDecoder issuer (tbsCert);
{
CryptoPP::BERSetDecoder c (issuer); c.SkipAll();
CryptoPP::BERSetDecoder st (issuer); st.SkipAll();
CryptoPP::BERSetDecoder l (issuer); l.SkipAll();
CryptoPP::BERSetDecoder o (issuer); o.SkipAll();
CryptoPP::BERSetDecoder ou (issuer); ou.SkipAll();
CryptoPP::BERSetDecoder cn (issuer);
{
CryptoPP::BERSequenceDecoder attributes (cn);
{
CryptoPP::BERGeneralDecoder ident(attributes, CryptoPP::OBJECT_IDENTIFIER);
ident.SkipAll ();
CryptoPP::BERDecodeTextString (attributes, name, CryptoPP::UTF8_STRING);
}
}
}
issuer.SkipAll();
// validity
CryptoPP::BERSequenceDecoder validity (tbsCert);
validity.SkipAll();
// subject
CryptoPP::BERSequenceDecoder subject (tbsCert);
subject.SkipAll();
// public key
CryptoPP::BERSequenceDecoder publicKey (tbsCert);
{
CryptoPP::BERSequenceDecoder ident (publicKey);
ident.SkipAll ();
CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING);
key.Skip (1); // FIXME: probably bug in crypto++
CryptoPP::BERSequenceDecoder keyPair (key);
CryptoPP::Integer n;
n.BERDecode (keyPair);
if (name.length () > 0)
{
PublicKey value;
n.Encode (value, 512);
m_SigningKeys[name] = value;
}
else
LogPrint (eLogWarning, "Unknown issuer. Skipped");
}
publicKey.SkipAll();
tbsCert.SkipAll();
x509Cert.SkipAll();
return name;
}
void Reseeder::LoadCertificates ()
{
boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed";
if (!boost::filesystem::exists (reseedDir))
{
LogPrint (eLogWarning, "Reseed certificates not loaded. ", reseedDir, " doesn't exist");
return;
}
int numCertificates = 0;
boost::filesystem::directory_iterator end; // empty
for (boost::filesystem::directory_iterator it (reseedDir); it != end; ++it)
{
if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt")
{
LoadCertificate (it->path ().string ());
numCertificates++;
}
}
LogPrint (eLogInfo, numCertificates, " certificates loaded");
}
std::string Reseeder::HttpsRequest (const std::string& address)
{
i2p::util::http::url u(address);
if (u.port_ == 80) u.port_ = 443;
TlsSession session (u.host_, u.port_);
if (session.IsEstablished ())
{
// send request
std::stringstream ss;
ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n";
session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ());
// read response
std::stringstream rs;
while (session.Receive (rs))
;
return i2p::util::http::GetHttpContent (rs);
}
else
return "";
}
//-------------------------------------------------------------
template<class Hash>
class TlsCipherMAC: public TlsCipher
{
public:
TlsCipherMAC (const uint8_t * keys): m_Seqn (0)
{
memcpy (m_MacKey, keys, Hash::DIGESTSIZE);
}
void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac)
{
uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2)
htobe64buf (header, m_Seqn);
header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2
htobe16buf (header + 11, len);
CryptoPP::HMAC<Hash> hmac (m_MacKey, Hash::DIGESTSIZE);
hmac.Update (header, 13);
hmac.Update (buf, len);
hmac.Final (mac);
m_Seqn++;
}
private:
uint64_t m_Seqn;
uint8_t m_MacKey[Hash::DIGESTSIZE]; // client
};
template<class Hash>
class TlsCipher_AES_256_CBC: public TlsCipherMAC<Hash>
{
public:
TlsCipher_AES_256_CBC (const uint8_t * keys): TlsCipherMAC<Hash> (keys)
{
m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE);
m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32);
}
size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out)
{
size_t size = 0;
m_Rnd.GenerateBlock (out, 16); // iv
size += 16;
m_Encryption.SetIV (out);
memcpy (out + size, in, len);
size += len;
memcpy (out + size, mac, Hash::DIGESTSIZE);
size += Hash::DIGESTSIZE;
uint8_t paddingSize = size + 1;
paddingSize &= 0x0F; // %16
if (paddingSize > 0) paddingSize = 16 - paddingSize;
memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size
size += paddingSize + 1;
m_Encryption.Encrypt (out + 16, size - 16, out + 16);
return size;
}
size_t Decrypt (uint8_t * buf, size_t len) // payload is buf + 16
{
m_Decryption.SetIV (buf);
m_Decryption.Decrypt (buf + 16, len - 16, buf + 16);
return len - 16 - Hash::DIGESTSIZE - buf[len -1] - 1; // IV(16), mac(32 or 20) and padding
}
size_t GetIVSize () const { return 16; };
private:
CryptoPP::AutoSeededRandomPool m_Rnd;
i2p::crypto::CBCEncryption m_Encryption;
i2p::crypto::CBCDecryption m_Decryption;
};
class TlsCipher_RC4_SHA: public TlsCipherMAC<CryptoPP::SHA1>
{
public:
TlsCipher_RC4_SHA (const uint8_t * keys): TlsCipherMAC (keys)
{
m_Encryption.SetKey (keys + 40, 16); // 20 + 20
m_Decryption.SetKey (keys + 56, 16); // 20 + 20 + 16
}
size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out)
{
memcpy (out, in, len);
memcpy (out + len, mac, 20);
m_Encryption.ProcessData (out, out, len + 20);
return len + 20;
}
size_t Decrypt (uint8_t * buf, size_t len)
{
m_Decryption.ProcessData (buf, buf, len);
return len - 20;
}
private:
CryptoPP::Weak1::ARC4 m_Encryption, m_Decryption;
};
TlsSession::TlsSession (const std::string& host, int port):
m_IsEstablished (false), m_Cipher (nullptr)
{
m_Site.connect(host, boost::lexical_cast<std::string>(port));
if (m_Site.good ())
Handshake ();
else
LogPrint (eLogError, "Can't connect to ", host, ":", port);
}
TlsSession::~TlsSession ()
{
delete m_Cipher;
}
void TlsSession::Handshake ()
{
static uint8_t clientHello[] =
{
0x16, // handshake
0x03, 0x03, // version (TLS 1.2)
0x00, 0x33, // length of handshake
// handshake
0x01, // handshake type (client hello)
0x00, 0x00, 0x2F, // length of handshake payload
// client hello
0x03, 0x03, // highest version supported (TLS 1.2)
0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36,
0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80,
0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00,
0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes
0x00, // session id length
0x00, 0x06, // chiper suites length
0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256
0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA
0x00, 0x05, // RSA_WITH_RC4_128_SHA
0x01, // compression methods length
0x00, // no compression
0x00, 0x00 // extensions length
};
static uint8_t changeCipherSpecs[] =
{
0x14, // change cipher specs
0x03, 0x03, // version (TLS 1.2)
0x00, 0x01, // length
0x01 // type
};
// send ClientHello
m_Site.write ((char *)clientHello, sizeof (clientHello));
m_FinishedHash.Update (clientHello + 5, sizeof (clientHello) - 5);
// read ServerHello
uint8_t type;
m_Site.read ((char *)&type, 1);
uint16_t version;
m_Site.read ((char *)&version, 2);
uint16_t length;
m_Site.read ((char *)&length, 2);
length = be16toh (length);
char * serverHello = new char[length];
m_Site.read (serverHello, length);
m_FinishedHash.Update ((uint8_t *)serverHello, length);
uint8_t serverRandom[32];
if (serverHello[0] == 0x02) // handshake type server hello
memcpy (serverRandom, serverHello + 6, 32);
else
LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]);
uint8_t sessionIDLen = serverHello[38]; // 6 + 32
char * cipherSuite = serverHello + 39 + sessionIDLen;
if (cipherSuite[1] == 0x3D || cipherSuite[1] == 0x35 || cipherSuite[1] == 0x05)
m_IsEstablished = true;
else
LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]);
// read Certificate
m_Site.read ((char *)&type, 1);
m_Site.read ((char *)&version, 2);
m_Site.read ((char *)&length, 2);
length = be16toh (length);
char * certificate = new char[length];
m_Site.read (certificate, length);
m_FinishedHash.Update ((uint8_t *)certificate, length);
CryptoPP::RSA::PublicKey publicKey;
// 0 - handshake type
// 1 - 3 - handshake payload length
// 4 - 6 - length of array of certificates
// 7 - 9 - length of certificate
if (certificate[0] == 0x0B) // handshake type certificate
publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10);
else
LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]);
// read ServerHelloDone
m_Site.read ((char *)&type, 1);
m_Site.read ((char *)&version, 2);
m_Site.read ((char *)&length, 2);
length = be16toh (length);
char * serverHelloDone = new char[length];
m_Site.read (serverHelloDone, length);
m_FinishedHash.Update ((uint8_t *)serverHelloDone, length);
if (serverHelloDone[0] != 0x0E) // handshake type hello done
LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]);
// our turn now
// generate secret key
uint8_t secret[48];
secret[0] = 3; secret[1] = 3; // version
CryptoPP::AutoSeededRandomPool rnd;
rnd.GenerateBlock (secret + 2, 46); // 46 random bytes
// encrypt RSA
CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey);
size_t encryptedLen = encryptor.CiphertextLength (48); // number of bytes for encrypted 48 bytes, usually 256 (2048 bits key)
uint8_t * encrypted = new uint8_t[encryptedLen + 2]; // + 2 bytes for length
htobe16buf (encrypted, encryptedLen); // first two bytes means length
encryptor.Encrypt (rnd, secret, 48, encrypted + 2);
// send ClientKeyExchange
// 0x10 - handshake type "client key exchange"
SendHandshakeMsg (0x10, encrypted, encryptedLen + 2);
delete[] encrypted;
// send ChangeCipherSpecs
m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs));
// calculate master secret
uint8_t random[64];
memcpy (random, clientHello + 11, 32);
memcpy (random + 32, serverRandom, 32);
PRF (secret, "master secret", random, 64, 48, m_MasterSecret);
// create keys
memcpy (random, serverRandom, 32);
memcpy (random + 32, clientHello + 11, 32);
uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32)
PRF (m_MasterSecret, "key expansion", random, 64, 128, keys);
// create cipher
if (cipherSuite[1] == 0x3D)
{
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA256");
m_Cipher = new TlsCipher_AES_256_CBC<CryptoPP::SHA256> (keys);
}
else if (cipherSuite[1] == 0x35)
{
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA");
m_Cipher = new TlsCipher_AES_256_CBC<CryptoPP::SHA1> (keys);
}
else
{
// TODO:
if (cipherSuite[1] == 0x05)
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_RC4_128_SHA");
m_Cipher = new TlsCipher_RC4_SHA (keys);
}
// send finished
SendFinishedMsg ();
// read ChangeCipherSpecs
uint8_t changeCipherSpecs1[6];
m_Site.read ((char *)changeCipherSpecs1, 6);
// read finished
m_Site.read ((char *)&type, 1);
m_Site.read ((char *)&version, 2);
m_Site.read ((char *)&length, 2);
length = be16toh (length);
char * finished1 = new char[length];
m_Site.read (finished1, length);
m_Cipher->Decrypt ((uint8_t *)finished1, length); // for streaming ciphers
delete[] finished1;
delete[] serverHello;
delete[] certificate;
delete[] serverHelloDone;
}
void TlsSession::SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len)
{
uint8_t handshakeHeader[9];
handshakeHeader[0] = 0x16; // handshake
handshakeHeader[1] = 0x03; handshakeHeader[2] = 0x03; // version is always TLS 1.2 (3,3)
htobe16buf (handshakeHeader + 3, len + 4); // length of payload
//payload starts
handshakeHeader[5] = handshakeType; // handshake type
handshakeHeader[6] = 0; // highest byte of payload length is always zero
htobe16buf (handshakeHeader + 7, len); // length of data
m_Site.write ((char *)handshakeHeader, 9);
m_FinishedHash.Update (handshakeHeader + 5, 4); // only payload counts
m_Site.write ((char *)data, len);
m_FinishedHash.Update (data, len);
}
void TlsSession::SendFinishedMsg ()
{
// 0x16 handshake
// 0x03, 0x03 version (TLS 1.2)
// 2 bytes length of handshake (80 or 64 bytes)
// handshake (encrypted)
// unencrypted context
// 0x14 handshake type (finished)
// 0x00, 0x00, 0x0C length of handshake payload
// 12 bytes of verified data
uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80];
finishedPayload[0] = 0x14; // handshake type (finished)
finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes
m_FinishedHash.Final (finishedHashDigest);
PRF (m_MasterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4);
uint8_t mac[32];
m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac);
size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload);
uint8_t finished[5];
finished[0] = 0x16; // handshake
finished[1] = 0x03; finished[2] = 0x03; // version is always TLS 1.2 (3,3)
htobe16buf (finished + 3, encryptedPayloadSize); // length of payload
m_Site.write ((char *)finished, sizeof (finished));
m_Site.write ((char *)encryptedPayload, encryptedPayloadSize);
}
void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen,
size_t len, uint8_t * buf)
{
// secret is assumed 48 bytes
// random is not more than 64 bytes
CryptoPP::HMAC<CryptoPP::SHA256> hmac (secret, 48);
uint8_t seed[96]; size_t seedLen;
seedLen = strlen (label);
memcpy (seed, label, seedLen);
memcpy (seed + seedLen, random, randomLen);
seedLen += randomLen;
size_t offset = 0;
uint8_t a[128];
hmac.CalculateDigest (a, seed, seedLen);
while (offset < len)
{
memcpy (a + 32, seed, seedLen);
hmac.CalculateDigest (buf + offset, a, seedLen + 32);
offset += 32;
hmac.CalculateDigest (a, a, 32);
}
}
CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len)
{
CryptoPP::ByteQueue queue;
queue.Put (certificate, len);
queue.MessageEnd ();
// extract X.509
CryptoPP::BERSequenceDecoder x509Cert (queue);
CryptoPP::BERSequenceDecoder tbsCert (x509Cert);
// version
uint32_t ver;
CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED);
CryptoPP::BERDecodeUnsigned<uint32_t>(context, ver, CryptoPP::INTEGER);
// serial
CryptoPP::Integer serial;
serial.BERDecode(tbsCert);
// signature
CryptoPP::BERSequenceDecoder signature (tbsCert);
signature.SkipAll();
// issuer
CryptoPP::BERSequenceDecoder issuer (tbsCert);
issuer.SkipAll();
// validity
CryptoPP::BERSequenceDecoder validity (tbsCert);
validity.SkipAll();
// subject
CryptoPP::BERSequenceDecoder subject (tbsCert);
subject.SkipAll();
// public key
CryptoPP::BERSequenceDecoder publicKey (tbsCert);
CryptoPP::BERSequenceDecoder ident (publicKey);
ident.SkipAll ();
CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING);
key.Skip (1); // FIXME: probably bug in crypto++
CryptoPP::BERSequenceDecoder keyPair (key);
CryptoPP::Integer n, e;
n.BERDecode (keyPair);
e.BERDecode (keyPair);
CryptoPP::RSA::PublicKey ret;
ret.Initialize (n, e);
return ret;
}
void TlsSession::Send (const uint8_t * buf, size_t len)
{
uint8_t * out = new uint8_t[len + 64 + 5]; // 64 = 32 mac + 16 iv + upto 16 padding, 5 = header
out[0] = 0x17; // application data
out[1] = 0x03; out[2] = 0x03; // version
uint8_t mac[32];
m_Cipher->CalculateMAC (0x17, buf, len, mac);
size_t encryptedLen = m_Cipher->Encrypt (buf, len, mac, out + 5);
htobe16buf (out + 3, encryptedLen);
m_Site.write ((char *)out, encryptedLen + 5);
delete[] out;
}
bool TlsSession::Receive (std::ostream& rs)
{
if (m_Site.eof ()) return false;
uint8_t type; uint16_t version, length;
m_Site.read ((char *)&type, 1);
m_Site.read ((char *)&version, 2);
m_Site.read ((char *)&length, 2);
length = be16toh (length);
uint8_t * buf = new uint8_t[length];
m_Site.read ((char *)buf, length);
size_t decryptedLen = m_Cipher->Decrypt (buf, length);
rs.write ((char *)buf + m_Cipher->GetIVSize (), decryptedLen);
delete[] buf;
return true;
}
}
}

95
core/Reseed.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef RESEED_H
#define RESEED_H
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cryptopp/osrng.h>
#include <cryptopp/rsa.h>
#include <boost/asio.hpp>
#include "Identity.h"
#include "crypto/aes.h"
namespace i2p
{
namespace data
{
class Reseeder
{
typedef Tag<512> PublicKey;
public:
Reseeder();
~Reseeder();
bool reseedNow(); // depreacted
int ReseedNowSU3 ();
void LoadCertificates ();
private:
void LoadCertificate (const std::string& filename);
std::string LoadCertificate (CryptoPP::ByteQueue& queue); // returns issuer's name
int ReseedFromSU3 (const std::string& host, bool https = false);
int ProcessSU3File (const char * filename);
int ProcessSU3Stream (std::istream& s);
bool FindZipDataDescriptor (std::istream& s);
std::string HttpsRequest (const std::string& address);
private:
std::map<std::string, PublicKey> m_SigningKeys;
};
class TlsCipher
{
public:
virtual ~TlsCipher () {};
virtual void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) = 0;
virtual size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) = 0;
virtual size_t Decrypt (uint8_t * buf, size_t len) = 0;
virtual size_t GetIVSize () const { return 0; }; // override for AES
};
class TlsSession
{
public:
TlsSession (const std::string& host, int port);
~TlsSession ();
void Send (const uint8_t * buf, size_t len);
bool Receive (std::ostream& rs);
bool IsEstablished () const { return m_IsEstablished; };
private:
void Handshake ();
void SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len);
void SendFinishedMsg ();
CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len);
void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen,
size_t len, uint8_t * buf);
private:
bool m_IsEstablished;
boost::asio::ip::tcp::iostream m_Site;
CryptoPP::SHA256 m_FinishedHash;
uint8_t m_MasterSecret[64]; // actual size is 48, but must be multiple of 32
TlsCipher * m_Cipher;
};
}
}
#endif

318
core/RouterContext.cpp Normal file
View file

@ -0,0 +1,318 @@
#include <fstream>
#include <cryptopp/dh.h>
#include <cryptopp/dsa.h>
#include <boost/lexical_cast.hpp>
#include "crypto/CryptoConst.h"
#include "RouterContext.h"
#include "util/Timestamp.h"
#include "I2NPProtocol.h"
#include "NetDb.h"
#include "util/util.h"
#include "version.h"
namespace i2p
{
RouterContext context;
RouterContext::RouterContext ():
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
m_StartupTime (0), m_Status (eRouterStatusOK )
{
}
void RouterContext::Init ()
{
m_StartupTime = i2p::util::GetSecondsSinceEpoch ();
if (!Load ())
CreateNewRouter ();
UpdateRouterInfo ();
}
void RouterContext::CreateNewRouter ()
{
m_Keys = i2p::data::CreateRandomKeys ();
SaveKeys ();
NewRouterInfo ();
}
void RouterContext::NewRouterInfo ()
{
i2p::data::RouterInfo routerInfo;
routerInfo.SetRouterIdentity (GetIdentity ());
int port = i2p::util::config::GetArg("-port", 0);
if (!port)
port = m_Rnd.GenerateWord32 (9111, 30777); // I2P network ports range
routerInfo.AddSSUAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port, routerInfo.GetIdentHash ());
routerInfo.AddNTCPAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port);
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC
routerInfo.SetProperty ("coreVersion", I2P_VERSION);
routerInfo.SetProperty ("netId", "2");
routerInfo.SetProperty ("router.version", I2P_VERSION);
routerInfo.SetProperty ("stat_uptime", "90m");
routerInfo.CreateBuffer (m_Keys);
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
}
void RouterContext::UpdateRouterInfo ()
{
m_RouterInfo.CreateBuffer (m_Keys);
m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO));
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
}
void RouterContext::UpdatePort (int port)
{
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
{
if (address.port != port)
{
address.port = port;
updated = true;
}
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
{
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
{
if (address.host != host && address.IsCompatible (host))
{
address.host = host;
updated = true;
}
}
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
UpdateRouterInfo ();
}
bool RouterContext::AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag)
{
bool ret = false;
auto address = routerInfo.GetSSUAddress ();
if (address)
{
ret = m_RouterInfo.AddIntroducer (address, tag);
if (ret)
UpdateRouterInfo ();
}
return ret;
}
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
if (m_RouterInfo.RemoveIntroducer (e))
UpdateRouterInfo ();
}
void RouterContext::SetFloodfill (bool floodfill)
{
m_IsFloodfill = floodfill;
if (floodfill)
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
else
{
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
// we don't publish number of routers and leaseset for non-floodfill
m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_LEASESETS);
m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_ROUTERS);
}
UpdateRouterInfo ();
}
void RouterContext::SetHighBandwidth ()
{
if (!m_RouterInfo.IsHighBandwidth ())
{
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth);
UpdateRouterInfo ();
}
}
void RouterContext::SetLowBandwidth ()
{
if (m_RouterInfo.IsHighBandwidth ())
{
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth);
UpdateRouterInfo ();
}
}
bool RouterContext::IsUnreachable () const
{
return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable;
}
void RouterContext::SetUnreachable ()
{
// set caps
m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B
// remove NTCP address
auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++)
{
if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{
addresses.erase (addresses.begin () + i);
break;
}
}
// delete previous introducers
for (auto& addr : addresses)
addr.introducers.clear ();
// update
UpdateRouterInfo ();
}
void RouterContext::SetReachable ()
{
// update caps
uint8_t caps = m_RouterInfo.GetCaps ();
caps &= ~i2p::data::RouterInfo::eUnreachable;
caps |= i2p::data::RouterInfo::eReachable;
caps |= i2p::data::RouterInfo::eSSUIntroducer;
if (m_IsFloodfill)
caps |= i2p::data::RouterInfo::eFloodfill;
m_RouterInfo.SetCaps (caps);
// insert NTCP back
auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++)
{
if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU)
{
// insert NTCP address with host/port form SSU
m_RouterInfo.AddNTCPAddress (addresses[i].host.to_string ().c_str (), addresses[i].port);
break;
}
}
// delete previous introducers
for (auto& addr : addresses)
addr.introducers.clear ();
// update
UpdateRouterInfo ();
}
void RouterContext::SetSupportsV6 (bool supportsV6)
{
if (supportsV6)
m_RouterInfo.EnableV6 ();
else
m_RouterInfo.DisableV6 ();
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
{
bool updated = false, found = false;
int port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
{
if (addr.host.is_v6 () && addr.transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{
if (addr.host != host)
{
addr.host = host;
updated = true;
}
found = true;
}
else
port = addr.port;
}
if (!found)
{
// create new address
m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port);
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint ("Our v6 MTU=", mtu);
if (mtu > 1472) mtu = 1472;
}
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
updated = true;
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateStats ()
{
if (m_IsFloodfill)
{
// update routers and leasesets
m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumLeaseSets ()));
m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumRouters ()));
UpdateRouterInfo ();
}
}
bool RouterContext::Load ()
{
std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ofstream::in);
if (!fk.is_open ()) return false;
i2p::data::Keys keys;
fk.read ((char *)&keys, sizeof (keys));
m_Keys = keys;
i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
m_RouterInfo.SetProperty ("router.version", I2P_VERSION);
if (IsUnreachable ())
SetReachable (); // we assume reachable until we discover firewall through peer tests
return true;
}
void RouterContext::SaveKeys ()
{
std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out);
i2p::data::Keys keys;
memcpy (keys.privateKey, m_Keys.GetPrivateKey (), sizeof (keys.privateKey));
memcpy (keys.signingPrivateKey, m_Keys.GetSigningPrivateKey (), sizeof (keys.signingPrivateKey));
auto& ident = GetIdentity ().GetStandardIdentity ();
memcpy (keys.publicKey, ident.publicKey, sizeof (keys.publicKey));
memcpy (keys.signingKey, ident.signingKey, sizeof (keys.signingKey));
fk.write ((char *)&keys, sizeof (keys));
}
std::shared_ptr<i2p::tunnel::TunnelPool> RouterContext::GetTunnelPool () const
{
return i2p::tunnel::tunnels.GetExploratoryPool ();
}
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
}
void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg);
}
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
}
uint32_t RouterContext::GetUptime () const
{
return i2p::util::GetSecondsSinceEpoch () - m_StartupTime;
}
}

107
core/RouterContext.h Normal file
View file

@ -0,0 +1,107 @@
#ifndef ROUTER_CONTEXT_H__
#define ROUTER_CONTEXT_H__
#include <inttypes.h>
#include <string>
#include <memory>
#include <mutex>
#include <boost/asio.hpp>
#include <cryptopp/dsa.h>
#include <cryptopp/osrng.h>
#include "Identity.h"
#include "RouterInfo.h"
#include "Garlic.h"
namespace i2p
{
const char ROUTER_INFO[] = "router.info";
const char ROUTER_KEYS[] = "router.keys";
const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes
const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets";
const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters";
enum RouterStatus
{
eRouterStatusOK = 0,
eRouterStatusTesting = 1,
eRouterStatusFirewalled = 2
};
class RouterContext: public i2p::garlic::GarlicDestination
{
public:
RouterContext ();
void Init ();
i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; };
std::shared_ptr<const i2p::data::RouterInfo> GetSharedRouterInfo () const
{
return std::shared_ptr<const i2p::data::RouterInfo> (&m_RouterInfo,
[](const i2p::data::RouterInfo *) {});
}
CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; };
uint32_t GetUptime () const;
uint32_t GetStartupTime () const { return m_StartupTime; };
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
RouterStatus GetStatus () const { return m_Status; };
void SetStatus (RouterStatus status) { m_Status = status; };
void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
bool AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag);
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
bool IsUnreachable () const;
void SetUnreachable ();
void SetReachable ();
bool IsFloodfill () const { return m_IsFloodfill; };
void SetFloodfill (bool floodfill);
void SetHighBandwidth ();
void SetLowBandwidth ();
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
void SetSupportsV6 (bool supportsV6);
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
void UpdateStats ();
// implements LocalDestination
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); };
const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ().GetStandardIdentity ().publicKey; };
void SetLeaseSetUpdated () {};
// implements GarlicDestination
std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet () { return nullptr; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
// override GarlicDestination
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
private:
void CreateNewRouter ();
void NewRouterInfo ();
void UpdateRouterInfo ();
bool Load ();
void SaveKeys ();
private:
i2p::data::RouterInfo m_RouterInfo;
i2p::data::PrivateKeys m_Keys;
CryptoPP::AutoSeededRandomPool m_Rnd;
uint64_t m_LastUpdateTime;
bool m_AcceptsTunnels, m_IsFloodfill;
uint64_t m_StartupTime; // in seconds since epoch
RouterStatus m_Status;
std::mutex m_GarlicMutex;
};
extern RouterContext context;
}
#endif

666
core/RouterInfo.cpp Normal file
View file

@ -0,0 +1,666 @@
#include <stdio.h>
#include <string.h>
#include "util/I2PEndian.h"
#include <fstream>
#include <boost/lexical_cast.hpp>
#include <cryptopp/sha.h>
#include <cryptopp/dsa.h>
#include "crypto/CryptoConst.h"
#include "util/base64.h"
#include "util/Timestamp.h"
#include "util/Log.h"
#include "RouterInfo.h"
#include "RouterContext.h"
namespace i2p
{
namespace data
{
RouterInfo::RouterInfo (const std::string& fullPath):
m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false),
m_SupportedTransports (0), m_Caps (0)
{
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
ReadFromFile ();
}
RouterInfo::RouterInfo (const uint8_t * buf, int len):
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
{
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (true);
}
RouterInfo::~RouterInfo ()
{
delete[] m_Buffer;
}
void RouterInfo::Update (const uint8_t * buf, int len)
{
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
m_IsUpdated = true;
m_IsUnreachable = false;
m_SupportedTransports = 0;
m_Caps = 0;
m_Addresses.clear ();
m_Properties.clear ();
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (true);
// don't delete buffer until save to file
}
void RouterInfo::SetRouterIdentity (const IdentityEx& identity)
{
m_RouterIdentity = identity;
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
}
bool RouterInfo::LoadFile ()
{
std::ifstream s(m_FullPath.c_str (), std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0,std::ios::end);
m_BufferLen = s.tellg ();
if (m_BufferLen < 40)
{
LogPrint(eLogError, "File", m_FullPath, " is malformed");
return false;
}
s.seekg(0, std::ios::beg);
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
s.read((char *)m_Buffer, m_BufferLen);
}
else
{
LogPrint (eLogError, "Can't open file ", m_FullPath);
return false;
}
return true;
}
void RouterInfo::ReadFromFile ()
{
if (LoadFile ())
ReadFromBuffer (false);
}
void RouterInfo::ReadFromBuffer (bool verifySignature)
{
size_t identityLen = m_RouterIdentity.FromBuffer (m_Buffer, m_BufferLen);
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
ReadFromStream (str);
if (verifySignature)
{
// verify signature
int l = m_BufferLen - m_RouterIdentity.GetSignatureLen ();
if (!m_RouterIdentity.Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l))
{
LogPrint (eLogError, "signature verification failed");
m_IsUnreachable = true;
}
m_RouterIdentity.DropVerifier ();
}
}
void RouterInfo::ReadFromStream (std::istream& s)
{
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
m_Timestamp = be64toh (m_Timestamp);
// read addresses
uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses));
bool introducers = false;
for (int i = 0; i < numAddresses; i++)
{
bool isValidAddress = true;
Address address;
s.read ((char *)&address.cost, sizeof (address.cost));
s.read ((char *)&address.date, sizeof (address.date));
char transportStyle[5];
ReadString (transportStyle, s);
if (!strcmp (transportStyle, "NTCP"))
address.transportStyle = eTransportNTCP;
else if (!strcmp (transportStyle, "SSU"))
address.transportStyle = eTransportSSU;
else
address.transportStyle = eTransportUnknown;
address.port = 0;
address.mtu = 0;
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size));
size = be16toh (size);
while (r < size)
{
char key[500], value[500];
r += ReadString (key, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!strcmp (key, "host"))
{
boost::system::error_code ecode;
address.host = boost::asio::ip::address::from_string (value, ecode);
if (ecode)
{
if (address.transportStyle == eTransportNTCP)
{
m_SupportedTransports |= eNTCPV4; // TODO:
address.addressString = value;
}
else
{
// TODO: resolve address for SSU
LogPrint (eLogWarning, "Unexpected SSU address ", value);
isValidAddress = false;
}
}
else
{
// add supported protocol
if (address.host.is_v4 ())
m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4;
else
m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6;
}
}
else if (!strcmp (key, "port"))
address.port = boost::lexical_cast<int>(value);
else if (!strcmp (key, "mtu"))
address.mtu = boost::lexical_cast<int>(value);
else if (!strcmp (key, "key"))
i2p::util::Base64ToByteStream (value, strlen (value), address.key, 32);
else if (!strcmp (key, "caps"))
ExtractCaps (value);
else if (key[0] == 'i')
{
// introducers
introducers = true;
size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
key[l-1] = 0;
if (index >= address.introducers.size ())
address.introducers.resize (index + 1);
Introducer& introducer = address.introducers.at (index);
if (!strcmp (key, "ihost"))
{
boost::system::error_code ecode;
introducer.iHost = boost::asio::ip::address::from_string (value, ecode);
}
else if (!strcmp (key, "iport"))
introducer.iPort = boost::lexical_cast<int>(value);
else if (!strcmp (key, "itag"))
introducer.iTag = boost::lexical_cast<uint32_t>(value);
else if (!strcmp (key, "ikey"))
i2p::util::Base64ToByteStream (value, strlen (value), introducer.iKey, 32);
}
}
if (isValidAddress)
m_Addresses.push_back(address);
}
// read peers
uint8_t numPeers;
s.read ((char *)&numPeers, sizeof (numPeers));
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
// read properties
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size));
size = be16toh (size);
while (r < size)
{
#ifdef _WIN32
char key[500], value[500];
// TODO: investigate why properties get read as one long string under Windows
// length should not be more than 44
#else
char key[50], value[50];
#endif
r += ReadString (key, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, s);
s.seekg (1, std::ios_base::cur); r++; // ;
m_Properties[key] = value;
// extract caps
if (!strcmp (key, "caps"))
ExtractCaps (value);
}
if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers))
SetUnreachable (true);
}
void RouterInfo::ExtractCaps (const char * value)
{
const char * cap = value;
while (*cap)
{
switch (*cap)
{
case CAPS_FLAG_FLOODFILL:
m_Caps |= Caps::eFloodfill;
break;
case CAPS_FLAG_HIGH_BANDWIDTH1:
case CAPS_FLAG_HIGH_BANDWIDTH2:
case CAPS_FLAG_HIGH_BANDWIDTH3:
m_Caps |= Caps::eHighBandwidth;
break;
case CAPS_FLAG_HIDDEN:
m_Caps |= Caps::eHidden;
break;
case CAPS_FLAG_REACHABLE:
m_Caps |= Caps::eReachable;
break;
case CAPS_FLAG_UNREACHABLE:
m_Caps |= Caps::eUnreachable;
break;
case CAPS_FLAG_SSU_TESTING:
m_Caps |= Caps::eSSUTesting;
break;
case CAPS_FLAG_SSU_INTRODUCER:
m_Caps |= Caps::eSSUIntroducer;
break;
default: ;
}
cap++;
}
}
void RouterInfo::UpdateCapsProperty ()
{
std::string caps;
if (m_Caps & eFloodfill)
{
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // highest bandwidth
caps += CAPS_FLAG_FLOODFILL; // floodfill
}
else
caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth
if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable
SetProperty ("caps", caps);
}
void RouterInfo::WriteToStream (std::ostream& s)
{
uint64_t ts = htobe64 (m_Timestamp);
s.write ((char *)&ts, sizeof (ts));
// addresses
uint8_t numAddresses = m_Addresses.size ();
s.write ((char *)&numAddresses, sizeof (numAddresses));
for (auto& address : m_Addresses)
{
s.write ((char *)&address.cost, sizeof (address.cost));
s.write ((char *)&address.date, sizeof (address.date));
std::stringstream properties;
if (address.transportStyle == eTransportNTCP)
WriteString ("NTCP", s);
else if (address.transportStyle == eTransportSSU)
{
WriteString ("SSU", s);
// caps
WriteString ("caps", properties);
properties << '=';
std::string caps;
if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
WriteString (caps, properties);
properties << ';';
}
else
WriteString ("", s);
WriteString ("host", properties);
properties << '=';
WriteString (address.host.to_string (), properties);
properties << ';';
if (address.transportStyle == eTransportSSU)
{
// write introducers if any
if (address.introducers.size () > 0)
{
int i = 0;
for (auto introducer: address.introducers)
{
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (introducer.iHost.to_string (), properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("ikey" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
char value[64];
size_t l = i2p::util::ByteStreamToBase64 (introducer.iKey, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("iport" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iPort), properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
properties << ';';
i++;
}
}
// write intro key
WriteString ("key", properties);
properties << '=';
char value[64];
size_t l = i2p::util::ByteStreamToBase64 (address.key, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
// write mtu
if (address.mtu)
{
WriteString ("mtu", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.mtu), properties);
properties << ';';
}
}
WriteString ("port", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.port), properties);
properties << ';';
uint16_t size = htobe16 (properties.str ().size ());
s.write ((char *)&size, sizeof (size));
s.write (properties.str ().c_str (), properties.str ().size ());
}
// peers
uint8_t numPeers = 0;
s.write ((char *)&numPeers, sizeof (numPeers));
// properties
std::stringstream properties;
for (auto& p : m_Properties)
{
WriteString (p.first, properties);
properties << '=';
WriteString (p.second, properties);
properties << ';';
}
uint16_t size = htobe16 (properties.str ().size ());
s.write ((char *)&size, sizeof (size));
s.write (properties.str ().c_str (), properties.str ().size ());
}
const uint8_t * RouterInfo::LoadBuffer ()
{
if (!m_Buffer)
{
if (LoadFile ())
LogPrint ("Buffer for ", GetIdentHashAbbreviation (), " loaded from file");
}
return m_Buffer;
}
void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
{
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp
std::stringstream s;
uint8_t ident[1024];
auto identLen = privateKeys.GetPublic ().ToBuffer (ident, 1024);
s.write ((char *)ident, identLen);
WriteToStream (s);
m_BufferLen = s.str ().size ();
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
// signature
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
m_BufferLen += privateKeys.GetPublic ().GetSignatureLen ();
}
void RouterInfo::SaveToFile (const std::string& fullPath)
{
m_FullPath = fullPath;
if (m_Buffer)
{
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
if (f.is_open ())
f.write ((char *)m_Buffer, m_BufferLen);
else
LogPrint(eLogError, "Can't save RouterInfo to ", fullPath);
}
else
LogPrint (eLogError, "Can't save RouterInfo m_Buffer==NULL");
}
size_t RouterInfo::ReadString (char * str, std::istream& s)
{
uint8_t len;
s.read ((char *)&len, 1);
s.read (str, len);
str[len] = 0;
return len+1;
}
void RouterInfo::WriteString (const std::string& str, std::ostream& s)
{
uint8_t len = str.size ();
s.write ((char *)&len, 1);
s.write (str.c_str (), len);
}
void RouterInfo::AddNTCPAddress (const char * host, int port)
{
Address addr;
addr.host = boost::asio::ip::address::from_string (host);
addr.port = port;
addr.transportStyle = eTransportNTCP;
addr.cost = 2;
addr.date = 0;
addr.mtu = 0;
m_Addresses.push_back(addr);
m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eNTCPV4;
}
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
{
Address addr;
addr.host = boost::asio::ip::address::from_string (host);
addr.port = port;
addr.transportStyle = eTransportSSU;
addr.cost = 10; // NTCP should have priority over SSU
addr.date = 0;
addr.mtu = mtu;
memcpy (addr.key, key, 32);
m_Addresses.push_back(addr);
m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eSSUV4;
m_Caps |= eSSUTesting;
m_Caps |= eSSUIntroducer;
}
bool RouterInfo::AddIntroducer (const Address * address, uint32_t tag)
{
for (auto& addr : m_Addresses)
{
if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ())
{
for (auto intro: addr.introducers)
if (intro.iTag == tag) return false; // already presented
Introducer x;
x.iHost = address->host;
x.iPort = address->port;
x.iTag = tag;
memcpy (x.iKey, address->key, 32); // TODO: replace to Tag<32>
addr.introducers.push_back (x);
return true;
}
}
return false;
}
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
for (auto& addr : m_Addresses)
{
if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ())
{
for (std::vector<Introducer>::iterator it = addr.introducers.begin (); it != addr.introducers.end (); it++)
if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
{
addr.introducers.erase (it);
return true;
}
}
}
return false;
}
void RouterInfo::SetCaps (uint8_t caps)
{
m_Caps = caps;
UpdateCapsProperty ();
}
void RouterInfo::SetCaps (const char * caps)
{
SetProperty ("caps", caps);
m_Caps = 0;
ExtractCaps (caps);
}
void RouterInfo::SetProperty (const std::string& key, const std::string& value)
{
m_Properties[key] = value;
}
void RouterInfo::DeleteProperty (const std::string& key)
{
m_Properties.erase (key);
}
bool RouterInfo::IsFloodfill () const
{
return m_Caps & Caps::eFloodfill;
}
bool RouterInfo::IsNTCP (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eNTCPV4;
else
return m_SupportedTransports & (eNTCPV4 | eNTCPV6);
}
bool RouterInfo::IsSSU (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eSSUV4;
else
return m_SupportedTransports & (eSSUV4 | eSSUV6);
}
bool RouterInfo::IsV6 () const
{
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
}
void RouterInfo::EnableV6 ()
{
if (!IsV6 ())
m_SupportedTransports |= eNTCPV6 | eSSUV6;
}
void RouterInfo::DisableV6 ()
{
if (IsV6 ())
{
// NTCP
m_SupportedTransports &= ~eNTCPV6;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
m_Addresses[i].host.is_v6 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
// SSU
m_SupportedTransports &= ~eSSUV6;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU &&
m_Addresses[i].host.is_v6 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
}
}
bool RouterInfo::UsesIntroducer () const
{
return m_Caps & Caps::eUnreachable; // non-reachable
}
const RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) const
{
return GetAddress (eTransportNTCP, v4only);
}
const RouterInfo::Address * RouterInfo::GetSSUAddress (bool v4only) const
{
return GetAddress (eTransportSSU, v4only);
}
const RouterInfo::Address * RouterInfo::GetSSUV6Address () const
{
return GetAddress (eTransportSSU, false, true);
}
const RouterInfo::Address * RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const
{
for (auto& address : m_Addresses)
{
if (address.transportStyle == s)
{
if ((!v4only || address.host.is_v4 ()) && (!v6only || address.host.is_v6 ()))
return &address;
}
}
return nullptr;
}
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
{
if (!m_Profile)
m_Profile = GetRouterProfile (GetIdentHash ());
return m_Profile;
}
}
}

183
core/RouterInfo.h Normal file
View file

@ -0,0 +1,183 @@
#ifndef ROUTER_INFO_H__
#define ROUTER_INFO_H__
#include <inttypes.h>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <boost/asio.hpp>
#include "Identity.h"
#include "Profiling.h"
namespace i2p
{
namespace data
{
const char CAPS_FLAG_FLOODFILL = 'f';
const char CAPS_FLAG_HIDDEN = 'H';
const char CAPS_FLAG_REACHABLE = 'R';
const char CAPS_FLAG_UNREACHABLE = 'U';
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K';
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L';
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M';
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N';
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O';
const char CAPS_FLAG_SSU_TESTING = 'B';
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
const int MAX_RI_BUFFER_SIZE = 2048;
class RouterInfo: public RoutingDestination
{
public:
enum SupportedTranports
{
eNTCPV4 = 0x01,
eNTCPV6 = 0x02,
eSSUV4 = 0x04,
eSSUV6 = 0x08
};
enum Caps
{
eFloodfill = 0x01,
eHighBandwidth = 0x02,
eReachable = 0x04,
eSSUTesting = 0x08,
eSSUIntroducer = 0x10,
eHidden = 0x20,
eUnreachable = 0x40
};
enum TransportStyle
{
eTransportUnknown = 0,
eTransportNTCP,
eTransportSSU
};
struct Introducer
{
boost::asio::ip::address iHost;
int iPort;
Tag<32> iKey;
uint32_t iTag;
};
struct Address
{
TransportStyle transportStyle;
boost::asio::ip::address host;
std::string addressString;
int port, mtu;
uint64_t date;
uint8_t cost;
// SSU only
Tag<32> key; // intro key for SSU
std::vector<Introducer> introducers;
bool IsCompatible (const boost::asio::ip::address& other) const
{
return (host.is_v4 () && other.is_v4 ()) ||
(host.is_v6 () && other.is_v6 ());
}
};
RouterInfo (const std::string& fullPath);
RouterInfo (): m_Buffer (nullptr) { };
RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = default;
RouterInfo (const uint8_t * buf, int len);
~RouterInfo ();
const IdentityEx& GetRouterIdentity () const { return m_RouterIdentity; };
void SetRouterIdentity (const IdentityEx& identity);
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
std::string GetIdentHashAbbreviation () const { return GetIdentHash ().ToBase64 ().substr (0, 4); };
uint64_t GetTimestamp () const { return m_Timestamp; };
std::vector<Address>& GetAddresses () { return m_Addresses; };
const Address * GetNTCPAddress (bool v4only = true) const;
const Address * GetSSUAddress (bool v4only = true) const;
const Address * GetSSUV6Address () const;
void AddNTCPAddress (const char * host, int port);
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
bool AddIntroducer (const Address * address, uint32_t tag);
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only
void DeleteProperty (const std::string& key); // called from RouterContext only
void ClearProperties () { m_Properties.clear (); };
bool IsFloodfill () const;
bool IsNTCP (bool v4only = true) const;
bool IsSSU (bool v4only = true) const;
bool IsV6 () const;
void EnableV6 ();
void DisableV6 ();
bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; };
bool UsesIntroducer () const;
bool IsIntroducer () const { return m_Caps & eSSUIntroducer; };
bool IsPeerTesting () const { return m_Caps & eSSUTesting; };
bool IsHidden () const { return m_Caps & eHidden; };
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
uint8_t GetCaps () const { return m_Caps; };
void SetCaps (uint8_t caps);
void SetCaps (const char * caps);
void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; };
bool IsUnreachable () const { return m_IsUnreachable; };
const uint8_t * GetBuffer () const { return m_Buffer; };
const uint8_t * LoadBuffer (); // load if necessary
int GetBufferLen () const { return m_BufferLen; };
void CreateBuffer (const PrivateKeys& privateKeys);
bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; };
void SaveToFile (const std::string& fullPath);
std::shared_ptr<RouterProfile> GetProfile () const;
void SaveProfile () { if (m_Profile) m_Profile->Save (); };
void Update (const uint8_t * buf, int len);
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
// implements RoutingDestination
const IdentHash& GetIdentHash () const { return m_RouterIdentity.GetIdentHash (); };
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity.GetStandardIdentity ().publicKey; };
bool IsDestination () const { return false; };
private:
bool LoadFile ();
void ReadFromFile ();
void ReadFromStream (std::istream& s);
void ReadFromBuffer (bool verifySignature);
void WriteToStream (std::ostream& s);
size_t ReadString (char * str, std::istream& s);
void WriteString (const std::string& str, std::ostream& s);
void ExtractCaps (const char * value);
const Address * GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
void UpdateCapsProperty ();
private:
std::string m_FullPath;
IdentityEx m_RouterIdentity;
uint8_t * m_Buffer;
int m_BufferLen;
uint64_t m_Timestamp;
std::vector<Address> m_Addresses;
std::map<std::string, std::string> m_Properties;
bool m_IsUpdated, m_IsUnreachable;
uint8_t m_SupportedTransports, m_Caps;
mutable std::shared_ptr<RouterProfile> m_Profile;
};
}
}
#endif

894
core/Streaming.cpp Normal file
View file

@ -0,0 +1,894 @@
#include <cryptopp/gzip.h>
#include "util/Log.h"
#include "RouterInfo.h"
#include "RouterContext.h"
#include "tunnel/Tunnel.h"
#include "util/Timestamp.h"
#include "Destination.h"
#include "Streaming.h"
namespace i2p
{
namespace stream
{
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local,
std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service),
m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service),
m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port),
m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO),
m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0)
{
m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
m_RemoteIdentity = remote->GetIdentity ();
m_CurrentRemoteLease.endDate = 0;
}
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE),
m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0)
{
m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
}
Stream::~Stream ()
{
Terminate ();
while (!m_ReceiveQueue.empty ())
{
auto packet = m_ReceiveQueue.front ();
m_ReceiveQueue.pop ();
delete packet;
}
for (auto it: m_SentPackets)
delete it;
m_SentPackets.clear ();
for (auto it: m_SavedPackets)
delete it;
m_SavedPackets.clear ();
LogPrint (eLogDebug, "Stream deleted");
}
void Stream::Terminate ()
{
m_AckSendTimer.cancel ();
m_ReceiveTimer.cancel ();
m_ResendTimer.cancel ();
if (m_SendHandler)
{
auto handler = m_SendHandler;
m_SendHandler = nullptr;
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted));
}
}
void Stream::HandleNextPacket (Packet * packet)
{
m_NumReceivedBytes += packet->GetLength ();
if (!m_SendStreamID)
m_SendStreamID = packet->GetReceiveStreamID ();
if (!packet->IsNoAck ()) // ack received
ProcessAck (packet);
int32_t receivedSeqn = packet->GetSeqn ();
bool isSyn = packet->IsSYN ();
if (!receivedSeqn && !isSyn)
{
// plain ack
LogPrint (eLogDebug, "Plain ACK received");
delete packet;
return;
}
LogPrint (eLogDebug, "Received seqn=", receivedSeqn);
if (isSyn || receivedSeqn == m_LastReceivedSequenceNumber + 1)
{
// we have received next in sequence message
ProcessPacket (packet);
// we should also try stored messages if any
for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();)
{
if ((*it)->GetSeqn () == (uint32_t)(m_LastReceivedSequenceNumber + 1))
{
Packet * savedPacket = *it;
m_SavedPackets.erase (it++);
ProcessPacket (savedPacket);
}
else
break;
}
// schedule ack for last message
if (m_Status == eStreamStatusOpen)
{
if (!m_IsAckSendScheduled)
{
m_IsAckSendScheduled = true;
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT));
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
}
}
else if (isSyn)
// we have to send SYN back to incoming connection
SendBuffer (); // also sets m_IsOpen
}
else
{
if (receivedSeqn <= m_LastReceivedSequenceNumber)
{
// we have received duplicate
LogPrint (eLogWarning, "Duplicate message ", receivedSeqn, " received");
SendQuickAck (); // resend ack for previous message again
delete packet; // packet dropped
}
else
{
LogPrint (eLogWarning, "Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1);
// save message and wait for missing message again
SavePacket (packet);
if (m_LastReceivedSequenceNumber >= 0)
{
// send NACKs for missing messages ASAP
if (m_IsAckSendScheduled)
{
m_IsAckSendScheduled = false;
m_AckSendTimer.cancel ();
}
SendQuickAck ();
}
else
{
// wait for SYN
m_IsAckSendScheduled = true;
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT));
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
}
}
}
}
void Stream::SavePacket (Packet * packet)
{
m_SavedPackets.insert (packet);
}
void Stream::ProcessPacket (Packet * packet)
{
// process flags
uint32_t receivedSeqn = packet->GetSeqn ();
uint16_t flags = packet->GetFlags ();
LogPrint (eLogDebug, "Process seqn=", receivedSeqn, ", flags=", flags);
const uint8_t * optionData = packet->GetOptionData ();
if (flags & PACKET_FLAG_SYNCHRONIZE)
LogPrint (eLogDebug, "Synchronize");
if (flags & PACKET_FLAG_DELAY_REQUESTED)
{
optionData += 2;
}
if (flags & PACKET_FLAG_FROM_INCLUDED)
{
optionData += m_RemoteIdentity.FromBuffer (optionData, packet->GetOptionSize ());
LogPrint (eLogInfo, "From identity ", m_RemoteIdentity.GetIdentHash ().ToBase64 ());
if (!m_RemoteLeaseSet)
LogPrint (eLogDebug, "Incoming stream from ", m_RemoteIdentity.GetIdentHash ().ToBase64 ());
}
if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED)
{
uint16_t maxPacketSize = bufbe16toh (optionData);
LogPrint (eLogDebug, "Max packet size ", maxPacketSize);
optionData += 2;
}
if (flags & PACKET_FLAG_SIGNATURE_INCLUDED)
{
LogPrint (eLogDebug, "Signature");
uint8_t signature[256];
auto signatureLen = m_RemoteIdentity.GetSignatureLen ();
memcpy (signature, optionData, signatureLen);
memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
if (!m_RemoteIdentity.Verify (packet->GetBuffer (), packet->GetLength (), signature))
{
LogPrint (eLogError, "Signature verification failed");
Close ();
flags |= PACKET_FLAG_CLOSE;
}
memcpy (const_cast<uint8_t *>(optionData), signature, signatureLen);
optionData += signatureLen;
}
packet->offset = packet->GetPayload () - packet->buf;
if (packet->GetLength () > 0)
{
m_ReceiveQueue.push (packet);
m_ReceiveTimer.cancel ();
}
else
delete packet;
m_LastReceivedSequenceNumber = receivedSeqn;
if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET))
{
LogPrint (eLogInfo, (flags & PACKET_FLAG_RESET) ? "Reset" : "Closed");
m_Status = eStreamStatusReset;
Close ();
}
}
void Stream::ProcessAck (Packet * packet)
{
bool acknowledged = false;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
uint32_t ackThrough = packet->GetAckThrough ();
int nackCount = packet->GetNACKCount ();
for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();)
{
auto seqn = (*it)->GetSeqn ();
if (seqn <= ackThrough)
{
if (nackCount > 0)
{
bool nacked = false;
for (int i = 0; i < nackCount; i++)
if (seqn == packet->GetNACK (i))
{
nacked = true;
break;
}
if (nacked)
{
LogPrint (eLogDebug, "Packet ", seqn, " NACK");
it++;
continue;
}
}
auto sentPacket = *it;
uint64_t rtt = ts - sentPacket->sendTime;
m_RTT = (m_RTT*seqn + rtt)/(seqn + 1);
m_RTO = m_RTT*1.5; // TODO: implement it better
LogPrint (eLogDebug, "Packet ", seqn, " acknowledged rtt=", rtt);
m_SentPackets.erase (it++);
delete sentPacket;
acknowledged = true;
if (m_WindowSize < WINDOW_SIZE)
m_WindowSize++; // slow start
else
{
// linear growth
if (ts > m_LastWindowSizeIncreaseTime + m_RTT)
{
m_WindowSize++;
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
m_LastWindowSizeIncreaseTime = ts;
}
}
}
else
break;
}
if (m_SentPackets.empty ())
m_ResendTimer.cancel ();
if (acknowledged)
{
m_NumResendAttempts = 0;
SendBuffer ();
}
if (m_Status == eStreamStatusClosing)
Close (); // all outgoing messages have been sent
}
size_t Stream::Send (const uint8_t * buf, size_t len)
{
if (len > 0 && buf)
{
std::unique_lock<std::mutex> l(m_SendBufferMutex);
m_SendBuffer.clear ();
m_SendBuffer.write ((const char *)buf, len);
}
m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ()));
return len;
}
void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler)
{
if (m_SendHandler)
handler (boost::asio::error::make_error_code (boost::asio::error::in_progress));
else
m_SendHandler = handler;
Send (buf, len);
}
void Stream::SendBuffer ()
{
int numMsgs = m_WindowSize - m_SentPackets.size ();
if (numMsgs <= 0) return; // window is full
bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet
std::vector<Packet *> packets;
{
std::unique_lock<std::mutex> l(m_SendBufferMutex);
while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.eof () && numMsgs > 0))
{
Packet * p = new Packet ();
uint8_t * packet = p->GetBuffer ();
// TODO: implement setters
size_t size = 0;
htobe32buf (packet + size, m_SendStreamID);
size += 4; // sendStreamID
htobe32buf (packet + size, m_RecvStreamID);
size += 4; // receiveStreamID
htobe32buf (packet + size, m_SequenceNumber++);
size += 4; // sequenceNum
if (isNoAck)
htobe32buf (packet + size, m_LastReceivedSequenceNumber);
else
htobuf32 (packet + size, 0);
size += 4; // ack Through
packet[size] = 0;
size++; // NACK count
packet[size] = m_RTO/1000;
size++; // resend delay
if (m_Status == eStreamStatusNew)
{
// initial packet
m_Status = eStreamStatusOpen;
uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED |
PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED;
if (isNoAck) flags |= PACKET_FLAG_NO_ACK;
htobe16buf (packet + size, flags);
size += 2; // flags
size_t identityLen = m_LocalDestination.GetOwner ().GetIdentity ().GetFullLen ();
size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen ();
htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size
size += 2; // options size
m_LocalDestination.GetOwner ().GetIdentity ().ToBuffer (packet + size, identityLen);
size += identityLen; // from
htobe16buf (packet + size, STREAMING_MTU);
size += 2; // max packet size
uint8_t * signature = packet + size; // set it later
memset (signature, 0, signatureLen); // zeroes for now
size += signatureLen; // signature
m_SendBuffer.read ((char *)(packet + size), STREAMING_MTU - size);
size += m_SendBuffer.gcount (); // payload
m_LocalDestination.GetOwner ().Sign (packet, size, signature);
}
else
{
// follow on packet
htobuf16 (packet + size, 0);
size += 2; // flags
htobuf16 (packet + size, 0); // no options
size += 2; // options size
m_SendBuffer.read((char *)(packet + size), STREAMING_MTU - size);
size += m_SendBuffer.gcount (); // payload
}
p->len = size;
packets.push_back (p);
numMsgs--;
}
if (m_SendBuffer.eof () && m_SendHandler)
{
m_SendHandler (boost::system::error_code ());
m_SendHandler = nullptr;
}
}
if (packets.size () > 0)
{
m_IsAckSendScheduled = false;
m_AckSendTimer.cancel ();
bool isEmpty = m_SentPackets.empty ();
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: packets)
{
it->sendTime = ts;
m_SentPackets.insert (it);
}
SendPackets (packets);
if (m_Status == eStreamStatusClosing && m_SendBuffer.eof ())
SendClose ();
if (isEmpty)
ScheduleResend ();
}
}
void Stream::SendQuickAck ()
{
int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber;
if (!m_SavedPackets.empty ())
{
int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn ();
if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn;
}
if (lastReceivedSeqn < 0)
{
LogPrint (eLogError, "No packets have been received yet");
return;
}
Packet p;
uint8_t * packet = p.GetBuffer ();
size_t size = 0;
htobe32buf (packet + size, m_SendStreamID);
size += 4; // sendStreamID
htobe32buf (packet + size, m_RecvStreamID);
size += 4; // receiveStreamID
htobuf32 (packet + size, 0); // this is plain Ack message
size += 4; // sequenceNum
htobe32buf (packet + size, lastReceivedSeqn);
size += 4; // ack Through
uint8_t numNacks = 0;
if (lastReceivedSeqn > m_LastReceivedSequenceNumber)
{
// fill NACKs
uint8_t * nacks = packet + size + 1;
auto nextSeqn = m_LastReceivedSequenceNumber + 1;
for (auto it: m_SavedPackets)
{
auto seqn = it->GetSeqn ();
if (numNacks + (seqn - nextSeqn) >= 256)
{
LogPrint (eLogError, "Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn);
htobe32buf (packet + 12, nextSeqn); // change ack Through
break;
}
for (uint32_t i = nextSeqn; i < seqn; i++)
{
htobe32buf (nacks, i);
nacks += 4;
numNacks++;
}
nextSeqn = seqn + 1;
}
packet[size] = numNacks;
size++; // NACK count
size += numNacks*4; // NACKs
}
else
{
// No NACKs
packet[size] = 0;
size++; // NACK count
}
size++; // resend delay
htobuf16 (packet + size, 0); // nof flags set
size += 2; // flags
htobuf16 (packet + size, 0); // no options
size += 2; // options size
p.len = size;
SendPackets (std::vector<Packet *> { &p });
LogPrint ("Quick Ack sent. ", (int)numNacks, " NACKs");
}
void Stream::Close ()
{
switch (m_Status)
{
case eStreamStatusOpen:
m_Status = eStreamStatusClosing;
Close (); // recursion
if (m_Status == eStreamStatusClosing) //still closing
LogPrint (eLogInfo, "Trying to send stream data before closing");
break;
case eStreamStatusReset:
SendClose ();
Terminate ();
m_LocalDestination.DeleteStream (shared_from_this ());
break;
case eStreamStatusClosing:
if (m_SentPackets.empty () && m_SendBuffer.eof ()) // nothing to send
{
m_Status = eStreamStatusClosed;
SendClose ();
Terminate ();
m_LocalDestination.DeleteStream (shared_from_this ());
}
break;
case eStreamStatusClosed:
// already closed
Terminate ();
m_LocalDestination.DeleteStream (shared_from_this ());
break;
default:
LogPrint (eLogWarning, "Unexpected stream status ", (int)m_Status);
};
}
void Stream::SendClose ()
{
Packet * p = new Packet ();
uint8_t * packet = p->GetBuffer ();
size_t size = 0;
htobe32buf (packet + size, m_SendStreamID);
size += 4; // sendStreamID
htobe32buf (packet + size, m_RecvStreamID);
size += 4; // receiveStreamID
htobe32buf (packet + size, m_SequenceNumber++);
size += 4; // sequenceNum
htobe32buf (packet + size, m_LastReceivedSequenceNumber);
size += 4; // ack Through
packet[size] = 0;
size++; // NACK count
size++; // resend delay
htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED);
size += 2; // flags
size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen ();
htobe16buf (packet + size, signatureLen); // signature only
size += 2; // options size
uint8_t * signature = packet + size;
memset (packet + size, 0, signatureLen);
size += signatureLen; // signature
m_LocalDestination.GetOwner ().Sign (packet, size, signature);
p->len = size;
m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p));
LogPrint ("FIN sent");
}
size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len)
{
size_t pos = 0;
while (pos < len && !m_ReceiveQueue.empty ())
{
Packet * packet = m_ReceiveQueue.front ();
size_t l = std::min (packet->GetLength (), len - pos);
memcpy (buf + pos, packet->GetBuffer (), l);
pos += l;
packet->offset += l;
if (!packet->GetLength ())
{
m_ReceiveQueue.pop ();
delete packet;
}
}
return pos;
}
bool Stream::SendPacket (Packet * packet)
{
if (packet)
{
if (m_IsAckSendScheduled)
{
m_IsAckSendScheduled = false;
m_AckSendTimer.cancel ();
}
SendPackets (std::vector<Packet *> { packet });
if (m_Status == eStreamStatusOpen)
{
bool isEmpty = m_SentPackets.empty ();
m_SentPackets.insert (packet);
if (isEmpty)
ScheduleResend ();
}
else
delete packet;
return true;
}
else
return false;
}
void Stream::SendPackets (const std::vector<Packet *>& packets)
{
if (!m_RemoteLeaseSet)
{
UpdateCurrentRemoteLease ();
if (!m_RemoteLeaseSet)
{
LogPrint (eLogError, "Can't send packets. Missing remote LeaseSet");
return;
}
}
if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ())
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
if (!m_CurrentOutboundTunnel)
{
LogPrint (eLogError, "No outbound tunnels in the pool");
return;
}
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (!m_CurrentRemoteLease.endDate || ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000)
UpdateCurrentRemoteLease (true);
if (ts < m_CurrentRemoteLease.endDate)
{
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
for (auto it: packets)
{
auto msg = m_RoutingSession->WrapSingleMessage (CreateDataMessage (it->GetBuffer (), it->GetLength ()));
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeTunnel,
m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID,
msg
});
m_NumSentBytes += it->GetLength ();
}
m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs);
}
else
LogPrint (eLogWarning, "All leases are expired");
}
void Stream::ScheduleResend ()
{
m_ResendTimer.cancel ();
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO));
m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer,
shared_from_this (), std::placeholders::_1));
}
void Stream::HandleResendTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
// check for resend attempts
if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS)
{
LogPrint (eLogWarning, "Stream packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate");
m_Status = eStreamStatusReset;
Close ();
return;
}
// collect packets to resend
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
std::vector<Packet *> packets;
for (auto it : m_SentPackets)
{
if (ts >= it->sendTime + m_RTO)
{
it->sendTime = ts;
packets.push_back (it);
}
}
// select tunnels if necessary and send
if (packets.size () > 0)
{
m_NumResendAttempts++;
m_RTO *= 2;
switch (m_NumResendAttempts)
{
case 1: // congesion avoidance
m_WindowSize /= 2;
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
break;
case 2:
m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time
// no break here
case 4:
UpdateCurrentRemoteLease (); // pick another lease
LogPrint (eLogWarning, "Another remote lease has been selected for stream");
break;
case 3:
// pick another outbound tunnel
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
LogPrint (eLogWarning, "Another outbound tunnel has been selected for stream");
break;
default: ;
}
SendPackets (packets);
}
ScheduleResend ();
}
}
void Stream::HandleAckSendTimer (const boost::system::error_code& ecode)
{
if (m_IsAckSendScheduled)
{
if (m_LastReceivedSequenceNumber < 0)
{
LogPrint (eLogWarning, "SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on. Terminate");
m_Status = eStreamStatusReset;
Close ();
return;
}
if (m_Status == eStreamStatusOpen)
SendQuickAck ();
m_IsAckSendScheduled = false;
}
}
void Stream::UpdateCurrentRemoteLease (bool expired)
{
if (!m_RemoteLeaseSet)
{
m_RemoteLeaseSet = m_LocalDestination.GetOwner ().FindLeaseSet (m_RemoteIdentity.GetIdentHash ());
if (!m_RemoteLeaseSet)
LogPrint ("LeaseSet ", m_RemoteIdentity.GetIdentHash ().ToBase64 (), " not found");
}
if (m_RemoteLeaseSet)
{
if (!m_RoutingSession)
m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (m_RemoteLeaseSet, 32);
auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first
if (leases.empty ())
{
expired = false;
m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); // time to re-request
leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold
}
if (!leases.empty ())
{
bool updated = false;
if (expired)
{
for (auto it: leases)
if ((it.tunnelGateway == m_CurrentRemoteLease.tunnelGateway) && (it.tunnelID != m_CurrentRemoteLease.tunnelID))
{
m_CurrentRemoteLease = it;
updated = true;
break;
}
}
if (!updated)
{
uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1);
if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID)
// make sure we don't select previous
i = (i + 1) % leases.size (); // if so, pick next
m_CurrentRemoteLease = leases[i];
}
}
else
{
m_RemoteLeaseSet = nullptr;
m_CurrentRemoteLease.endDate = 0;
// re-request expired
}
}
else
m_CurrentRemoteLease.endDate = 0;
}
std::shared_ptr<I2NPMessage> Stream::CreateDataMessage (const uint8_t * payload, size_t len)
{
auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ());
CryptoPP::Gzip compressor;
if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)
compressor.SetDeflateLevel (CryptoPP::Gzip::MIN_DEFLATE_LEVEL);
else
compressor.SetDeflateLevel (CryptoPP::Gzip::DEFAULT_DEFLATE_LEVEL);
compressor.Put (payload, len);
compressor.MessageEnd();
int size = compressor.MaxRetrievable ();
uint8_t * buf = msg->GetPayload ();
htobe32buf (buf, size); // length
buf += 4;
compressor.Get (buf, size);
htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port
htobe16buf (buf + 6, m_Port); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData);
return msg;
}
void StreamingDestination::Start ()
{
}
void StreamingDestination::Stop ()
{
ResetAcceptor ();
{
std::unique_lock<std::mutex> l(m_StreamsMutex);
m_Streams.clear ();
}
}
void StreamingDestination::HandleNextPacket (Packet * packet)
{
uint32_t sendStreamID = packet->GetSendStreamID ();
if (sendStreamID)
{
auto it = m_Streams.find (sendStreamID);
if (it != m_Streams.end ())
it->second->HandleNextPacket (packet);
else
{
LogPrint ("Unknown stream sendStreamID=", sendStreamID);
delete packet;
}
}
else
{
if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream
{
auto incomingStream = CreateNewIncomingStream ();
incomingStream->HandleNextPacket (packet);
if (m_Acceptor != nullptr)
m_Acceptor (incomingStream);
else
{
LogPrint ("Acceptor for incoming stream is not set");
DeleteStream (incomingStream);
}
}
else // follow on packet without SYN
{
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
for (auto it: m_Streams)
if (it.second->GetSendStreamID () == receiveStreamID)
{
// found
it.second->HandleNextPacket (packet);
return;
}
// TODO: should queue it up
LogPrint ("Unknown stream receiveStreamID=", receiveStreamID);
delete packet;
}
}
}
std::shared_ptr<Stream> StreamingDestination::CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
{
auto s = std::make_shared<Stream> (m_Owner.GetService (), *this, remote, port);
std::unique_lock<std::mutex> l(m_StreamsMutex);
m_Streams[s->GetRecvStreamID ()] = s;
return s;
}
std::shared_ptr<Stream> StreamingDestination::CreateNewIncomingStream ()
{
auto s = std::make_shared<Stream> (m_Owner.GetService (), *this);
std::unique_lock<std::mutex> l(m_StreamsMutex);
m_Streams[s->GetRecvStreamID ()] = s;
return s;
}
void StreamingDestination::DeleteStream (std::shared_ptr<Stream> stream)
{
if (stream)
{
std::unique_lock<std::mutex> l(m_StreamsMutex);
auto it = m_Streams.find (stream->GetRecvStreamID ());
if (it != m_Streams.end ())
m_Streams.erase (it);
}
}
void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len)
{
// unzip it
CryptoPP::Gunzip decompressor;
decompressor.Put (buf, len);
decompressor.MessageEnd();
Packet * uncompressed = new Packet;
uncompressed->offset = 0;
uncompressed->len = decompressor.MaxRetrievable ();
if (uncompressed->len <= MAX_PACKET_SIZE)
{
decompressor.Get (uncompressed->buf, uncompressed->len);
HandleNextPacket (uncompressed);
}
else
{
LogPrint ("Received packet size ", uncompressed->len, " exceeds max packet size. Skipped");
delete uncompressed;
}
}
}
}

271
core/Streaming.h Normal file
View file

@ -0,0 +1,271 @@
#ifndef STREAMING_H__
#define STREAMING_H__
#include <inttypes.h>
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <functional>
#include <memory>
#include <mutex>
#include <boost/asio.hpp>
#include "util/I2PEndian.h"
#include "Identity.h"
#include "LeaseSet.h"
#include "I2NPProtocol.h"
#include "Garlic.h"
#include "tunnel/Tunnel.h"
namespace i2p
{
namespace client
{
class ClientDestination;
}
namespace stream
{
const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001;
const uint16_t PACKET_FLAG_CLOSE = 0x0002;
const uint16_t PACKET_FLAG_RESET = 0x0004;
const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008;
const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010;
const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020;
const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040;
const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080;
const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100;
const uint16_t PACKET_FLAG_ECHO = 0x0200;
const uint16_t PACKET_FLAG_NO_ACK = 0x0400;
const size_t STREAMING_MTU = 1730;
const size_t MAX_PACKET_SIZE = 4096;
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
const int ACK_SEND_TIMEOUT = 200; // in milliseconds
const int MAX_NUM_RESEND_ATTEMPTS = 6;
const int WINDOW_SIZE = 6; // in messages
const int MIN_WINDOW_SIZE = 1;
const int MAX_WINDOW_SIZE = 128;
const int INITIAL_RTT = 8000; // in milliseconds
const int INITIAL_RTO = 9000; // in milliseconds
struct Packet
{
size_t len, offset;
uint8_t buf[MAX_PACKET_SIZE];
uint64_t sendTime;
Packet (): len (0), offset (0), sendTime (0) {};
uint8_t * GetBuffer () { return buf + offset; };
size_t GetLength () const { return len - offset; };
uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
uint32_t GetSeqn () const { return bufbe32toh (buf + 8); };
uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); };
uint8_t GetNACKCount () const { return buf[16]; };
uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); };
const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags
uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); };
uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); };
const uint8_t * GetOptionData () const { return GetOption () + 2; };
const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); };
bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; };
bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; };
};
struct PacketCmp
{
bool operator() (const Packet * p1, const Packet * p2) const
{
return p1->GetSeqn () < p2->GetSeqn ();
};
};
enum StreamStatus
{
eStreamStatusNew = 0,
eStreamStatusOpen,
eStreamStatusReset,
eStreamStatusClosing,
eStreamStatusClosed
};
class StreamingDestination;
class Stream: public std::enable_shared_from_this<Stream>
{
public:
typedef std::function<void (const boost::system::error_code& ecode)> SendHandler;
Stream (boost::asio::io_service& service, StreamingDestination& local,
std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
~Stream ();
uint32_t GetSendStreamID () const { return m_SendStreamID; };
uint32_t GetRecvStreamID () const { return m_RecvStreamID; };
std::shared_ptr<const i2p::data::LeaseSet> GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; };
bool IsOpen () const { return m_Status == eStreamStatusOpen; };
bool IsEstablished () const { return m_SendStreamID; };
StreamStatus GetStatus () const { return m_Status; };
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
void HandleNextPacket (Packet * packet);
size_t Send (const uint8_t * buf, size_t len);
void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler);
template<typename Buffer, typename ReceiveHandler>
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
void Close ();
void Cancel () { m_ReceiveTimer.cancel (); };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
size_t GetSendQueueSize () const { return m_SentPackets.size (); };
size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); };
size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); };
int GetWindowSize () const { return m_WindowSize; };
int GetRTT () const { return m_RTT; };
private:
void Terminate ();
void SendBuffer ();
void SendQuickAck ();
void SendClose ();
bool SendPacket (Packet * packet);
void SendPackets (const std::vector<Packet *>& packets);
void SavePacket (Packet * packet);
void ProcessPacket (Packet * packet);
void ProcessAck (Packet * packet);
size_t ConcatenatePackets (uint8_t * buf, size_t len);
void UpdateCurrentRemoteLease (bool expired = false);
template<typename Buffer, typename ReceiveHandler>
void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler);
void ScheduleResend ();
void HandleResendTimer (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:
boost::asio::io_service& m_Service;
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
int32_t m_LastReceivedSequenceNumber;
StreamStatus m_Status;
bool m_IsAckSendScheduled;
StreamingDestination& m_LocalDestination;
i2p::data::IdentityEx m_RemoteIdentity;
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
i2p::data::Lease m_CurrentRemoteLease;
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
std::queue<Packet *> m_ReceiveQueue;
std::set<Packet *, PacketCmp> m_SavedPackets;
std::set<Packet *, PacketCmp> m_SentPackets;
boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer;
size_t m_NumSentBytes, m_NumReceivedBytes;
uint16_t m_Port;
std::mutex m_SendBufferMutex;
std::stringstream m_SendBuffer;
int m_WindowSize, m_RTT, m_RTO;
uint64_t m_LastWindowSizeIncreaseTime;
int m_NumResendAttempts;
SendHandler m_SendHandler;
};
class StreamingDestination
{
public:
typedef std::function<void (std::shared_ptr<Stream>)> Acceptor;
StreamingDestination (i2p::client::ClientDestination& owner, uint16_t localPort = 0):
m_Owner (owner), m_LocalPort (localPort) {};
~StreamingDestination () {};
void Start ();
void Stop ();
std::shared_ptr<Stream> CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
void DeleteStream (std::shared_ptr<Stream> stream);
void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; };
void ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; };
bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
i2p::client::ClientDestination& GetOwner () { return m_Owner; };
uint16_t GetLocalPort () const { return m_LocalPort; };
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
private:
void HandleNextPacket (Packet * packet);
std::shared_ptr<Stream> CreateNewIncomingStream ();
private:
i2p::client::ClientDestination& m_Owner;
uint16_t m_LocalPort;
std::mutex m_StreamsMutex;
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams;
Acceptor m_Acceptor;
public:
// for HTTP only
const decltype(m_Streams)& GetStreams () const { return m_Streams; };
};
//-------------------------------------------------
template<typename Buffer, typename ReceiveHandler>
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
{
auto s = shared_from_this();
m_Service.post ([=](void)
{
if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset)
s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler);
else
{
s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout));
s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode)
{ s->HandleReceiveTimer (ecode, buffer, handler); });
}
});
}
template<typename Buffer, typename ReceiveHandler>
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler)
{
size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
if (received > 0)
handler (boost::system::error_code (), received);
else if (ecode == boost::asio::error::operation_aborted)
{
// timeout not expired
if (m_Status == eStreamStatusReset)
handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0);
else
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0);
}
else
// timeout expired
handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received);
}
}
}
#endif

247
core/UPnP.cpp Normal file
View file

@ -0,0 +1,247 @@
#ifdef USE_UPNP
#include <string>
#include <thread>
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#ifdef _WIN32
#include <windows.h>
#define dlsym GetProcAddress
#else
#include <dlfcn.h>
#endif
#include "util/Log.h"
#include "RouterContext.h"
#include "UPnP.h"
#include "NetDb.h"
#include "util/util.h"
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
// These are per-process and are safe to reuse for all threads
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int);
int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *,
const char *, const char *, const char *, const char *);
#else
/* miniupnpc 1.6 */
UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int, int, int *);
int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *,
const char *, const char *, const char *, const char *, const char *);
#endif
int (*UPNP_GetValidIGDFunc) (struct UPNPDev *, struct UPNPUrls *, struct IGDdatas *, char *, int);
int (*UPNP_GetExternalIPAddressFunc) (const char *, const char *, char *);
int (*UPNP_DeletePortMappingFunc) (const char *, const char *, const char *, const char *, const char *);
void (*freeUPNPDevlistFunc) (struct UPNPDev *);
void (*FreeUPNPUrlsFunc) (struct UPNPUrls *);
// Nice approach http://stackoverflow.com/a/21517513/673826
template<class M, typename F>
F GetKnownProcAddressImpl(M hmod, const char *name, F) {
auto proc = reinterpret_cast<F>(dlsym(hmod, name));
if (!proc) {
LogPrint("Error resolving ", name, " from UPNP library. This often happens if there is version mismatch!");
}
return proc;
}
#define GetKnownProcAddress(hmod, func) GetKnownProcAddressImpl(hmod, #func, func##Func);
namespace i2p
{
namespace transport
{
UPnP::UPnP () : m_Thread (nullptr) , m_IsModuleLoaded (false)
{
}
void UPnP::Stop ()
{
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void UPnP::Start()
{
if (!m_IsModuleLoaded) {
#ifdef MAC_OSX
m_Module = dlopen ("libminiupnpc.dylib", RTLD_LAZY);
#elif _WIN32
m_Module = LoadLibrary ("miniupnpc.dll"); // official prebuilt binary, e.g., in upnpc-exe-win32-20140422.zip
#else
m_Module = dlopen ("libminiupnpc.so", RTLD_LAZY);
#endif
if (m_Module == NULL)
{
LogPrint ("Error loading UPNP library. This often happens if there is version mismatch!");
return;
}
else
{
upnpDiscoverFunc = GetKnownProcAddress (m_Module, upnpDiscover);
UPNP_GetValidIGDFunc = GetKnownProcAddress (m_Module, UPNP_GetValidIGD);
UPNP_GetExternalIPAddressFunc = GetKnownProcAddress (m_Module, UPNP_GetExternalIPAddress);
UPNP_AddPortMappingFunc = GetKnownProcAddress (m_Module, UPNP_AddPortMapping);
UPNP_DeletePortMappingFunc = GetKnownProcAddress (m_Module, UPNP_DeletePortMapping);
freeUPNPDevlistFunc = GetKnownProcAddress (m_Module, freeUPNPDevlist);
FreeUPNPUrlsFunc = GetKnownProcAddress (m_Module, FreeUPNPUrls);
if (upnpDiscoverFunc && UPNP_GetValidIGDFunc && UPNP_GetExternalIPAddressFunc && UPNP_AddPortMappingFunc &&
UPNP_DeletePortMappingFunc && freeUPNPDevlistFunc && FreeUPNPUrlsFunc)
m_IsModuleLoaded = true;
}
}
m_Thread = new std::thread (std::bind (&UPnP::Run, this));
}
UPnP::~UPnP ()
{
}
void UPnP::Run ()
{
for (auto& address : context.GetRouterInfo ().GetAddresses ())
{
if (!address.host.is_v6 ())
{
Discover ();
if (address.transportStyle == data::RouterInfo::eTransportSSU )
{
TryPortMapping (I2P_UPNP_UDP, address.port);
}
else if (address.transportStyle == data::RouterInfo::eTransportNTCP )
{
TryPortMapping (I2P_UPNP_TCP, address.port);
}
}
}
}
void UPnP::Discover ()
{
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0);
#else
/* miniupnpc 1.6 */
int nerror = 0;
m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror);
#endif
int r;
r = UPNP_GetValidIGDFunc (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr));
if (r == 1)
{
r = UPNP_GetExternalIPAddressFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress);
if(r != UPNPCOMMAND_SUCCESS)
{
LogPrint ("UPnP: UPNP_GetExternalIPAddress () returned ", r);
return;
}
else
{
if (m_externalIPAddress[0])
{
LogPrint ("UPnP: ExternalIPAddress = ", m_externalIPAddress);
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress));
return;
}
else
{
LogPrint ("UPnP: GetExternalIPAddress failed.");
return;
}
}
}
}
void UPnP::TryPortMapping (int type, int port)
{
std::string strType, strPort (std::to_string (port));
switch (type)
{
case I2P_UPNP_TCP:
strType = "TCP";
break;
case I2P_UPNP_UDP:
default:
strType = "UDP";
}
int r;
std::string strDesc = "I2Pd";
try {
for (;;) {
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0);
#else
/* miniupnpc 1.6 */
r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0");
#endif
if (r!=UPNPCOMMAND_SUCCESS)
{
LogPrint ("AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r);
return;
}
else
{
LogPrint ("UPnP Port Mapping successful. (", m_NetworkAddr ,":", strPort.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", strPort.c_str() ,")");
return;
}
std::this_thread::sleep_for(std::chrono::minutes(20)); // c++11
//boost::this_thread::sleep_for(); // pre c++11
//sleep(20*60); // non-portable
}
}
catch (boost::thread_interrupted)
{
CloseMapping(type, port);
Close();
throw;
}
}
void UPnP::CloseMapping (int type, int port)
{
std::string strType, strPort (std::to_string (port));
switch (type)
{
case I2P_UPNP_TCP:
strType = "TCP";
break;
case I2P_UPNP_UDP:
default:
strType = "UDP";
}
int r = 0;
r = UPNP_DeletePortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0);
LogPrint ("UPNP_DeletePortMapping() returned : ", r, "\n");
}
void UPnP::Close ()
{
freeUPNPDevlistFunc (m_Devlist);
m_Devlist = 0;
FreeUPNPUrlsFunc (&m_upnpUrls);
#ifndef _WIN32
dlclose (m_Module);
#else
FreeLibrary (m_Module);
#endif
}
}
}
#endif

63
core/UPnP.h Normal file
View file

@ -0,0 +1,63 @@
#ifndef __UPNP_H__
#define __UPNP_H__
#ifdef USE_UPNP
#include <string>
#include <thread>
#include <miniupnpc/miniwget.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#include <miniupnpc/upnperrors.h>
#include <boost/asio.hpp>
#include "util/util.h"
#define I2P_UPNP_TCP 1
#define I2P_UPNP_UDP 2
namespace i2p
{
namespace transport
{
class UPnP
{
public:
UPnP ();
~UPnP ();
void Close ();
void Start ();
void Stop ();
void Discover ();
void TryPortMapping (int type, int port);
void CloseMapping (int type, int port);
private:
void Run ();
std::thread * m_Thread;
struct UPNPUrls m_upnpUrls;
struct IGDdatas m_upnpData;
// For miniupnpc
char * m_MulticastIf = 0;
char * m_Minissdpdpath = 0;
struct UPNPDev * m_Devlist = 0;
char m_NetworkAddr[64];
char m_externalIPAddress[40];
bool m_IsModuleLoaded;
#ifndef _WIN32
void *m_Module;
#else
HINSTANCE m_Module;
#endif
};
}
}
#endif
#endif

112
core/api.cpp Normal file
View file

@ -0,0 +1,112 @@
#include <string>
#include <map>
#include "util/Log.h"
#include "NetDb.h"
#include "transport/Transports.h"
#include "tunnel/Tunnel.h"
#include "RouterContext.h"
#include "Identity.h"
#include "Destination.h"
#include "util/util.h"
#include "api.h"
namespace i2p
{
namespace api
{
void InitI2P (int argc, char* argv[], const char * appName)
{
i2p::util::filesystem::SetAppName (appName);
i2p::util::config::OptionParser(argc, argv);
i2p::context.Init ();
}
void StartI2P (std::ostream * logStream)
{
if (logStream)
StartLog (logStream);
else
StartLog (i2p::util::filesystem::GetAppName () + ".log");
i2p::data::netdb.Start();
LogPrint("NetDB started");
i2p::transport::transports.Start();
LogPrint("Transports started");
i2p::tunnel::tunnels.Start();
LogPrint("Tunnels started");
}
void StopI2P ()
{
LogPrint("Shutdown started.");
i2p::tunnel::tunnels.Stop();
LogPrint("Tunnels stopped");
i2p::transport::transports.Stop();
LogPrint("Transports stopped");
i2p::data::netdb.Stop();
LogPrint("NetDB stopped");
StopLog ();
}
i2p::client::ClientDestination * CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
const std::map<std::string, std::string> * params)
{
auto localDestination = new i2p::client::ClientDestination (keys, isPublic, params);
localDestination->Start ();
return localDestination;
}
i2p::client::ClientDestination * CreateLocalDestination (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 = new i2p::client::ClientDestination (keys, isPublic, params);
localDestination->Start ();
return localDestination;
}
void DestroyLocalDestination (i2p::client::ClientDestination * dest)
{
if (dest)
{
dest->Stop ();
delete dest;
}
}
void RequestLeaseSet (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote)
{
if (dest)
dest->RequestDestination (remote);
}
std::shared_ptr<i2p::stream::Stream> CreateStream (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote)
{
if (!dest) return nullptr;
auto leaseSet = dest->FindLeaseSet (remote);
if (leaseSet)
{
auto stream = dest->CreateStream (leaseSet);
stream->Send (nullptr, 0); // connect
return stream;
}
else
{
RequestLeaseSet (dest, remote);
return nullptr;
}
}
void AcceptStream (i2p::client::ClientDestination * dest, const i2p::stream::StreamingDestination::Acceptor& acceptor)
{
if (dest)
dest->AcceptStreams (acceptor);
}
void DestroyStream (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
stream->Close ();
}
}
}

36
core/api.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef API_H__
#define API_H__
#include <memory>
#include <iostream>
#include "Identity.h"
#include "Destination.h"
#include "Streaming.h"
namespace i2p
{
namespace api
{
// initialization start and stop
void InitI2P (int argc, char* argv[], const char * appName);
void StartI2P (std::ostream * logStream = nullptr);
// write system log to logStream, if not specified to <appName>.log in application's folder
void StopI2P ();
// destinations
i2p::client::ClientDestination * CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
const std::map<std::string, std::string> * params = nullptr);
i2p::client::ClientDestination * CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256,
const std::map<std::string, std::string> * params = nullptr); // transient destinations usually not published
void DestroyLocalDestination (i2p::client::ClientDestination * dest);
// streams
void RequestLeaseSet (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote);
std::shared_ptr<i2p::stream::Stream> CreateStream (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote);
void AcceptStream (i2p::client::ClientDestination * dest, const i2p::stream::StreamingDestination::Acceptor& acceptor);
void DestroyStream (std::shared_ptr<i2p::stream::Stream> stream);
}
}
#endif

66
core/crypto/AESNIMacros.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef AESNIMACROS_H__
#define AESNIMACROS_H__
#define KeyExpansion256(round0,round1) \
"pshufd $0xff, %%xmm2, %%xmm2 \n" \
"movaps %%xmm1, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pxor %%xmm2, %%xmm1 \n" \
"movaps %%xmm1, "#round0"(%[sched]) \n" \
"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
"movaps %%xmm3, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pxor %%xmm2, %%xmm3 \n" \
"movaps %%xmm3, "#round1"(%[sched]) \n"
#define EncryptAES256(sched) \
"pxor (%["#sched"]), %%xmm0 \n" \
"aesenc 16(%["#sched"]), %%xmm0 \n" \
"aesenc 32(%["#sched"]), %%xmm0 \n" \
"aesenc 48(%["#sched"]), %%xmm0 \n" \
"aesenc 64(%["#sched"]), %%xmm0 \n" \
"aesenc 80(%["#sched"]), %%xmm0 \n" \
"aesenc 96(%["#sched"]), %%xmm0 \n" \
"aesenc 112(%["#sched"]), %%xmm0 \n" \
"aesenc 128(%["#sched"]), %%xmm0 \n" \
"aesenc 144(%["#sched"]), %%xmm0 \n" \
"aesenc 160(%["#sched"]), %%xmm0 \n" \
"aesenc 176(%["#sched"]), %%xmm0 \n" \
"aesenc 192(%["#sched"]), %%xmm0 \n" \
"aesenc 208(%["#sched"]), %%xmm0 \n" \
"aesenclast 224(%["#sched"]), %%xmm0 \n"
#define DecryptAES256(sched) \
"pxor 224(%["#sched"]), %%xmm0 \n" \
"aesdec 208(%["#sched"]), %%xmm0 \n" \
"aesdec 192(%["#sched"]), %%xmm0 \n" \
"aesdec 176(%["#sched"]), %%xmm0 \n" \
"aesdec 160(%["#sched"]), %%xmm0 \n" \
"aesdec 144(%["#sched"]), %%xmm0 \n" \
"aesdec 128(%["#sched"]), %%xmm0 \n" \
"aesdec 112(%["#sched"]), %%xmm0 \n" \
"aesdec 96(%["#sched"]), %%xmm0 \n" \
"aesdec 80(%["#sched"]), %%xmm0 \n" \
"aesdec 64(%["#sched"]), %%xmm0 \n" \
"aesdec 48(%["#sched"]), %%xmm0 \n" \
"aesdec 32(%["#sched"]), %%xmm0 \n" \
"aesdec 16(%["#sched"]), %%xmm0 \n" \
"aesdeclast (%["#sched"]), %%xmm0 \n"
#define CallAESIMC(offset) \
"movaps "#offset"(%[shed]), %%xmm0 \n" \
"aesimc %%xmm0, %%xmm0 \n" \
"movaps %%xmm0, "#offset"(%[shed]) \n"
#endif

View file

@ -0,0 +1,73 @@
#include <inttypes.h>
#include "CryptoConst.h"
namespace i2p
{
namespace crypto
{
const uint8_t elgp_[256]=
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
const uint8_t dsap_[128]=
{
0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23,
0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c,
0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8,
0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c,
0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11,
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
};
const uint8_t dsaq_[20]=
{
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
0x68, 0x40, 0x46, 0xb7
};
const uint8_t dsag_[128]=
{
0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2,
0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52,
0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc,
0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a,
0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c,
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
};
const CryptoConstants& GetCryptoConstants ()
{
static CryptoConstants cryptoConstants =
{
{elgp_, 256}, // elgp
{2}, // elgg
{dsap_, 128}, // dsap
{dsaq_, 20}, // dsaq
{dsag_, 128} // dsag
};
return cryptoConstants;
}
}
}

38
core/crypto/CryptoConst.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef CRYPTO_CONST_H__
#define CRYPTO_CONST_H__
#include <cryptopp/integer.h>
namespace i2p
{
namespace crypto
{
struct CryptoConstants
{
// DH/ElGamal
const CryptoPP::Integer elgp;
const CryptoPP::Integer elgg;
// DSA
const CryptoPP::Integer dsap;
const CryptoPP::Integer dsaq;
const CryptoPP::Integer dsag;
};
const CryptoConstants& GetCryptoConstants ();
// DH/ElGamal
#define elgp GetCryptoConstants ().elgp
#define elgg GetCryptoConstants ().elgg
// DSA
#define dsap GetCryptoConstants ().dsap
#define dsaq GetCryptoConstants ().dsaq
#define dsag GetCryptoConstants ().dsag
// RSA
const int rsae = 65537;
}
}
#endif

87
core/crypto/ElGamal.h Normal file
View file

@ -0,0 +1,87 @@
#ifndef EL_GAMAL_H__
#define EL_GAMAL_H__
#include <inttypes.h>
#include <cryptopp/integer.h>
#include <cryptopp/osrng.h>
#include <cryptopp/dh.h>
#include <cryptopp/sha.h>
#include "CryptoConst.h"
#include "util/Log.h"
namespace i2p
{
namespace crypto
{
class ElGamalEncryption
{
public:
ElGamalEncryption (const uint8_t * key)
{
CryptoPP::AutoSeededRandomPool rnd;
CryptoPP::Integer y (key, 256), k (rnd, CryptoPP::Integer::One(), elgp-1);
a = a_exp_b_mod_c (elgg, k, elgp);
b1 = a_exp_b_mod_c (y, k, elgp);
}
void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const
{
// calculate b = b1*m mod p
uint8_t m[255];
m[0] = 0xFF;
memcpy (m+33, data, len);
CryptoPP::SHA256().CalculateDigest(m+1, m+33, 222);
CryptoPP::Integer b (a_times_b_mod_c (b1, CryptoPP::Integer (m, 255), elgp));
// copy a and b
if (zeroPadding)
{
encrypted[0] = 0;
a.Encode (encrypted + 1, 256);
encrypted[257] = 0;
b.Encode (encrypted + 258, 256);
}
else
{
a.Encode (encrypted, 256);
b.Encode (encrypted + 256, 256);
}
}
private:
CryptoPP::Integer a, b1;
};
inline bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted,
uint8_t * data, bool zeroPadding = false)
{
CryptoPP::Integer x(key, 256), a(zeroPadding? encrypted +1 : encrypted, 256),
b(zeroPadding? encrypted + 258 :encrypted + 256, 256);
uint8_t m[255];
a_times_b_mod_c (b, a_exp_b_mod_c (a, elgp - x - 1, elgp), elgp).Encode (m, 255);
if (!CryptoPP::SHA256().VerifyDigest (m + 1, m + 33, 222))
{
LogPrint ("ElGamal decrypt hash doesn't match");
return false;
}
memcpy (data, m + 33, 222);
return true;
}
inline void GenerateElGamalKeyPair (CryptoPP::RandomNumberGenerator& rnd, uint8_t * priv, uint8_t * pub)
{
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
rnd.GenerateBlock (priv, 256);
a_exp_b_mod_c (elgg, CryptoPP::Integer (priv, 256), elgp).Encode (pub, 256);
#else
CryptoPP::DH dh (elgp, elgg);
dh.GenerateKeyPair(rnd, priv, pub);
#endif
}
}
}
#endif

121
core/crypto/Signature.cpp Normal file
View file

@ -0,0 +1,121 @@
#include <memory>
#include <cryptopp/integer.h>
#include <cryptopp/eccrypto.h>
#include "util/Log.h"
#include "Signature.h"
namespace i2p
{
namespace crypto
{
class Ed25519
{
public:
Ed25519 ()
{
q = CryptoPP::Integer::Power2 (255) - CryptoPP::Integer (19); // 2^255-19
l = CryptoPP::Integer::Power2 (252) + CryptoPP::Integer ("27742317777372353535851937790883648493");
// 2^252 + 27742317777372353535851937790883648493
d = CryptoPP::Integer (-121665) * CryptoPP::Integer (121666).InverseMod (q); // -121665/121666
I = a_exp_b_mod_c (CryptoPP::Integer::Two (), (q - CryptoPP::Integer::One ()).DividedBy (4), q);
B = DecodePoint (CryptoPP::Integer (4)*CryptoPP::Integer (5).InverseMod (q));
}
CryptoPP::ECP::Point DecodePublicKey (const uint8_t * key) const
{
return DecodePoint (CryptoPP::Integer (key, 32));
}
CryptoPP::ECP::Point GeneratePublicKey (const uint8_t * privateKey) const
{
return Mul (B, CryptoPP::Integer (privateKey, 32));
}
private:
CryptoPP::ECP::Point Sum (const CryptoPP::ECP::Point& p1, const CryptoPP::ECP::Point& p2) const
{
CryptoPP::Integer m = d*p1.x*p2.x*p1.y*p2.y,
x = a_times_b_mod_c (p1.x*p2.y + p2.x*p1.y, (CryptoPP::Integer::One() + m).InverseMod (q), q),
y = a_times_b_mod_c (p1.y*p2.y + p1.x*p2.x, (CryptoPP::Integer::One() - m).InverseMod (q), q);
return CryptoPP::ECP::Point {x, y};
}
CryptoPP::ECP::Point Mul (const CryptoPP::ECP::Point& p, const CryptoPP::Integer& e) const
{
CryptoPP::ECP::Point res {0, 1};
if (!e.IsZero ())
{
auto bitCount = e.BitCount ();
for (int i = bitCount - 1; i >= 0; i--)
{
res = Sum (res, res);
if (e.GetBit (i)) res = Sum (res, p);
}
}
return res;
}
bool IsOnCurve (const CryptoPP::ECP::Point& p) const
{
auto x2 = p.x.Squared(), y2 = p.y.Squared ();
return (y2 - x2 - CryptoPP::Integer::One() - d*x2*y2).Modulo (q).IsZero ();
}
CryptoPP::Integer RecoverX (const CryptoPP::Integer& y) const
{
auto y2 = y.Squared ();
auto xx = (y2 - CryptoPP::Integer::One())*(d*y2 + CryptoPP::Integer::One()).InverseMod (q);
auto x = a_exp_b_mod_c (xx, (q + CryptoPP::Integer (3)).DividedBy (8), q);
if (!(x.Squared () - xx).Modulo (q).IsZero ())
x = a_times_b_mod_c (x, I, q);
if (x.IsOdd ()) x = q - x;
return x;
}
CryptoPP::ECP::Point DecodePoint (const CryptoPP::Integer& y) const
{
auto x = RecoverX (y);
CryptoPP::ECP::Point p {x, y};
if (!IsOnCurve (p))
{
LogPrint (eLogError, "Decoded point is not on 25519");
return CryptoPP::ECP::Point {0, 1};
}
return p;
}
private:
CryptoPP::Integer q, l, d, I;
CryptoPP::ECP::Point B; // base point
};
static std::unique_ptr<Ed25519> g_Ed25519;
std::unique_ptr<Ed25519>& GetEd25519 ()
{
if (!g_Ed25519)
g_Ed25519.reset (new Ed25519 ());
return g_Ed25519;
}
EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey):
m_PublicKey (GetEd25519 ()->DecodePublicKey (signingKey))
{
}
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
return true; // TODO:
}
void EDDSA25519Signer::Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
{
// TODO
}
}
}

445
core/crypto/Signature.h Normal file
View file

@ -0,0 +1,445 @@
#ifndef SIGNATURE_H__
#define SIGNATURE_H__
#include <inttypes.h>
#include <cryptopp/dsa.h>
#include <cryptopp/rsa.h>
#include <cryptopp/asn.h>
#include <cryptopp/oids.h>
#include <cryptopp/osrng.h>
#include <cryptopp/eccrypto.h>
#include "CryptoConst.h"
namespace i2p
{
namespace crypto
{
class Verifier
{
public:
virtual ~Verifier () {};
virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0;
virtual size_t GetPublicKeyLen () const = 0;
virtual size_t GetSignatureLen () const = 0;
virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; };
};
class Signer
{
public:
virtual ~Signer () {};
virtual void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const = 0;
};
const size_t DSA_PUBLIC_KEY_LENGTH = 128;
const size_t DSA_SIGNATURE_LENGTH = 40;
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
class DSAVerifier: public Verifier
{
public:
DSAVerifier (const uint8_t * signingKey)
{
m_PublicKey.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingKey, DSA_PUBLIC_KEY_LENGTH));
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
CryptoPP::DSA::Verifier verifier (m_PublicKey);
return verifier.VerifyMessage (buf, len, signature, DSA_SIGNATURE_LENGTH);
}
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
private:
CryptoPP::DSA::PublicKey m_PublicKey;
};
class DSASigner: public Signer
{
public:
DSASigner (const uint8_t * signingPrivateKey)
{
m_PrivateKey.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH));
}
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
{
CryptoPP::DSA::Signer signer (m_PrivateKey);
signer.SignMessage (rnd, buf, len, signature);
}
private:
CryptoPP::DSA::PrivateKey m_PrivateKey;
};
inline void CreateDSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CryptoPP::DSA::PrivateKey privateKey;
CryptoPP::DSA::PublicKey publicKey;
privateKey.Initialize (rnd, dsap, dsaq, dsag);
privateKey.MakePublicKey (publicKey);
privateKey.GetPrivateExponent ().Encode (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
publicKey.GetPublicElement ().Encode (signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
}
template<typename Hash, size_t keyLen>
class ECDSAVerifier: public Verifier
{
public:
template<typename Curve>
ECDSAVerifier (Curve curve, const uint8_t * signingKey)
{
m_PublicKey.Initialize (curve,
CryptoPP::ECP::Point (CryptoPP::Integer (signingKey, keyLen/2),
CryptoPP::Integer (signingKey + keyLen/2, keyLen/2)));
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::Verifier verifier (m_PublicKey);
return verifier.VerifyMessage (buf, len, signature, keyLen); // signature length
}
size_t GetPublicKeyLen () const { return keyLen; };
size_t GetSignatureLen () const { return keyLen; }; // signature length = key length
private:
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::PublicKey m_PublicKey;
};
template<typename Hash>
class ECDSASigner: public Signer
{
public:
template<typename Curve>
ECDSASigner (Curve curve, const uint8_t * signingPrivateKey, size_t keyLen)
{
m_PrivateKey.Initialize (curve, CryptoPP::Integer (signingPrivateKey, keyLen/2)); // private key length
}
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
{
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::Signer signer (m_PrivateKey);
signer.SignMessage (rnd, buf, len, signature);
}
private:
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::PrivateKey m_PrivateKey;
};
template<typename Hash, typename Curve>
inline void CreateECDSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, Curve curve,
size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::PrivateKey privateKey;
typename CryptoPP::ECDSA<CryptoPP::ECP, Hash>::PublicKey publicKey;
privateKey.Initialize (rnd, curve);
privateKey.MakePublicKey (publicKey);
privateKey.GetPrivateExponent ().Encode (signingPrivateKey, keyLen/2);
auto q = publicKey.GetPublicElement ();
q.x.Encode (signingPublicKey, keyLen/2);
q.y.Encode (signingPublicKey + keyLen/2, keyLen/2);
}
// ECDSA_SHA256_P256
const size_t ECDSAP256_KEY_LENGTH = 64;
class ECDSAP256Verifier: public ECDSAVerifier<CryptoPP::SHA256, ECDSAP256_KEY_LENGTH>
{
public:
ECDSAP256Verifier (const uint8_t * signingKey):
ECDSAVerifier (CryptoPP::ASN1::secp256r1(), signingKey)
{
}
};
class ECDSAP256Signer: public ECDSASigner<CryptoPP::SHA256>
{
public:
ECDSAP256Signer (const uint8_t * signingPrivateKey):
ECDSASigner (CryptoPP::ASN1::secp256r1(), signingPrivateKey, ECDSAP256_KEY_LENGTH)
{
}
};
inline void CreateECDSAP256RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys<CryptoPP::SHA256> (rnd, CryptoPP::ASN1::secp256r1(), ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// ECDSA_SHA384_P384
const size_t ECDSAP384_KEY_LENGTH = 96;
class ECDSAP384Verifier: public ECDSAVerifier<CryptoPP::SHA384, ECDSAP384_KEY_LENGTH>
{
public:
ECDSAP384Verifier (const uint8_t * signingKey):
ECDSAVerifier (CryptoPP::ASN1::secp384r1(), signingKey)
{
}
};
class ECDSAP384Signer: public ECDSASigner<CryptoPP::SHA384>
{
public:
ECDSAP384Signer (const uint8_t * signingPrivateKey):
ECDSASigner (CryptoPP::ASN1::secp384r1(), signingPrivateKey, ECDSAP384_KEY_LENGTH)
{
}
};
inline void CreateECDSAP384RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys<CryptoPP::SHA384> (rnd, CryptoPP::ASN1::secp384r1(), ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// ECDSA_SHA512_P521
const size_t ECDSAP521_KEY_LENGTH = 132;
class ECDSAP521Verifier: public ECDSAVerifier<CryptoPP::SHA512, ECDSAP521_KEY_LENGTH>
{
public:
ECDSAP521Verifier (const uint8_t * signingKey):
ECDSAVerifier (CryptoPP::ASN1::secp521r1(), signingKey)
{
}
};
class ECDSAP521Signer: public ECDSASigner<CryptoPP::SHA512>
{
public:
ECDSAP521Signer (const uint8_t * signingPrivateKey):
ECDSASigner (CryptoPP::ASN1::secp521r1(), signingPrivateKey, ECDSAP521_KEY_LENGTH)
{
}
};
inline void CreateECDSAP521RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys<CryptoPP::SHA512> (rnd, CryptoPP::ASN1::secp521r1(), ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// RSA
template<typename Hash, size_t keyLen>
class RSAVerifier: public Verifier
{
public:
RSAVerifier (const uint8_t * signingKey)
{
m_PublicKey.Initialize (CryptoPP::Integer (signingKey, keyLen), CryptoPP::Integer (rsae));
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
typename CryptoPP::RSASS<CryptoPP::PKCS1v15, Hash>::Verifier verifier (m_PublicKey);
return verifier.VerifyMessage (buf, len, signature, keyLen); // signature length
}
size_t GetPublicKeyLen () const { return keyLen; }
size_t GetSignatureLen () const { return keyLen; }
size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; };
private:
CryptoPP::RSA::PublicKey m_PublicKey;
};
template<typename Hash>
class RSASigner: public Signer
{
public:
RSASigner (const uint8_t * signingPrivateKey, size_t keyLen)
{
m_PrivateKey.Initialize (CryptoPP::Integer (signingPrivateKey, keyLen/2),
rsae,
CryptoPP::Integer (signingPrivateKey + keyLen/2, keyLen/2));
}
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const
{
typename CryptoPP::RSASS<CryptoPP::PKCS1v15, Hash>::Signer signer (m_PrivateKey);
signer.SignMessage (rnd, buf, len, signature);
}
private:
CryptoPP::RSA::PrivateKey m_PrivateKey;
};
inline void CreateRSARandomKeys (CryptoPP::RandomNumberGenerator& rnd,
size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CryptoPP::RSA::PrivateKey privateKey;
privateKey.Initialize (rnd, publicKeyLen*8, rsae);
privateKey.GetModulus ().Encode (signingPrivateKey, publicKeyLen);
privateKey.GetPrivateExponent ().Encode (signingPrivateKey + publicKeyLen, publicKeyLen);
privateKey.GetModulus ().Encode (signingPublicKey, publicKeyLen);
}
// RSA_SHA256_2048
const size_t RSASHA2562048_KEY_LENGTH = 256;
class RSASHA2562048Verifier: public RSAVerifier<CryptoPP::SHA256, RSASHA2562048_KEY_LENGTH>
{
public:
RSASHA2562048Verifier (const uint8_t * signingKey): RSAVerifier (signingKey)
{
}
};
class RSASHA2562048Signer: public RSASigner<CryptoPP::SHA256>
{
public:
RSASHA2562048Signer (const uint8_t * signingPrivateKey):
RSASigner (signingPrivateKey, RSASHA2562048_KEY_LENGTH*2)
{
}
};
// RSA_SHA384_3072
const size_t RSASHA3843072_KEY_LENGTH = 384;
class RSASHA3843072Verifier: public RSAVerifier<CryptoPP::SHA384, RSASHA3843072_KEY_LENGTH>
{
public:
RSASHA3843072Verifier (const uint8_t * signingKey): RSAVerifier (signingKey)
{
}
};
class RSASHA3843072Signer: public RSASigner<CryptoPP::SHA384>
{
public:
RSASHA3843072Signer (const uint8_t * signingPrivateKey):
RSASigner (signingPrivateKey, RSASHA3843072_KEY_LENGTH*2)
{
}
};
// RSA_SHA512_4096
const size_t RSASHA5124096_KEY_LENGTH = 512;
class RSASHA5124096Verifier: public RSAVerifier<CryptoPP::SHA512, RSASHA5124096_KEY_LENGTH>
{
public:
RSASHA5124096Verifier (const uint8_t * signingKey): RSAVerifier (signingKey)
{
}
};
class RSASHA5124096Signer: public RSASigner<CryptoPP::SHA512>
{
public:
RSASHA5124096Signer (const uint8_t * signingPrivateKey):
RSASigner (signingPrivateKey, RSASHA5124096_KEY_LENGTH*2)
{
}
};
// Raw verifiers
class RawVerifier
{
public:
virtual ~RawVerifier () {};
virtual void Update (const uint8_t * buf, size_t len) = 0;
virtual bool Verify (const uint8_t * signature) = 0;
};
template<typename Hash, size_t keyLen>
class RSARawVerifier: public RawVerifier
{
public:
RSARawVerifier (const uint8_t * signingKey):
n (signingKey, keyLen)
{
}
void Update (const uint8_t * buf, size_t len)
{
m_Hash.Update (buf, len);
}
bool Verify (const uint8_t * signature)
{
// RSA encryption first
CryptoPP::Integer enSig (a_exp_b_mod_c (CryptoPP::Integer (signature, keyLen),
CryptoPP::Integer (i2p::crypto::rsae), n)); // s^e mod n
uint8_t enSigBuf[keyLen];
enSig.Encode (enSigBuf, keyLen);
uint8_t digest[Hash::DIGESTSIZE];
m_Hash.Final (digest);
if ((int)keyLen < Hash::DIGESTSIZE) return false; // can't verify digest longer than key
// we assume digest is right aligned, at least for PKCS#1 v1.5 padding
return !memcmp (enSigBuf + (keyLen - Hash::DIGESTSIZE), digest, Hash::DIGESTSIZE);
}
private:
CryptoPP::Integer n; // RSA modulus
Hash m_Hash;
};
class RSASHA5124096RawVerifier: public RSARawVerifier<CryptoPP::SHA512, RSASHA5124096_KEY_LENGTH>
{
public:
RSASHA5124096RawVerifier (const uint8_t * signingKey): RSARawVerifier (signingKey)
{
}
};
// EdDSA
const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32;
const size_t EDDSA25519_SIGNATURE_LENGTH = 64;
const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32;
class EDDSA25519Verifier: public Verifier
{
public:
EDDSA25519Verifier (const uint8_t * signingKey);
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; };
private:
CryptoPP::ECP::Point m_PublicKey;
};
class EDDSA25519Signer: public Signer
{
public:
EDDSA25519Signer (const uint8_t * signingPrivateKey) {};
void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const;
};
}
}
#endif

222
core/crypto/aes.cpp Normal file
View file

@ -0,0 +1,222 @@
#include <stdlib.h>
#include "aes.h"
namespace i2p {
namespace crypto {
#ifdef AESNI
#include "AESNIMacros.h"
void ECBCryptoAESNI::ExpandKey (const AESKey& key)
{
__asm__
(
"movups (%[key]), %%xmm1 \n"
"movups 16(%[key]), %%xmm3 \n"
"movaps %%xmm1, (%[sched]) \n"
"movaps %%xmm3, 16(%[sched]) \n"
"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
KeyExpansion256(32,48)
"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
KeyExpansion256(64,80)
"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
KeyExpansion256(96,112)
"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
KeyExpansion256(128,144)
"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
KeyExpansion256(160,176)
"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
KeyExpansion256(192,208)
"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
// key expansion final
"pshufd $0xff, %%xmm2, %%xmm2 \n"
"movaps %%xmm1, %%xmm4 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pxor %%xmm2, %%xmm1 \n"
"movups %%xmm1, 224(%[sched]) \n"
: // output
: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
);
}
void ECBEncryptionAESNI::Encrypt (const CipherBlock * in, CipherBlock * out)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
);
}
void ECBDecryptionAESNI::Decrypt (const CipherBlock * in, CipherBlock * out)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
);
}
void ECBDecryptionAESNI::SetKey (const AESKey& key)
{
ExpandKey (key); // expand encryption key first
// then invert it using aesimc
__asm__
(
CallAESIMC(16)
CallAESIMC(32)
CallAESIMC(48)
CallAESIMC(64)
CallAESIMC(80)
CallAESIMC(96)
CallAESIMC(112)
CallAESIMC(128)
CallAESIMC(144)
CallAESIMC(160)
CallAESIMC(176)
CallAESIMC(192)
CallAESIMC(208)
: : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory"
);
}
#endif
void CBCEncryption::Encrypt (int numBlocks, const CipherBlock * in, CipherBlock * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "cc", "memory"
);
#else
for (int i = 0; i < numBlocks; i++)
{
m_LastBlock ^= in[i];
m_ECBEncryption.Encrypt (&m_LastBlock, &m_LastBlock);
out[i] = m_LastBlock;
}
#endif
}
void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
{
// len/16
int numBlocks = len >> 4;
if (numBlocks > 0)
Encrypt (numBlocks, (const CipherBlock *)in, (CipherBlock *)out);
}
void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
"movups %%xmm0, (%[iv]) \n"
:
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
#else
Encrypt (1, (const CipherBlock *)in, (CipherBlock *)out);
#endif
}
void CBCDecryption::Decrypt (int numBlocks, const CipherBlock * in, CipherBlock * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
#else
for (int i = 0; i < numBlocks; i++)
{
CipherBlock tmp = in[i];
m_ECBDecryption.Decrypt (in + i, out + i);
out[i] ^= m_IV;
m_IV = tmp;
}
#endif
}
void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
{
int numBlocks = len >> 4;
if (numBlocks > 0)
Decrypt (numBlocks, (const CipherBlock *)in, (CipherBlock *)out);
}
void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"movups %%xmm0, (%[iv]) \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
:
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
#else
Decrypt (1, (const CipherBlock *)in, (CipherBlock *)out);
#endif
}
} // crypto
} // i2p

194
core/crypto/aes.h Normal file
View file

@ -0,0 +1,194 @@
#ifndef AES_H__
#define AES_H__
#include <inttypes.h>
#include <cryptopp/modes.h>
#include <cryptopp/aes.h>
#include "Identity.h"
namespace i2p
{
namespace crypto
{
struct CipherBlock
{
uint8_t buf[16];
void operator^=(const CipherBlock& other) // XOR
{
#if defined(__x86_64__) // for Intel x64
__asm__
(
"movups (%[buf]), %%xmm0 \n"
"movups (%[other]), %%xmm1 \n"
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[buf]) \n"
:
: [buf]"r"(buf), [other]"r"(other.buf)
: "%xmm0", "%xmm1", "memory"
);
#else
// TODO: implement it better
for (int i = 0; i < 16; i++)
buf[i] ^= other.buf[i];
#endif
}
};
typedef i2p::data::Tag<32> AESKey;
template<size_t sz>
class AESAlignedBuffer // 16 bytes alignment
{
public:
AESAlignedBuffer ()
{
m_Buf = m_UnalignedBuffer;
uint8_t rem = ((size_t)m_Buf) & 0x0f;
if (rem)
m_Buf += (16 - rem);
}
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
private:
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
uint8_t * m_Buf;
};
#ifdef AESNI
class ECBCryptoAESNI
{
public:
uint8_t * GetKeySchedule () { return m_KeySchedule; };
protected:
void ExpandKey (const AESKey& key);
private:
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
};
class ECBEncryptionAESNI: public ECBCryptoAESNI
{
public:
void SetKey (const AESKey& key) { ExpandKey (key); };
void Encrypt (const CipherBlock * in, CipherBlock * out);
};
class ECBDecryptionAESNI: public ECBCryptoAESNI
{
public:
void SetKey (const AESKey& key);
void Decrypt (const CipherBlock * in, CipherBlock * out);
};
typedef ECBEncryptionAESNI ECBEncryption;
typedef ECBDecryptionAESNI ECBDecryption;
#else // use crypto++
class ECBEncryption
{
public:
void SetKey (const AESKey& key)
{
m_Encryption.SetKey (key, 32);
}
void Encrypt (const CipherBlock * in, CipherBlock * out)
{
m_Encryption.ProcessData (out->buf, in->buf, 16);
}
private:
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption m_Encryption;
};
class ECBDecryption
{
public:
void SetKey (const AESKey& key)
{
m_Decryption.SetKey (key, 32);
}
void Decrypt (const CipherBlock * in, CipherBlock * out)
{
m_Decryption.ProcessData (out->buf, in->buf, 16);
}
private:
CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption m_Decryption;
};
#endif
class CBCEncryption
{
public:
CBCEncryption () { memset (m_LastBlock.buf, 0, 16); };
CBCEncryption(const AESKey& key, const uint8_t* iv)
: CBCEncryption()
{
SetKey(key);
SetIV(iv);
}
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy (m_LastBlock.buf, iv, 16); }; // 16 bytes
void Encrypt (int numBlocks, const CipherBlock * in, CipherBlock * out);
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Encrypt (const uint8_t * in, uint8_t * out); // one block
private:
CipherBlock m_LastBlock;
ECBEncryption m_ECBEncryption;
};
class CBCDecryption
{
public:
CBCDecryption () { memset (m_IV.buf, 0, 16); };
CBCDecryption(const AESKey& key, const uint8_t* iv)
: CBCDecryption()
{
SetKey(key);
SetIV(iv);
}
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy (m_IV.buf, iv, 16); }; // 16 bytes
void Decrypt (int numBlocks, const CipherBlock * in, CipherBlock * out);
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Decrypt (const uint8_t * in, uint8_t * out); // one block
private:
CipherBlock m_IV;
ECBDecryption m_ECBDecryption;
};
}
}
#endif

60
core/crypto/hmac.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef HMAC_H__
#define HMAC_H__
#include <inttypes.h>
#include <string.h>
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <cryptopp/md5.h>
#include "Identity.h"
namespace i2p
{
namespace crypto
{
const uint64_t IPAD = 0x3636363636363636;
const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C;
typedef i2p::data::Tag<32> MACKey;
inline void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest)
// key is 32 bytes
// digest is 16 bytes
// block size is 64 bytes
{
uint64_t buf[256];
// ikeypad
buf[0] = key.GetLL ()[0] ^ IPAD;
buf[1] = key.GetLL ()[1] ^ IPAD;
buf[2] = key.GetLL ()[2] ^ IPAD;
buf[3] = key.GetLL ()[3] ^ IPAD;
buf[4] = IPAD;
buf[5] = IPAD;
buf[6] = IPAD;
buf[7] = IPAD;
// concatenate with msg
memcpy (buf + 8, msg, len);
// calculate first hash
uint8_t hash[16]; // MD5
CryptoPP::Weak1::MD5().CalculateDigest (hash, (uint8_t *)buf, len + 64);
// okeypad
buf[0] = key.GetLL ()[0] ^ OPAD;
buf[1] = key.GetLL ()[1] ^ OPAD;
buf[2] = key.GetLL ()[2] ^ OPAD;
buf[3] = key.GetLL ()[3] ^ OPAD;
buf[4] = OPAD;
buf[5] = OPAD;
buf[6] = OPAD;
buf[7] = OPAD;
// copy first hash after okeypad
memcpy (buf + 8, hash, 16);
// fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P)
memset (buf + 10, 0, 16);
// calculate digest
CryptoPP::Weak1::MD5().CalculateDigest (digest, (uint8_t *)buf, 96);
}
}
}
#endif

View file

@ -0,0 +1,936 @@
#include <string.h>
#include <stdlib.h>
#include "util/I2PEndian.h"
#include <cryptopp/dh.h>
#include <cryptopp/adler32.h>
#include "util/base64.h"
#include "util/Log.h"
#include "util/Timestamp.h"
#include "crypto/CryptoConst.h"
#include "I2NPProtocol.h"
#include "RouterContext.h"
#include "Transports.h"
#include "NetDb.h"
#include "NTCPSession.h"
using namespace i2p::crypto;
namespace i2p
{
namespace transport
{
NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter):
TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()),
m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false),
m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false)
{
m_DHKeysPair = transports.GetNextDHKeysPair ();
m_Establisher = new Establisher;
}
NTCPSession::~NTCPSession ()
{
delete m_Establisher;
}
void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key)
{
CryptoPP::DH dh (elgp, elgg);
uint8_t sharedKey[256];
if (!dh.Agree (sharedKey, m_DHKeysPair->privateKey, pubKey))
{
LogPrint (eLogError, "Couldn't create shared key");
Terminate ();
return;
};
uint8_t * aesKey = key;
if (sharedKey[0] & 0x80)
{
aesKey[0] = 0;
memcpy (aesKey + 1, sharedKey, 31);
}
else if (sharedKey[0])
memcpy (aesKey, sharedKey, 32);
else
{
// find first non-zero byte
uint8_t * nonZero = sharedKey + 1;
while (!*nonZero)
{
nonZero++;
if (nonZero - sharedKey > 32)
{
LogPrint (eLogWarning, "First 32 bytes of shared key is all zeros. Ignored");
return;
}
}
memcpy (aesKey, nonZero, 32);
}
}
void NTCPSession::Done ()
{
m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ()));
}
void NTCPSession::Terminate ()
{
if (!m_IsTerminated)
{
m_IsTerminated = true;
m_IsEstablished = false;
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCPSession (shared_from_this ());
m_SendQueue.clear ();
m_NextMessage = nullptr;
m_TerminationTimer.cancel ();
LogPrint (eLogInfo, "NTCP session terminated");
}
}
void NTCPSession::Connected ()
{
m_IsEstablished = true;
delete m_Establisher;
m_Establisher = nullptr;
delete m_DHKeysPair;
m_DHKeysPair = nullptr;
SendTimeSyncMessage ();
m_SendQueue.push_back (CreateDatabaseStoreMsg ()); // we tell immediately who we are
transports.PeerConnected (shared_from_this ());
}
void NTCPSession::ClientLogin ()
{
if (!m_DHKeysPair)
m_DHKeysPair = transports.GetNextDHKeysPair ();
// send Phase1
const uint8_t * x = m_DHKeysPair->publicKey;
memcpy (m_Establisher->phase1.pubKey, x, 256);
CryptoPP::SHA256().CalculateDigest(m_Establisher->phase1.HXxorHI, x, 256);
const uint8_t * ident = m_RemoteIdentity.GetIdentHash ();
for (int i = 0; i < 32; i++)
m_Establisher->phase1.HXxorHI[i] ^= ident[i];
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
ScheduleTermination ();
}
void NTCPSession::ServerLogin ()
{
boost::system::error_code ec;
auto ep = m_Socket.remote_endpoint(ec);
if (!ec)
{
m_ConnectedFrom = ep.address ();
// receive Phase1
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
ScheduleTermination ();
}
}
void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "Couldn't send Phase 1 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
}
void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "Phase 1 read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
// verify ident
uint8_t digest[32];
CryptoPP::SHA256().CalculateDigest(digest, m_Establisher->phase1.pubKey, 256);
const uint8_t * ident = i2p::context.GetRouterInfo ().GetIdentHash ();
for (int i = 0; i < 32; i++)
{
if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i])
{
LogPrint (eLogError, "Wrong ident");
Terminate ();
return;
}
}
SendPhase2 ();
}
}
void NTCPSession::SendPhase2 ()
{
if (!m_DHKeysPair)
m_DHKeysPair = transports.GetNextDHKeysPair ();
const uint8_t * y = m_DHKeysPair->publicKey;
memcpy (m_Establisher->phase2.pubKey, y, 256);
uint8_t xy[512];
memcpy (xy, m_Establisher->phase1.pubKey, 256);
memcpy (xy + 256, y, 256);
CryptoPP::SHA256().CalculateDigest(m_Establisher->phase2.encrypted.hxy, xy, 512);
uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ());
m_Establisher->phase2.encrypted.timestamp = tsB;
// TODO: fill filler
i2p::crypto::AESKey aesKey;
CreateAESKey (m_Establisher->phase1.pubKey, aesKey);
m_Encryption.SetKey (aesKey);
m_Encryption.SetIV (y + 240);
m_Decryption.SetKey (aesKey);
m_Decryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase2Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB));
}
void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
{
if (ecode)
{
LogPrint (eLogError, "Couldn't send Phase 2 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsB));
}
}
void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "Phase 2 read error: ", ecode.message (), ". Wrong ident assumed");
if (ecode != boost::asio::error::operation_aborted)
{
// this RI is not valid
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true);
transports.ReuseDHKeysPair (m_DHKeysPair);
m_DHKeysPair = nullptr;
Terminate ();
}
}
else
{
i2p::crypto::AESKey aesKey;
CreateAESKey (m_Establisher->phase2.pubKey, aesKey);
m_Decryption.SetKey (aesKey);
m_Decryption.SetIV (m_Establisher->phase2.pubKey + 240);
m_Encryption.SetKey (aesKey);
m_Encryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
// verify
uint8_t xy[512];
memcpy (xy, m_DHKeysPair->publicKey, 256);
memcpy (xy + 256, m_Establisher->phase2.pubKey, 256);
if (!CryptoPP::SHA256().VerifyDigest(m_Establisher->phase2.encrypted.hxy, xy, 512))
{
LogPrint (eLogError, "Incorrect hash");
transports.ReuseDHKeysPair (m_DHKeysPair);
m_DHKeysPair = nullptr;
Terminate ();
return ;
}
SendPhase3 ();
}
}
void NTCPSession::SendPhase3 ()
{
auto keys = i2p::context.GetPrivateKeys ();
uint8_t * buf = m_ReceiveBuffer;
htobe16buf (buf, keys.GetPublic ().GetFullLen ());
buf += 2;
buf += i2p::context.GetIdentity ().ToBuffer (buf, NTCP_BUFFER_SIZE);
uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ());
htobuf32(buf,tsA);
buf += 4;
size_t signatureLen = keys.GetPublic ().GetSignatureLen ();
size_t len = (buf - m_ReceiveBuffer) + signatureLen;
size_t paddingSize = len & 0x0F; // %16
if (paddingSize > 0)
{
paddingSize = 16 - paddingSize;
// TODO: fill padding with random data
buf += paddingSize;
len += paddingSize;
}
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (m_RemoteIdentity.GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB
s.Sign (keys, buf);
m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, len), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsA));
}
void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
{
if (ecode)
{
LogPrint (eLogError, "Couldn't send Phase 3 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
// wait for phase4
auto signatureLen = m_RemoteIdentity.GetSignatureLen ();
size_t paddingSize = signatureLen & 0x0F; // %16
if (paddingSize > 0) signatureLen += (16 - paddingSize);
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase4Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsA));
}
}
void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
{
if (ecode)
{
LogPrint (eLogError, "Phase 3 read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
uint8_t * buf = m_ReceiveBuffer;
uint16_t size = bufbe16toh (buf);
m_RemoteIdentity.FromBuffer (buf + 2, size);
if (m_Server.FindNTCPSession (m_RemoteIdentity.GetIdentHash ()))
{
LogPrint (eLogError, "NTCP session already exists");
Terminate ();
}
size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity.GetSignatureLen ();
size_t paddingLen = expectedSize & 0x0F;
if (paddingLen) paddingLen = (16 - paddingLen);
if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE)
{
// we need more bytes for Phase3
expectedSize += paddingLen;
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsB, paddingLen));
}
else
HandlePhase3 (tsB, paddingLen);
}
}
void NTCPSession::HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen)
{
if (ecode)
{
LogPrint (eLogError, "Phase 3 extra read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE);
HandlePhase3 (tsB, paddingLen);
}
}
void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen)
{
uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity.GetFullLen () + 2 /*size*/;
uint32_t tsA = buf32toh(buf);
buf += 4;
buf += paddingLen;
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (tsB); // tsB
if (!s.Verify (m_RemoteIdentity, buf))
{
LogPrint (eLogError, "signature verification failed");
Terminate ();
return;
}
m_RemoteIdentity.DropVerifier ();
SendPhase4 (tsA, tsB);
}
void NTCPSession::SendPhase4 (uint32_t tsA, uint32_t tsB)
{
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (m_RemoteIdentity.GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (tsB); // tsB
auto keys = i2p::context.GetPrivateKeys ();
auto signatureLen = keys.GetPublic ().GetSignatureLen ();
s.Sign (keys, m_ReceiveBuffer);
size_t paddingSize = signatureLen & 0x0F; // %16
if (paddingSize > 0) signatureLen += (16 - paddingSize);
m_Encryption.Encrypt (m_ReceiveBuffer, signatureLen, m_ReceiveBuffer);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase4Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogWarning, "Couldn't send Phase 4 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
LogPrint (eLogInfo, "NTCP server session from ", m_Socket.remote_endpoint (), " connected");
m_Server.AddNTCPSession (shared_from_this ());
Connected ();
m_ReceiveBufferOffset = 0;
m_NextMessage = nullptr;
Receive ();
}
}
void NTCPSession::HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
{
if (ecode)
{
LogPrint (eLogError, "Phase 4 read error: ", ecode.message (), ". Check your clock");
if (ecode != boost::asio::error::operation_aborted)
{
// this router doesn't like us
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true);
Terminate ();
}
}
else
{
m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
// verify signature
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB
if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer))
{
LogPrint (eLogError, "signature verification failed");
Terminate ();
return;
}
m_RemoteIdentity.DropVerifier ();
LogPrint (eLogInfo, "NTCP session to ", m_Socket.remote_endpoint (), " connected");
Connected ();
m_ReceiveBufferOffset = 0;
m_NextMessage = nullptr;
Receive ();
}
}
void NTCPSession::Receive ()
{
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, NTCP_BUFFER_SIZE - m_ReceiveBufferOffset),
std::bind(&NTCPSession::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "Read error: ", ecode.message ());
if (!m_NumReceivedBytes) m_Server.Ban (m_ConnectedFrom);
//if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_NumReceivedBytes += bytes_transferred;
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
m_ReceiveBufferOffset += bytes_transferred;
if (m_ReceiveBufferOffset >= 16)
{
int numReloads = 0;
do
{
uint8_t * nextBlock = m_ReceiveBuffer;
while (m_ReceiveBufferOffset >= 16)
{
if (!DecryptNextBlock (nextBlock)) // 16 bytes
{
Terminate ();
return;
}
nextBlock += 16;
m_ReceiveBufferOffset -= 16;
}
if (m_ReceiveBufferOffset > 0)
memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset);
// try to read more
if (numReloads < 5)
{
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
if (moreBytes)
{
if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset)
moreBytes = NTCP_BUFFER_SIZE - m_ReceiveBufferOffset;
moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes));
if (ec)
{
LogPrint (eLogError, "Read more bytes error: ", ec.message ());
Terminate ();
return;
}
m_NumReceivedBytes += moreBytes;
m_ReceiveBufferOffset += moreBytes;
numReloads++;
}
}
}
while (m_ReceiveBufferOffset >= 16);
m_Handler.Flush ();
}
ScheduleTermination (); // reset termination timer
Receive ();
}
}
bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes
{
if (!m_NextMessage) // new message, header expected
{
// descrypt header and extract length
uint8_t buf[16];
m_Decryption.Decrypt (encrypted, buf);
uint16_t dataSize = bufbe16toh (buf);
if (dataSize)
{
// new message
if (dataSize > NTCP_MAX_MESSAGE_SIZE)
{
LogPrint (eLogError, "NTCP data size ", dataSize, " exceeds max size");
return false;
}
auto msg = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage ();
m_NextMessage = ToSharedI2NPMessage (msg);
memcpy (m_NextMessage->buf, buf, 16);
m_NextMessageOffset = 16;
m_NextMessage->offset = 2; // size field
m_NextMessage->len = dataSize + 2;
}
else
{
// timestamp
LogPrint ("Timestamp");
return true;
}
}
else // message continues
{
m_Decryption.Decrypt (encrypted, m_NextMessage->buf + m_NextMessageOffset);
m_NextMessageOffset += 16;
}
if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum
{
// we have a complete I2NP message
if (CryptoPP::Adler32().VerifyDigest (m_NextMessage->buf + m_NextMessageOffset - 4, m_NextMessage->buf, m_NextMessageOffset - 4))
m_Handler.PutNextMessage (m_NextMessage);
else
LogPrint (eLogWarning, "Incorrect adler checksum of NTCP message. Dropped");
m_NextMessage = nullptr;
}
return true;
}
void NTCPSession::Send (std::shared_ptr<i2p::I2NPMessage> msg)
{
m_IsSending = true;
boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector<std::shared_ptr<I2NPMessage> >{ msg }));
}
boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg)
{
uint8_t * sendBuffer;
int len;
if (msg)
{
// regular I2NP
if (msg->offset < 2)
LogPrint (eLogError, "Malformed I2NP message"); // TODO:
sendBuffer = msg->GetBuffer () - 2;
len = msg->GetLength ();
htobe16buf (sendBuffer, len);
}
else
{
// prepare timestamp
sendBuffer = m_TimeSyncBuffer;
len = 4;
htobuf16(sendBuffer, 0);
htobe32buf (sendBuffer + 2, time (0));
}
int rem = (len + 6) & 0x0F; // %16
int padding = 0;
if (rem > 0) padding = 16 - rem;
// TODO: fill padding
CryptoPP::Adler32().CalculateDigest (sendBuffer + len + 2 + padding, sendBuffer, len + 2+ padding);
int l = len + padding + 6;
m_Encryption.Encrypt(sendBuffer, l, sendBuffer);
return boost::asio::buffer ((const uint8_t *)sendBuffer, l);
}
void NTCPSession::Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_IsSending = true;
std::vector<boost::asio::const_buffer> bufs;
for (auto it: msgs)
bufs.push_back (CreateMsgBuffer (it));
boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (),
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs));
}
void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
m_IsSending = false;
if (ecode)
{
LogPrint (eLogWarning, "Couldn't send msgs: ", ecode.message ());
// we shouldn't call Terminate () here, because HandleReceive takes care
// TODO: 'delete this' statement in Terminate () must be eliminated later
// Terminate ();
}
else
{
m_NumSentBytes += bytes_transferred;
i2p::transport::transports.UpdateSentBytes (bytes_transferred);
if (!m_SendQueue.empty())
{
Send (m_SendQueue);
m_SendQueue.clear ();
}
else
ScheduleTermination (); // reset termination timer
}
}
void NTCPSession::SendTimeSyncMessage ()
{
Send (nullptr);
}
void NTCPSession::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs));
}
void NTCPSession::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
if (m_IsTerminated) return;
if (m_IsSending)
{
for (auto it: msgs)
m_SendQueue.push_back (it);
}
else
Send (msgs);
}
void NTCPSession::ScheduleTermination ()
{
m_TerminationTimer.cancel ();
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT));
m_TerminationTimer.async_wait (std::bind (&NTCPSession::HandleTerminationTimer,
shared_from_this (), std::placeholders::_1));
}
void NTCPSession::HandleTerminationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
LogPrint ("No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds");
//Terminate ();
m_Socket.close ();// invoke Terminate () from HandleReceive
}
}
//-----------------------------------------
NTCPServer::NTCPServer (int port):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr)
{
}
NTCPServer::~NTCPServer ()
{
Stop ();
}
void NTCPServer::Start ()
{
if (!m_IsRunning)
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&NTCPServer::Run, this));
// create acceptors
auto addresses = context.GetRouterInfo ().GetAddresses ();
for (auto& address : addresses)
{
if (address.transportStyle == i2p::data::RouterInfo::eTransportNTCP && address.host.is_v4 ())
{
m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port));
LogPrint (eLogInfo, "Start listening TCP port ", address.port);
auto conn = std::make_shared<NTCPSession>(*this);
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
conn, std::placeholders::_1));
if (context.SupportsV6 ())
{
m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);
m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true));
m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port));
m_NTCPV6Acceptor->listen ();
LogPrint (eLogInfo, "Start listening V6 TCP port ", address.port);
auto conn = std::make_shared<NTCPSession> (*this);
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
this, conn, std::placeholders::_1));
}
}
}
}
}
void NTCPServer::Stop ()
{
m_NTCPSessions.clear ();
if (m_IsRunning)
{
m_IsRunning = false;
delete m_NTCPAcceptor;
m_NTCPAcceptor = nullptr;
delete m_NTCPV6Acceptor;
m_NTCPV6Acceptor = nullptr;
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
}
void NTCPServer::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint ("NTCP server: ", ex.what ());
}
}
}
void NTCPServer::AddNTCPSession (std::shared_ptr<NTCPSession> session)
{
if (session)
{
std::unique_lock<std::mutex> l(m_NTCPSessionsMutex);
m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session;
}
}
void NTCPServer::RemoveNTCPSession (std::shared_ptr<NTCPSession> session)
{
if (session)
{
std::unique_lock<std::mutex> l(m_NTCPSessionsMutex);
m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ());
}
}
std::shared_ptr<NTCPSession> NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident)
{
std::unique_lock<std::mutex> l(m_NTCPSessionsMutex);
auto it = m_NTCPSessions.find (ident);
if (it != m_NTCPSessions.end ())
return it->second;
return nullptr;
}
void NTCPServer::HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
{
if (!error)
{
boost::system::error_code ec;
auto ep = conn->GetSocket ().remote_endpoint(ec);
if (!ec)
{
LogPrint (eLogInfo, "Connected from ", ep);
auto it = m_BanList.find (ep.address ());
if (it != m_BanList.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second)
{
LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds");
conn = nullptr;
}
else
m_BanList.erase (it);
}
if (conn)
conn->ServerLogin ();
}
else
LogPrint (eLogError, "Connected from error ", ec.message ());
}
if (error != boost::asio::error::operation_aborted)
{
conn = std::make_shared<NTCPSession> (*this);
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
conn, std::placeholders::_1));
}
}
void NTCPServer::HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
{
if (!error)
{
boost::system::error_code ec;
auto ep = conn->GetSocket ().remote_endpoint(ec);
if (!ec)
{
LogPrint (eLogInfo, "Connected from ", ep);
auto it = m_BanList.find (ep.address ());
if (it != m_BanList.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second)
{
LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds");
conn = nullptr;
}
else
m_BanList.erase (it);
}
if (conn)
conn->ServerLogin ();
}
else
LogPrint (eLogError, "Connected from error ", ec.message ());
}
if (error != boost::asio::error::operation_aborted)
{
conn = std::make_shared<NTCPSession> (*this);
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this,
conn, std::placeholders::_1));
}
}
void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn)
{
LogPrint (eLogInfo, "Connecting to ", address ,":", port);
m_Service.post([conn, this]()
{
this->AddNTCPSession (conn);
});
conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port),
std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn));
}
void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn)
{
if (ecode)
{
LogPrint (eLogError, "Connect error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true);
conn->Terminate ();
}
else
{
LogPrint (eLogInfo, "Connected to ", conn->GetSocket ().remote_endpoint ());
if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ());
conn->ClientLogin ();
}
}
void NTCPServer::Ban (const boost::asio::ip::address& addr)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT;
LogPrint (eLogInfo, addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds");
}
}
}

View file

@ -0,0 +1,183 @@
#ifndef NTCP_SESSION_H__
#define NTCP_SESSION_H__
#include <inttypes.h>
#include <map>
#include <memory>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
#include <cryptopp/modes.h>
#include <cryptopp/aes.h>
#include "crypto/aes.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "TransportSession.h"
namespace i2p
{
namespace transport
{
#pragma pack(1)
struct NTCPPhase1
{
uint8_t pubKey[256];
uint8_t HXxorHI[32];
};
struct NTCPPhase2
{
uint8_t pubKey[256];
struct
{
uint8_t hxy[32];
uint32_t timestamp;
uint8_t filler[12];
} encrypted;
};
#pragma pack()
const size_t NTCP_MAX_MESSAGE_SIZE = 16384;
const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028)
const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes
const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448
const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second
class NTCPServer;
class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession>
{
public:
NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr);
~NTCPSession ();
void Terminate ();
void Done ();
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
bool IsEstablished () const { return m_IsEstablished; };
void ClientLogin ();
void ServerLogin ();
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
private:
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void Connected ();
void SendTimeSyncMessage ();
void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; }
void CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key);
// client
void SendPhase3 ();
void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
//server
void SendPhase2 ();
void SendPhase4 (uint32_t tsA, uint32_t tsB);
void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen);
void HandlePhase3 (uint32_t tsB, size_t paddingLen);
void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
// common
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
bool DecryptNextBlock (const uint8_t * encrypted);
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg);
void Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs);
// timer
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
private:
NTCPServer& m_Server;
boost::asio::ip::tcp::socket m_Socket;
boost::asio::deadline_timer m_TerminationTimer;
bool m_IsEstablished, m_IsTerminated;
i2p::crypto::CBCDecryption m_Decryption;
i2p::crypto::CBCEncryption m_Encryption;
struct Establisher
{
NTCPPhase1 phase1;
NTCPPhase2 phase2;
} * m_Establisher;
i2p::crypto::AESAlignedBuffer<NTCP_BUFFER_SIZE + 16> m_ReceiveBuffer;
i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer;
int m_ReceiveBufferOffset;
std::shared_ptr<I2NPMessage> m_NextMessage;
size_t m_NextMessageOffset;
i2p::I2NPMessagesHandler m_Handler;
bool m_IsSending;
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
boost::asio::ip::address m_ConnectedFrom; // for ban
};
// TODO: move to NTCP.h/.cpp
class NTCPServer
{
public:
NTCPServer (int port);
~NTCPServer ();
void Start ();
void Stop ();
void AddNTCPSession (std::shared_ptr<NTCPSession> session);
void RemoveNTCPSession (std::shared_ptr<NTCPSession> session);
std::shared_ptr<NTCPSession> FindNTCPSession (const i2p::data::IdentHash& ident);
void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn);
boost::asio::io_service& GetService () { return m_Service; };
void Ban (const boost::asio::ip::address& addr);
private:
void Run ();
void HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
void HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor;
std::mutex m_NTCPSessionsMutex;
std::map<i2p::data::IdentHash, std::shared_ptr<NTCPSession> > m_NTCPSessions;
std::map<boost::asio::ip::address, uint32_t> m_BanList; // IP -> ban expiration time in seconds
public:
// for HTTP/I2PControl
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
};
}
}
#endif

556
core/transport/SSU.cpp Normal file
View file

@ -0,0 +1,556 @@
#include <string.h>
#include <boost/bind.hpp>
#include "util/Log.h"
#include "util/Timestamp.h"
#include "RouterContext.h"
#include "NetDb.h"
#include "SSU.h"
namespace i2p
{
namespace transport
{
SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService),
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService),
m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service)
{
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535));
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535));
if (context.SupportsV6 ())
{
m_SocketV6.open (boost::asio::ip::udp::v6());
m_SocketV6.set_option (boost::asio::ip::v6_only (true));
m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (65535));
m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (65535));
m_SocketV6.bind (m_EndpointV6);
}
}
SSUServer::~SSUServer ()
{
}
void SSUServer::Start ()
{
m_IsRunning = true;
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
if (context.SupportsV6 ())
{
m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this));
m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this));
}
SchedulePeerTestsCleanupTimer ();
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
}
void SSUServer::Stop ()
{
DeleteAllSessions ();
m_IsRunning = false;
m_Service.stop ();
m_Socket.close ();
m_ServiceV6.stop ();
m_SocketV6.close ();
m_ReceiversService.stop ();
if (m_ReceiversThread)
{
m_ReceiversThread->join ();
delete m_ReceiversThread;
m_ReceiversThread = nullptr;
}
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
if (m_ThreadV6)
{
m_ThreadV6->join ();
delete m_ThreadV6;
m_ThreadV6 = nullptr;
}
}
void SSUServer::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU server: ", ex.what ());
}
}
}
void SSUServer::RunV6 ()
{
while (m_IsRunning)
{
try
{
m_ServiceV6.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU V6 server: ", ex.what ());
}
}
}
void SSUServer::RunReceivers ()
{
while (m_IsRunning)
{
try
{
m_ReceiversService.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU receivers: ", ex.what ());
}
}
}
void SSUServer::AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay)
{
m_Relays[tag] = relay;
}
std::shared_ptr<SSUSession> SSUServer::FindRelaySession (uint32_t tag)
{
auto it = m_Relays.find (tag);
if (it != m_Relays.end ())
return FindSession (it->second);
return nullptr;
}
void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to)
{
if (to.protocol () == boost::asio::ip::udp::v4())
m_Socket.send_to (boost::asio::buffer (buf, len), to);
else
m_SocketV6.send_to (boost::asio::buffer (buf, len), to);
}
void SSUServer::Receive ()
{
SSUPacket * packet = new SSUPacket ();
m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from,
std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::ReceiveV6 ()
{
SSUPacket * packet = new SSUPacket ();
m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from,
std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode)
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
while (moreBytes && packets.size () < 25)
{
packet = new SSUPacket ();
packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from);
packets.push_back (packet);
moreBytes = m_Socket.available();
}
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets));
Receive ();
}
else
{
LogPrint ("SSU receive error: ", ecode.message ());
delete packet;
}
}
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode)
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
size_t moreBytes = m_SocketV6.available ();
while (moreBytes && packets.size () < 25)
{
packet = new SSUPacket ();
packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from);
packets.push_back (packet);
moreBytes = m_SocketV6.available();
}
m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets));
ReceiveV6 ();
}
else
{
LogPrint ("SSU V6 receive error: ", ecode.message ());
delete packet;
}
}
void SSUServer::HandleReceivedPackets (std::vector<SSUPacket *> packets)
{
std::shared_ptr<SSUSession> session;
for (auto it1: packets)
{
auto packet = it1;
try
{
if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous
{
if (session) session->FlushData ();
auto it = m_Sessions.find (packet->from);
if (it != m_Sessions.end ())
session = it->second;
if (!session)
{
session = std::make_shared<SSUSession> (*this, packet->from);
session->WaitForConnect ();
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions[packet->from] = session;
}
LogPrint (eLogInfo, "New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
}
}
session->ProcessNextMessage (packet->buf, packet->len, packet->from);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ());
if (session) session->FlushData ();
session = nullptr;
}
delete packet;
}
if (session) session->FlushData ();
}
std::shared_ptr<SSUSession> SSUServer::FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const
{
if (!router) return nullptr;
auto address = router->GetSSUAddress (true); // v4 only
if (!address) return nullptr;
auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
if (session || !context.SupportsV6 ())
return session;
// try v6
address = router->GetSSUV6Address ();
if (!address) return nullptr;
return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
}
std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
{
auto it = m_Sessions.find (e);
if (it != m_Sessions.end ())
return it->second;
else
return nullptr;
}
std::shared_ptr<SSUSession> SSUServer::GetSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
{
std::shared_ptr<SSUSession> session;
if (router)
{
auto address = router->GetSSUAddress (!context.SupportsV6 ());
if (address)
{
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto it = m_Sessions.find (remoteEndpoint);
if (it != m_Sessions.end ())
session = it->second;
else
{
// otherwise create new session
session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions[remoteEndpoint] = session;
}
if (!router->UsesIntroducer ())
{
// connect directly
LogPrint ("Creating new SSU session to [", router->GetIdentHashAbbreviation (), "] ",
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
session->Connect ();
}
else
{
// connect through introducer
int numIntroducers = address->introducers.size ();
if (numIntroducers > 0)
{
std::shared_ptr<SSUSession> introducerSession;
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
// we might have a session to introducer already
for (int i = 0; i < numIntroducers; i++)
{
introducer = &(address->introducers[i]);
it = m_Sessions.find (boost::asio::ip::udp::endpoint (introducer->iHost, introducer->iPort));
if (it != m_Sessions.end ())
{
introducerSession = it->second;
break;
}
}
if (introducerSession) // session found
LogPrint ("Session to introducer already exists");
else // create new
{
LogPrint ("Creating new session to introducer");
introducer = &(address->introducers[0]); // TODO:
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions[introducerEndpoint] = introducerSession;
}
// introduce
LogPrint ("Introduce new SSU session to [", router->GetIdentHashAbbreviation (),
"] through introducer ", introducer->iHost, ":", introducer->iPort);
session->WaitForIntroduction ();
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable
{
uint8_t buf[1];
Send (buf, 0, remoteEndpoint); // send HolePunch
}
introducerSession->Introduce (introducer->iTag, introducer->iKey);
}
else
{
LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented");
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions.erase (remoteEndpoint);
session.reset ();
}
}
}
}
else
LogPrint (eLogWarning, "Router ", router->GetIdentHashAbbreviation (), " doesn't have SSU address");
}
return session;
}
void SSUServer::DeleteSession (std::shared_ptr<SSUSession> session)
{
if (session)
{
session->Close ();
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions.erase (session->GetRemoteEndpoint ());
}
}
void SSUServer::DeleteAllSessions ()
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto it: m_Sessions)
it.second->Close ();
m_Sessions.clear ();
}
template<typename Filter>
std::shared_ptr<SSUSession> SSUServer::GetRandomSession (Filter filter)
{
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
for (auto s :m_Sessions)
if (filter (s.second)) filteredSessions.push_back (s.second);
if (filteredSessions.size () > 0)
{
auto ind = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, filteredSessions.size ()-1);
return filteredSessions[ind];
}
return nullptr;
}
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedSession (std::shared_ptr<const SSUSession> excluded)
{
return GetRandomSession (
[excluded](std::shared_ptr<SSUSession> session)->bool
{
return session->GetState () == eSessionStateEstablished && !session->IsV6 () &&
session != excluded;
}
);
}
std::set<SSUSession *> SSUServer::FindIntroducers (int maxNumIntroducers)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::set<SSUSession *> ret;
for (int i = 0; i < maxNumIntroducers; i++)
{
auto session = GetRandomSession (
[&ret, ts](std::shared_ptr<SSUSession> session)->bool
{
return session->GetRelayTag () && !ret.count (session.get ()) &&
session->GetState () == eSessionStateEstablished &&
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
}
);
if (session)
{
ret.insert (session.get ());
break;
}
}
return ret;
}
void SSUServer::ScheduleIntroducersUpdateTimer ()
{
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1));
}
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
// timeout expired
if (i2p::context.GetStatus () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimer ();
return;
}
if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore
// we are firewalled
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable ();
std::list<boost::asio::ip::udp::endpoint> newList;
size_t numIntroducers = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it :m_Introducers)
{
auto session = FindSession (it);
if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
{
session->SendKeepAlive ();
newList.push_back (it);
numIntroducers++;
}
else
i2p::context.RemoveIntroducer (it);
}
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
{
// create new
auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS);
if (introducers.size () > 0)
{
for (auto it1: introducers)
{
auto router = it1->GetRemoteRouter ();
if (router && i2p::context.AddIntroducer (*router, it1->GetRelayTag ()))
{
newList.push_back (it1->GetRemoteEndpoint ());
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
}
}
}
}
m_Introducers = newList;
if (m_Introducers.empty ())
{
auto introducer = i2p::data::netdb.GetRandomIntroducer ();
if (introducer)
GetSession (introducer);
}
ScheduleIntroducersUpdateTimer ();
}
}
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
{
m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session };
}
PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.role;
else
return ePeerTestParticipantUnknown;
}
std::shared_ptr<SSUSession> SSUServer::GetPeerTestSession (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.session;
else
return nullptr;
}
void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
it->second.role = role;
}
void SSUServer::RemovePeerTest (uint32_t nonce)
{
m_PeerTests.erase (nonce);
}
void SSUServer::SchedulePeerTestsCleanupTimer ()
{
m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT));
m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer,
this, std::placeholders::_1));
}
void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
int numDeleted = 0;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
{
if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL)
{
numDeleted++;
it = m_PeerTests.erase (it);
}
else
it++;
}
if (numDeleted > 0)
LogPrint (eLogInfo, numDeleted, " peer tests have been expired");
SchedulePeerTestsCleanupTimer ();
}
}
}
}

114
core/transport/SSU.h Normal file
View file

@ -0,0 +1,114 @@
#ifndef SSU_H__
#define SSU_H__
#include <inttypes.h>
#include <string.h>
#include <map>
#include <list>
#include <set>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
#include "crypto/aes.h"
#include "util/I2PEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "SSUSession.h"
namespace i2p
{
namespace transport
{
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
struct SSUPacket
{
i2p::crypto::AESAlignedBuffer<1500> buf;
boost::asio::ip::udp::endpoint from;
size_t len;
};
class SSUServer
{
public:
SSUServer (int port);
~SSUServer ();
void Start ();
void Stop ();
std::shared_ptr<SSUSession> GetSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
std::shared_ptr<SSUSession> FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const;
std::shared_ptr<SSUSession> FindSession (const boost::asio::ip::udp::endpoint& e) const;
std::shared_ptr<SSUSession> GetRandomEstablishedSession (std::shared_ptr<const SSUSession> excluded);
void DeleteSession (std::shared_ptr<SSUSession> session);
void DeleteAllSessions ();
boost::asio::io_service& GetService () { return m_Service; };
boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; };
const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; };
void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to);
void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay);
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
void RemovePeerTest (uint32_t nonce);
private:
void Run ();
void RunV6 ();
void RunReceivers ();
void Receive ();
void ReceiveV6 ();
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedPackets (std::vector<SSUPacket *> packets);
template<typename Filter>
std::shared_ptr<SSUSession> GetRandomSession (Filter filter);
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers);
void ScheduleIntroducersUpdateTimer ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode);
void SchedulePeerTestsCleanupTimer ();
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
private:
struct PeerTest
{
uint64_t creationTime;
PeerTestParticipant role;
std::shared_ptr<SSUSession> session; // for Bob to Alice
};
bool m_IsRunning;
std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread;
boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService;
boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork;
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer;
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to
mutable std::mutex m_SessionsMutex;
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions;
std::map<uint32_t, boost::asio::ip::udp::endpoint> m_Relays; // we are introducer
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds
public:
// for HTTP only
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
};
}
}
#endif

507
core/transport/SSUData.cpp Normal file
View file

@ -0,0 +1,507 @@
#include <stdlib.h>
#include <boost/bind.hpp>
#include "util/Log.h"
#include "util/Timestamp.h"
#include "NetDb.h"
#include "SSU.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
{
if (msg->len + fragmentSize > msg->maxLen)
{
LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough");
auto newMsg = ToSharedI2NPMessage(NewI2NPMessage ());
*newMsg = *msg;
msg = newMsg;
}
memcpy (msg->buf + msg->len, fragment, fragmentSize);
msg->len += fragmentSize;
nextFragmentNum++;
}
SSUData::SSUData (SSUSession& session):
m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()),
m_IncompleteMessagesCleanupTimer (session.GetService ())
{
m_MaxPacketSize = session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE;
m_PacketSize = m_MaxPacketSize;
auto remoteRouter = session.GetRemoteRouter ();
if (remoteRouter)
AdjustPacketSize (*remoteRouter);
}
SSUData::~SSUData ()
{
}
void SSUData::Start ()
{
ScheduleIncompleteMessagesCleanup ();
}
void SSUData::Stop ()
{
m_ResendTimer.cancel ();
m_DecayTimer.cancel ();
m_IncompleteMessagesCleanupTimer.cancel ();
}
void SSUData::AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter)
{
auto ssuAddress = remoteRouter.GetSSUAddress ();
if (ssuAddress && ssuAddress->mtu)
{
if (m_Session.IsV6 ())
m_PacketSize = ssuAddress->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
else
m_PacketSize = ssuAddress->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
if (m_PacketSize > 0)
{
// make sure packet size multiple of 16
m_PacketSize >>= 4;
m_PacketSize <<= 4;
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
LogPrint ("MTU=", ssuAddress->mtu, " packet size=", m_PacketSize);
}
else
{
LogPrint (eLogWarning, "Unexpected MTU ", ssuAddress->mtu);
m_PacketSize = m_MaxPacketSize;
}
}
}
void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent)
{
auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent);
if (routerInfo)
AdjustPacketSize (*routerInfo);
}
void SSUData::ProcessSentMessageAck (uint32_t msgID)
{
auto it = m_SentMessages.find (msgID);
if (it != m_SentMessages.end ())
{
m_SentMessages.erase (it);
if (m_SentMessages.empty ())
m_ResendTimer.cancel ();
}
}
void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag)
{
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED)
{
// explicit ACKs
uint8_t numAcks =*buf;
buf++;
for (int i = 0; i < numAcks; i++)
ProcessSentMessageAck (bufbe32toh (buf+i*4));
buf += numAcks*4;
}
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED)
{
// explicit ACK bitfields
uint8_t numBitfields =*buf;
buf++;
for (int i = 0; i < numBitfields; i++)
{
uint32_t msgID = bufbe32toh (buf);
buf += 4; // msgID
auto it = m_SentMessages.find (msgID);
// process individual Ack bitfields
bool isNonLast = false;
int fragment = 0;
do
{
uint8_t bitfield = *buf;
isNonLast = bitfield & 0x80;
bitfield &= 0x7F; // clear MSB
if (bitfield && it != m_SentMessages.end ())
{
int numSentFragments = it->second->fragments.size ();
// process bits
uint8_t mask = 0x01;
for (int j = 0; j < 7; j++)
{
if (bitfield & mask)
{
if (fragment < numSentFragments)
it->second->fragments[fragment].reset (nullptr);
}
fragment++;
mask <<= 1;
}
}
buf++;
}
while (isNonLast);
}
}
}
void SSUData::ProcessFragments (uint8_t * buf)
{
uint8_t numFragments = *buf; // number of fragments
buf++;
for (int i = 0; i < numFragments; i++)
{
uint32_t msgID = bufbe32toh (buf); // message ID
buf += 4;
uint8_t frag[4];
frag[0] = 0;
memcpy (frag + 1, buf, 3);
buf += 3;
uint32_t fragmentInfo = bufbe32toh (frag); // fragment info
uint16_t fragmentSize = fragmentInfo & 0x1FFF; // bits 0 - 13
bool isLast = fragmentInfo & 0x010000; // bit 16
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
{
LogPrint (eLogError, "Fragment size ", fragmentSize, "exceeds max SSU packet size");
return;
}
// find message with msgID
auto it = m_IncompleteMessages.find (msgID);
if (it == m_IncompleteMessages.end ())
{
// create new message
auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ());
msg->len -= I2NP_SHORT_HEADER_SIZE;
it = m_IncompleteMessages.insert (std::make_pair (msgID,
std::unique_ptr<IncompleteMessage>(new IncompleteMessage (msg)))).first;
}
std::unique_ptr<IncompleteMessage>& incompleteMessage = it->second;
// handle current fragment
if (fragmentNum == incompleteMessage->nextFragmentNum)
{
// expected fragment
incompleteMessage->AttachNextFragment (buf, fragmentSize);
if (!isLast && !incompleteMessage->savedFragments.empty ())
{
// try saved fragments
for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();)
{
auto& savedFragment = *it1;
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum)
{
incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len);
isLast = savedFragment->isLast;
incompleteMessage->savedFragments.erase (it1++);
}
else
break;
}
if (isLast)
LogPrint (eLogDebug, "Message ", msgID, " complete");
}
}
else
{
if (fragmentNum < incompleteMessage->nextFragmentNum)
// duplicate fragment
LogPrint (eLogWarning, "Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ". Ignored");
else
{
// missing fragment
LogPrint (eLogWarning, "Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast);
if (incompleteMessage->savedFragments.insert (std::unique_ptr<Fragment>(savedFragment)).second)
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
else
LogPrint (eLogWarning, "Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
}
isLast = false;
}
if (isLast)
{
// delete incomplete message
auto msg = incompleteMessage->msg;
incompleteMessage->msg = nullptr;
m_IncompleteMessages.erase (msgID);
// process message
SendMsgAck (msgID);
msg->FromSSU (msgID);
if (m_Session.GetState () == eSessionStateEstablished)
{
if (!m_ReceivedMessages.count (msgID))
{
if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES)
m_ReceivedMessages.clear ();
else
ScheduleDecay ();
m_ReceivedMessages.insert (msgID);
m_Handler.PutNextMessage (msg);
}
else
LogPrint (eLogWarning, "SSU message ", msgID, " already received");
}
else
{
// we expect DeliveryStatus
if (msg->GetTypeID () == eI2NPDeliveryStatus)
{
LogPrint ("SSU session established");
m_Session.Established ();
}
else
LogPrint (eLogError, "SSU unexpected message ", (int)msg->GetTypeID ());
}
}
else
SendFragmentAck (msgID, fragmentNum);
buf += fragmentSize;
}
}
void SSUData::FlushReceivedMessage ()
{
m_Handler.Flush ();
}
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
{
//uint8_t * start = buf;
uint8_t flag = *buf;
buf++;
LogPrint (eLogDebug, "Process SSU data flags=", (int)flag, " len=", len);
// process acks if presented
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
ProcessAcks (buf, flag);
// extended data if presented
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED)
{
uint8_t extendedDataSize = *buf;
buf++; // size
LogPrint (eLogDebug, "SSU extended data of ", extendedDataSize, " bytes presented");
buf += extendedDataSize;
}
// process data
ProcessFragments (buf);
}
void SSUData::Send (std::shared_ptr<i2p::I2NPMessage> msg)
{
uint32_t msgID = msg->ToSSU ();
if (m_SentMessages.count (msgID) > 0)
{
LogPrint (eLogWarning, "SSU message ", msgID, " already sent");
return;
}
if (m_SentMessages.empty ()) // schedule resend at first message only
ScheduleResend ();
auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr<SentMessage>(new SentMessage)));
std::unique_ptr<SentMessage>& sentMessage = ret.first->second;
if (ret.second)
{
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
sentMessage->numResends = 0;
}
auto& fragments = sentMessage->fragments;
size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
size_t len = msg->GetLength ();
uint8_t * msgBuf = msg->GetSSUHeader ();
uint32_t fragmentNum = 0;
while (len > 0)
{
Fragment * fragment = new Fragment;
fragment->fragmentNum = fragmentNum;
uint8_t * buf = fragment->buf;
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
payload++;
*payload = 1; // always 1 message fragment per message
payload++;
htobe32buf (payload, msgID);
payload += 4;
bool isLast = (len <= payloadSize);
size_t size = isLast ? len : payloadSize;
uint32_t fragmentInfo = (fragmentNum << 17);
if (isLast)
fragmentInfo |= 0x010000;
fragmentInfo |= size;
fragmentInfo = htobe32 (fragmentInfo);
memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3);
payload += 3;
memcpy (payload, msgBuf, size);
size += payload - buf;
if (size & 0x0F) // make sure 16 bytes boundary
size = ((size >> 4) + 1) << 4; // (/16 + 1)*16
fragment->len = size;
fragments.push_back (std::unique_ptr<Fragment> (fragment));
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size);
try
{
m_Session.Send (buf, size);
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogError, "Can't send SSU fragment ", ec.what ());
}
if (!isLast)
{
len -= payloadSize;
msgBuf += payloadSize;
}
else
len = 0;
fragmentNum++;
}
}
void SSUData::SendMsgAck (uint32_t msgID)
{
uint8_t buf[48 + 18]; // actual length is 44 = 37 + 7 but pad it to multiple of 16
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACKs
payload++;
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
payload += 4;
*payload = 0; // number of fragments
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48);
m_Session.Send (buf, 48);
}
void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum)
{
if (fragmentNum > 64)
{
LogPrint (eLogWarning, "Fragment number ", fragmentNum, " exceeds 64");
return;
}
uint8_t buf[64 + 18];
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACK bitfields
payload++;
// one ack
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
payload += 4;
div_t d = div (fragmentNum, 7);
memset (payload, 0x80, d.quot); // 0x80 means non-last
payload += d.quot;
*payload = 0x01 << d.rem; // set corresponding bit
payload++;
*payload = 0; // number of fragments
size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1)
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len);
m_Session.Send (buf, len);
}
void SSUData::ScheduleResend()
{
m_ResendTimer.cancel ();
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleResendTimer (ecode); });
}
void SSUData::HandleResendTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();)
{
if (ts >= it->second->nextResendTime)
{
if (it->second->numResends < MAX_NUM_RESENDS)
{
for (auto& f: it->second->fragments)
if (f)
{
try
{
m_Session.Send (f->buf, f->len); // resend
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogError, "Can't resend SSU fragment ", ec.what ());
}
}
it->second->numResends++;
it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL;
it++;
}
else
{
LogPrint (eLogError, "SSU message has not been ACKed after ", MAX_NUM_RESENDS, " attempts. Deleted");
it = m_SentMessages.erase (it);
}
}
else
it++;
}
ScheduleResend ();
}
}
void SSUData::ScheduleDecay ()
{
m_DecayTimer.cancel ();
m_DecayTimer.expires_from_now (boost::posix_time::seconds(DECAY_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleDecayTimer (ecode); });
}
void SSUData::HandleDecayTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
m_ReceivedMessages.clear ();
}
void SSUData::ScheduleIncompleteMessagesCleanup ()
{
m_IncompleteMessagesCleanupTimer.cancel ();
m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT));
auto s = m_Session.shared_from_this();
m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); });
}
void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
{
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)
{
LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds. Deleted");
it = m_IncompleteMessages.erase (it);
}
else
it++;
}
ScheduleIncompleteMessagesCleanup ();
}
}
}
}

128
core/transport/SSUData.h Normal file
View file

@ -0,0 +1,128 @@
#ifndef SSU_DATA_H__
#define SSU_DATA_H__
#include <inttypes.h>
#include <string.h>
#include <map>
#include <vector>
#include <set>
#include <memory>
#include <boost/asio.hpp>
#include "I2NPProtocol.h"
#include "Identity.h"
#include "RouterInfo.h"
namespace i2p
{
namespace transport
{
const size_t SSU_MTU_V4 = 1484;
const size_t SSU_MTU_V6 = 1472;
const size_t IPV4_HEADER_SIZE = 20;
const size_t IPV6_HEADER_SIZE = 40;
const size_t UDP_HEADER_SIZE = 8;
const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456
const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1424
const int RESEND_INTERVAL = 3; // in seconds
const int MAX_NUM_RESENDS = 5;
const int DECAY_INTERVAL = 20; // in seconds
const int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check
const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
// data flags
const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02;
const uint8_t DATA_FLAG_WANT_REPLY = 0x04;
const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08;
const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10;
const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40;
const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80;
struct Fragment
{
int fragmentNum;
size_t len;
bool isLast;
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest
Fragment () = default;
Fragment (int n, const uint8_t * b, int l, bool last):
fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); };
};
struct FragmentCmp
{
bool operator() (const std::unique_ptr<Fragment>& f1, const std::unique_ptr<Fragment>& f2) const
{
return f1->fragmentNum < f2->fragmentNum;
};
};
struct IncompleteMessage
{
std::shared_ptr<I2NPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
std::set<std::unique_ptr<Fragment>, FragmentCmp> savedFragments;
IncompleteMessage (std::shared_ptr<I2NPMessage> m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {};
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
};
struct SentMessage
{
std::vector<std::unique_ptr<Fragment> > fragments;
uint32_t nextResendTime; // in seconds
int numResends;
};
class SSUSession;
class SSUData
{
public:
SSUData (SSUSession& session);
~SSUData ();
void Start ();
void Stop ();
void ProcessMessage (uint8_t * buf, size_t len);
void FlushReceivedMessage ();
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent);
private:
void SendMsgAck (uint32_t msgID);
void SendFragmentAck (uint32_t msgID, int fragmentNum);
void ProcessAcks (uint8_t *& buf, uint8_t flag);
void ProcessFragments (uint8_t * buf);
void ProcessSentMessageAck (uint32_t msgID);
void ScheduleResend ();
void HandleResendTimer (const boost::system::error_code& ecode);
void ScheduleDecay ();
void HandleDecayTimer (const boost::system::error_code& ecode);
void ScheduleIncompleteMessagesCleanup ();
void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode);
void AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter);
private:
SSUSession& m_Session;
std::map<uint32_t, std::unique_ptr<IncompleteMessage> > m_IncompleteMessages;
std::map<uint32_t, std::unique_ptr<SentMessage> > m_SentMessages;
std::set<uint32_t> m_ReceivedMessages;
boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer;
int m_MaxPacketSize, m_PacketSize;
i2p::I2NPMessagesHandler m_Handler;
};
}
}
#endif

File diff suppressed because it is too large Load diff

159
core/transport/SSUSession.h Normal file
View file

@ -0,0 +1,159 @@
#ifndef SSU_SESSION_H__
#define SSU_SESSION_H__
#include <inttypes.h>
#include <set>
#include <memory>
#include "crypto/aes.h"
#include "crypto/hmac.h"
#include "I2NPProtocol.h"
#include "TransportSession.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
#pragma pack(1)
// Warning: do not change the order of these variables
// (or fix the unsafe casts in SSU.h)
struct SSUHeader
{
uint8_t mac[16];
uint8_t iv[16];
uint8_t flag;
uint32_t time;
uint8_t GetPayloadType () const { return flag >> 4; };
};
#pragma pack()
const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds
const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes
// payload types (4 bits)
const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0;
const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1;
const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3;
const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4;
const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5;
const uint8_t PAYLOAD_TYPE_DATA = 6;
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
enum SessionState
{
eSessionStateUnknown,
eSessionStateIntroduced,
eSessionStateEstablished,
eSessionStateClosed,
eSessionStateFailed
};
enum PeerTestParticipant
{
ePeerTestParticipantUnknown = 0,
ePeerTestParticipantAlice1,
ePeerTestParticipantAlice2,
ePeerTestParticipantBob,
ePeerTestParticipantCharlie
};
class SSUServer;
class SSUSession: public TransportSession, public std::enable_shared_from_this<SSUSession>
{
public:
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, bool peerTest = false);
void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
~SSUSession ();
void Connect ();
void WaitForConnect ();
void Introduce (uint32_t iTag, const uint8_t * iKey);
void WaitForIntroduction ();
void Close ();
void Done ();
boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void SendPeerTest (); // Alice
SessionState GetState () const { return m_State; };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
void SendKeepAlive ();
uint32_t GetRelayTag () const { return m_RelayTag; };
uint32_t GetCreationTime () const { return m_CreationTime; };
void FlushData ();
private:
boost::asio::io_service& GetService ();
void CreateAESandMacKey (const uint8_t * pubKey);
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void SendSessionRequest ();
void SendRelayRequest (uint32_t iTag, const uint8_t * iKey);
void ProcessSessionCreated (uint8_t * buf, size_t len);
void SendSessionCreated (const uint8_t * x);
void ProcessSessionConfirmed (uint8_t * buf, size_t len);
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
void ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to);
void SendRelayIntro (SSUSession * session, const boost::asio::ip::udp::endpoint& from);
void ProcessRelayResponse (uint8_t * buf, size_t len);
void ProcessRelayIntro (uint8_t * buf, size_t len);
void Established ();
void Failed ();
void ScheduleConnectTimer ();
void HandleConnectTimer (const boost::system::error_code& ecode);
void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true);
void ProcessData (uint8_t * buf, size_t len);
void SendSesionDestroyed ();
void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key
void Send (const uint8_t * buf, size_t size);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
void Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey);
void DecryptSessionKey (uint8_t * buf, size_t len);
bool Validate (uint8_t * buf, size_t len, const uint8_t * macKey);
const uint8_t * GetIntroKey () const;
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
private:
friend class SSUData; // TODO: change in later
SSUServer& m_Server;
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
boost::asio::deadline_timer m_Timer;
bool m_PeerTest;
SessionState m_State;
bool m_IsSessionKey;
uint32_t m_RelayTag;
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
i2p::crypto::AESKey m_SessionKey;
i2p::crypto::MACKey m_MacKey;
uint32_t m_CreationTime; // seconds since epoch
SSUData m_Data;
bool m_IsDataReceived;
};
}
}
#endif

View file

@ -0,0 +1,86 @@
#ifndef TRANSPORT_SESSION_H__
#define TRANSPORT_SESSION_H__
#include <inttypes.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace transport
{
struct DHKeysPair // transient keys for transport sessions
{
uint8_t publicKey[256];
uint8_t privateKey[256];
};
class SignedData
{
public:
SignedData () {};
void Insert (const uint8_t * buf, size_t len)
{
m_Stream.write ((char *)buf, len);
}
template<typename T>
void Insert (T t)
{
m_Stream.write ((char *)&t, sizeof (T));
}
bool Verify (const i2p::data::IdentityEx& ident, const uint8_t * signature) const
{
return ident.Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
}
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
{
keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
}
private:
std::stringstream m_Stream;
};
class TransportSession
{
public:
TransportSession (std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter):
m_RemoteRouter (in_RemoteRouter), m_DHKeysPair (nullptr),
m_NumSentBytes (0), m_NumReceivedBytes (0)
{
if (m_RemoteRouter)
m_RemoteIdentity = m_RemoteRouter->GetRouterIdentity ();
}
virtual ~TransportSession () { delete m_DHKeysPair; };
virtual void Done () = 0;
std::shared_ptr<const i2p::data::RouterInfo> GetRemoteRouter () { return m_RemoteRouter; };
const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
protected:
std::shared_ptr<const i2p::data::RouterInfo> m_RemoteRouter;
i2p::data::IdentityEx m_RemoteIdentity;
DHKeysPair * m_DHKeysPair; // X - for client and Y - for server
size_t m_NumSentBytes, m_NumReceivedBytes;
};
}
}
#endif

View file

@ -0,0 +1,505 @@
#include <cryptopp/dh.h>
#include "util/Log.h"
#include "crypto/CryptoConst.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "NetDb.h"
#include "Transports.h"
using namespace i2p::data;
namespace i2p
{
namespace transport
{
DHKeysPairSupplier::DHKeysPairSupplier (int size):
m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
{
}
DHKeysPairSupplier::~DHKeysPairSupplier ()
{
Stop ();
}
void DHKeysPairSupplier::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this));
}
void DHKeysPairSupplier::Stop ()
{
m_IsRunning = false;
m_Acquired.notify_one ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
}
void DHKeysPairSupplier::Run ()
{
while (m_IsRunning)
{
int num;
while ((num = m_QueueSize - m_Queue.size ()) > 0)
CreateDHKeysPairs (num);
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Acquired.wait (l); // wait for element gets aquired
}
}
void DHKeysPairSupplier::CreateDHKeysPairs (int num)
{
if (num > 0)
{
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
for (int i = 0; i < num; i++)
{
i2p::transport::DHKeysPair * pair = new i2p::transport::DHKeysPair ();
dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey);
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
}
}
}
DHKeysPair * DHKeysPairSupplier::Acquire ()
{
if (!m_Queue.empty ())
{
std::unique_lock<std::mutex> l(m_AcquiredMutex);
auto pair = m_Queue.front ();
m_Queue.pop ();
m_Acquired.notify_one ();
return pair;
}
else // queue is empty, create new
{
DHKeysPair * pair = new DHKeysPair ();
CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg);
dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey);
return pair;
}
}
void DHKeysPairSupplier::Return (DHKeysPair * pair)
{
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
}
Transports transports;
Transports::Transports ():
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service),
m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
{
}
Transports::~Transports ()
{
Stop ();
}
void Transports::Start ()
{
#ifdef USE_UPNP
m_UPnP.Start ();
LogPrint(eLogInfo, "UPnP started");
#endif
m_DHKeysPairSupplier.Start ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Transports::Run, this));
// create acceptors
auto addresses = context.GetRouterInfo ().GetAddresses ();
for (auto& address : addresses)
{
if (!m_NTCPServer)
{
m_NTCPServer = new NTCPServer (address.port);
m_NTCPServer->Start ();
}
if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ())
{
if (!m_SSUServer)
{
m_SSUServer = new SSUServer (address.port);
LogPrint ("Start listening UDP port ", address.port);
m_SSUServer->Start ();
DetectExternalIP ();
}
else
LogPrint ("SSU server already exists");
}
}
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
void Transports::Stop ()
{
#ifdef USE_UPNP
m_UPnP.Stop ();
LogPrint(eLogInfo, "UPnP stopped");
#endif
m_PeerCleanupTimer.cancel ();
m_Peers.clear ();
if (m_SSUServer)
{
m_SSUServer->Stop ();
delete m_SSUServer;
m_SSUServer = nullptr;
}
if (m_NTCPServer)
{
m_NTCPServer->Stop ();
delete m_NTCPServer;
m_NTCPServer = nullptr;
}
m_DHKeysPairSupplier.Stop ();
m_IsRunning = false;
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void Transports::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint ("Transports: ", ex.what ());
}
}
}
void Transports::UpdateBandwidth ()
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
if (m_LastBandwidthUpdateTime > 0)
{
auto delta = ts - m_LastBandwidthUpdateTime;
if (delta > 0)
{
m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second
m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second
}
}
m_LastBandwidthUpdateTime = ts;
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
}
bool Transports::IsBandwidthExceeded () const
{
if (i2p::context.GetRouterInfo ().IsHighBandwidth ()) return false;
return std::max (m_InBandwidth, m_OutBandwidth) > LOW_BANDWIDTH_LIMIT;
}
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
{
SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
}
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
{
m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs));
}
void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
{
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
{
// we send it to ourself
for (auto it: msgs)
i2p::HandleI2NPMessage (it);
return;
}
auto it = m_Peers.find (ident);
if (it == m_Peers.end ())
{
bool connected = false;
try
{
auto r = netdb.FindRouter (ident);
it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {},
i2p::util::GetSecondsSinceEpoch () })).first;
connected = ConnectToPeer (ident, it->second);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Transports::PostMessages ", ex.what ());
}
if (!connected) return;
}
if (!it->second.sessions.empty ())
it->second.sessions.front ()->SendI2NPMessages (msgs);
else
{
for (auto it1: msgs)
it->second.delayedMessages.push_back (it1);
}
}
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
{
if (peer.router) // we have RI already
{
if (!peer.numAttempts) // NTCP
{
peer.numAttempts++;
auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ());
if (address)
{
#if BOOST_VERSION >= 104900
if (!address->host.is_unspecified ()) // we have address now
#else
boost::system::error_code ecode;
address->host.to_string (ecode);
if (!ecode)
#endif
{
if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ())
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
m_NTCPServer->Connect (address->host, address->port, s);
return true;
}
}
else // we don't have address
{
if (address->addressString.length () > 0) // trying to resolve
{
LogPrint (eLogInfo, "Resolving ", address->addressString);
NTCPResolve (address->addressString, ident);
return true;
}
}
}
}
else if (peer.numAttempts == 1)// SSU
{
peer.numAttempts++;
if (m_SSUServer)
{
if (m_SSUServer->GetSession (peer.router))
return true;
}
}
LogPrint (eLogError, "No NTCP and SSU addresses available");
peer.Done ();
m_Peers.erase (ident);
return false;
}
else // otherwise request RI
{
LogPrint ("Router not found. Requested");
i2p::data::netdb.RequestDestination (ident, std::bind (
&Transports::RequestComplete, this, std::placeholders::_1, ident));
}
return true;
}
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
{
m_Service.post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
}
void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
{
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
if (r)
{
LogPrint ("Router found. Trying to connect");
it->second.router = r;
ConnectToPeer (ident, it->second);
}
else
{
LogPrint ("Router not found. Failed to send messages");
m_Peers.erase (it);
}
}
}
void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident)
{
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(m_Service);
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
std::bind (&Transports::HandleNTCPResolve, this,
std::placeholders::_1, std::placeholders::_2, ident, resolver));
}
void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
{
auto it1 = m_Peers.find (ident);
if (it1 != m_Peers.end ())
{
auto& peer = it1->second;
if (!ecode && peer.router)
{
auto address = (*it).endpoint ().address ();
LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address);
auto addr = peer.router->GetNTCPAddress ();
if (addr)
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
m_NTCPServer->Connect (address, addr->port, s);
return;
}
}
LogPrint (eLogError, "Unable to resolve NTCP address: ", ecode.message ());
m_Peers.erase (it1);
}
}
void Transports::CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
{
if (!router) return;
m_Service.post (std::bind (&Transports::PostCloseSession, this, router));
}
void Transports::PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
{
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr;
if (ssuSession) // try SSU first
{
m_SSUServer->DeleteSession (ssuSession);
LogPrint ("SSU session closed");
}
// TODO: delete NTCP
}
void Transports::DetectExternalIP ()
{
if (m_SSUServer)
{
i2p::context.SetStatus (eRouterStatusTesting);
for (int i = 0; i < 5; i++)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
if (router && router->IsSSU ())
m_SSUServer->GetSession (router, true); // peer test
else
{
// if not peer test capable routers found pick any
router = i2p::data::netdb.GetRandomRouter ();
if (router && router->IsSSU ())
m_SSUServer->GetSession (router); // no peer test
}
}
}
else
LogPrint (eLogError, "Can't detect external IP. SSU is not available");
}
DHKeysPair * Transports::GetNextDHKeysPair ()
{
return m_DHKeysPairSupplier.Acquire ();
}
void Transports::ReuseDHKeysPair (DHKeysPair * pair)
{
m_DHKeysPairSupplier.Return (pair);
}
void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
{
m_Service.post([session, this]()
{
auto ident = session->GetRemoteIdentity ().GetIdentHash ();
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
it->second.sessions.push_back (session);
session->SendI2NPMessages (it->second.delayedMessages);
it->second.delayedMessages.clear ();
}
else // incoming connection
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch () }));
});
}
void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
{
m_Service.post([session, this]()
{
auto ident = session->GetRemoteIdentity ().GetIdentHash ();
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
it->second.sessions.remove (session);
if (it->second.sessions.empty ()) // TODO: why?
{
if (it->second.delayedMessages.size () > 0)
ConnectToPeer (ident, it->second);
else
m_Peers.erase (it);
}
}
});
}
bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
{
auto it = m_Peers.find (ident);
return it != m_Peers.end ();
}
void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
{
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
{
LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
it = m_Peers.erase (it);
}
else
it++;
}
UpdateBandwidth (); // TODO: use separate timer(s) for it
if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test
DetectExternalIP ();
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
}
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
{
if(m_Peers.empty()) // ensure m.Peers.size() >= 1
return nullptr;
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator();
auto it = m_Peers.begin();
std::advance(it, rnd.GenerateWord32(0, m_Peers.size () - 1));
return it->second.router;
}
}
}

160
core/transport/Transports.h Normal file
View file

@ -0,0 +1,160 @@
#ifndef TRANSPORTS_H__
#define TRANSPORTS_H__
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <map>
#include <vector>
#include <queue>
#include <string>
#include <memory>
#include <atomic>
#include <cryptopp/osrng.h>
#include <boost/asio.hpp>
#include "TransportSession.h"
#include "NTCPSession.h"
#include "SSU.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "Identity.h"
#ifdef USE_UPNP
#include "UPnP.h"
#endif
namespace i2p
{
namespace transport
{
class DHKeysPairSupplier
{
public:
DHKeysPairSupplier (int size);
~DHKeysPairSupplier ();
void Start ();
void Stop ();
DHKeysPair * Acquire ();
void Return (DHKeysPair * pair);
private:
void Run ();
void CreateDHKeysPairs (int num);
private:
const int m_QueueSize;
std::queue<DHKeysPair *> m_Queue;
bool m_IsRunning;
std::thread * m_Thread;
std::condition_variable m_Acquired;
std::mutex m_AcquiredMutex;
CryptoPP::AutoSeededRandomPool m_Rnd;
};
struct Peer
{
int numAttempts;
std::shared_ptr<const i2p::data::RouterInfo> router;
std::list<std::shared_ptr<TransportSession> > sessions;
uint64_t creationTime;
std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
void Done ()
{
for (auto it: sessions)
it->Done ();
}
};
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds
const uint32_t LOW_BANDWIDTH_LIMIT = 32*1024; // 32KBs
class Transports
{
public:
Transports ();
~Transports ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
i2p::transport::DHKeysPair * GetNextDHKeysPair ();
void ReuseDHKeysPair (DHKeysPair * pair);
void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
void CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
void PeerConnected (std::shared_ptr<TransportSession> session);
void PeerDisconnected (std::shared_ptr<TransportSession> session);
bool IsConnected (const i2p::data::IdentHash& ident) const;
void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; };
void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; };
uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; };
uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; };
uint32_t GetInBandwidth () const { return m_InBandwidth; }; // bytes per second
uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; // bytes per second
bool IsBandwidthExceeded () const;
size_t GetNumPeers () const { return m_Peers.size (); };
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer () const;
private:
void Run ();
void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
void PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident);
void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
void UpdateBandwidth ();
void DetectExternalIP ();
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::deadline_timer m_PeerCleanupTimer;
NTCPServer * m_NTCPServer;
SSUServer * m_SSUServer;
std::map<i2p::data::IdentHash, Peer> m_Peers;
DHKeysPairSupplier m_DHKeysPairSupplier;
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes;
uint32_t m_InBandwidth, m_OutBandwidth;
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes;
uint64_t m_LastBandwidthUpdateTime;
#ifdef USE_UPNP
UPnP m_UPnP;
#endif
public:
// for HTTP only
const NTCPServer * GetNTCPServer () const { return m_NTCPServer; };
const SSUServer * GetSSUServer () const { return m_SSUServer; };
const decltype(m_Peers)& GetPeers () const { return m_Peers; };
};
extern Transports transports;
}
}
#endif

View file

@ -0,0 +1,111 @@
#include <string.h>
#include "util/I2PEndian.h"
#include "util/Log.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "Tunnel.h"
#include "transport/Transports.h"
#include "TransitTunnel.h"
namespace i2p
{
namespace tunnel
{
TransitTunnel::TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
m_TunnelID (receiveTunnelID), m_NextTunnelID (nextTunnelID),
m_NextIdent (nextIdent)
{
m_Encryption.SetKeys (layerKey, ivKey);
}
void TransitTunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
{
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
}
TransitTunnelParticipant::~TransitTunnelParticipant ()
{
}
void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (tunnelMsg, newMsg);
m_NumTransmittedBytes += tunnelMsg->GetLength ();
htobe32buf (newMsg->GetPayload (), GetNextTunnelID ());
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
m_TunnelDataMsgs.push_back (newMsg);
}
void TransitTunnelParticipant::FlushTunnelDataMsgs ()
{
if (!m_TunnelDataMsgs.empty ())
{
auto num = m_TunnelDataMsgs.size ();
if (num > 1)
LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID (), " ", num);
i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
m_TunnelDataMsgs.clear ();
}
}
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
LogPrint (eLogError, "We are not a gateway for transit tunnel ", m_TunnelID);
}
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
LogPrint (eLogError, "Incoming tunnel message is not supported ", m_TunnelID);
}
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
block.deliveryType = eDeliveryTypeLocal;
block.data = msg;
std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.PutTunnelDataMsg (block);
}
void TransitTunnelGateway::FlushTunnelDataMsgs ()
{
std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.SendBuffer ();
}
void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (tunnelMsg, newMsg);
LogPrint (eLogDebug, "TransitTunnel endpoint for ", GetTunnelID ());
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
}
TransitTunnel * CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
bool isGateway, bool isEndpoint)
{
if (isEndpoint)
{
LogPrint (eLogInfo, "TransitTunnel endpoint: ", receiveTunnelID, " created");
return new TransitTunnelEndpoint (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
else if (isGateway)
{
LogPrint (eLogInfo, "TransitTunnel gateway: ", receiveTunnelID, " created");
return new TransitTunnelGateway (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
else
{
LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created");
return new TransitTunnelParticipant (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
}
}
}

111
core/tunnel/TransitTunnel.h Normal file
View file

@ -0,0 +1,111 @@
#ifndef TRANSIT_TUNNEL_H__
#define TRANSIT_TUNNEL_H__
#include <inttypes.h>
#include <vector>
#include <mutex>
#include <memory>
#include "TunnelCrypto.h"
#include "I2NPProtocol.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
#include "TunnelBase.h"
namespace i2p
{
namespace tunnel
{
class TransitTunnel: public TunnelBase
{
public:
TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey);
virtual size_t GetNumTransmittedBytes () const { return 0; };
uint32_t GetTunnelID () const { return m_TunnelID; };
// implements TunnelBase
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; };
private:
uint32_t m_TunnelID, m_NextTunnelID;
i2p::data::IdentHash m_NextIdent;
i2p::crypto::TunnelEncryption m_Encryption;
};
class TransitTunnelParticipant: public TransitTunnel
{
public:
TransitTunnelParticipant (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_NumTransmittedBytes (0) {};
~TransitTunnelParticipant ();
size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; };
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
void FlushTunnelDataMsgs ();
private:
size_t m_NumTransmittedBytes;
std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
};
class TransitTunnelGateway: public TransitTunnel
{
public:
TransitTunnelGateway (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_Gateway(this) {};
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void FlushTunnelDataMsgs ();
size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); };
private:
std::mutex m_SendMutex;
TunnelGateway m_Gateway;
};
class TransitTunnelEndpoint: public TransitTunnel
{
public:
TransitTunnelEndpoint (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }
private:
TunnelEndpoint m_Endpoint;
};
TransitTunnel * CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
bool isGateway, bool isEndpoint);
}
}
#endif

732
core/tunnel/Tunnel.cpp Normal file
View file

@ -0,0 +1,732 @@
#include <string.h>
#include "util/I2PEndian.h"
#include <thread>
#include <algorithm>
#include <vector>
#include <cryptopp/sha.h>
#include "RouterContext.h"
#include "util/Log.h"
#include "util/Timestamp.h"
#include "I2NPProtocol.h"
#include "transport/Transports.h"
#include "NetDb.h"
#include "Tunnel.h"
namespace i2p
{
namespace tunnel
{
Tunnel::Tunnel (std::shared_ptr<const TunnelConfig> config):
m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false)
{
}
Tunnel::~Tunnel ()
{
}
void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel)
{
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
auto numHops = m_Config->GetNumHops ();
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
auto msg = NewI2NPShortMessage ();
*msg->GetPayload () = numRecords;
msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1;
// shuffle records
std::vector<int> recordIndicies;
for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
std::random_shuffle (recordIndicies.begin(), recordIndicies.end());
// create real records
uint8_t * records = msg->GetPayload () + 1;
TunnelHopConfig * hop = m_Config->GetFirstHop ();
int i = 0;
while (hop)
{
int idx = recordIndicies[i];
hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE,
hop->next ? rnd.GenerateWord32 () : replyMsgID); // we set replyMsgID for last hop only
hop->recordIndex = idx;
i++;
hop = hop->next;
}
// fill up fake records with random data
for (int i = numHops; i < numRecords; i++)
{
int idx = recordIndicies[i];
rnd.GenerateBlock (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE);
}
// decrypt real records
i2p::crypto::CBCDecryption decryption;
hop = m_Config->GetLastHop ()->prev;
while (hop)
{
decryption.SetKey (hop->replyKey);
// decrypt records after current hop
TunnelHopConfig * hop1 = hop->next;
while (hop1)
{
decryption.SetIV (hop->replyIV);
uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
hop1 = hop1->next;
}
hop = hop->prev;
}
msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild);
// send message
if (outboundTunnel)
outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, ToSharedI2NPMessage (msg));
else
i2p::transport::transports.SendMessage (GetNextIdentHash (), ToSharedI2NPMessage (msg));
}
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
{
LogPrint ("TunnelBuildResponse ", (int)msg[0], " records.");
i2p::crypto::CBCDecryption decryption;
TunnelHopConfig * hop = m_Config->GetLastHop ();
while (hop)
{
decryption.SetKey (hop->replyKey);
// decrypt records before and including current hop
TunnelHopConfig * hop1 = hop;
while (hop1)
{
auto idx = hop1->recordIndex;
if (idx >= 0 && idx < msg[0])
{
uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE;
decryption.SetIV (hop->replyIV);
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
}
else
LogPrint ("Tunnel hop index ", idx, " is out of range");
hop1 = hop1->prev;
}
hop = hop->prev;
}
bool established = true;
hop = m_Config->GetFirstHop ();
while (hop)
{
const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET];
LogPrint ("Ret code=", (int)ret);
hop->router->GetProfile ()->TunnelBuildResponse (ret);
if (ret)
// if any of participants declined the tunnel is not established
established = false;
hop = hop->next;
}
if (established)
{
// change reply keys to layer keys
hop = m_Config->GetFirstHop ();
while (hop)
{
hop->decryption.SetKeys (hop->layerKey, hop->ivKey);
hop = hop->next;
}
}
if (established) m_State = eTunnelStateEstablished;
return established;
}
void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
{
const uint8_t * inPayload = in->GetPayload () + 4;
uint8_t * outPayload = out->GetPayload () + 4;
TunnelHopConfig * hop = m_Config->GetLastHop ();
while (hop)
{
hop->decryption.Decrypt (inPayload, outPayload);
hop = hop->prev;
inPayload = outPayload;
}
}
void Tunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions");
}
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg)
{
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (msg, newMsg);
newMsg->from = shared_from_this ();
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
}
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
if (gwHash)
{
block.hash = gwHash;
if (gwTunnel)
{
block.deliveryType = eDeliveryTypeTunnel;
block.tunnelID = gwTunnel;
}
else
block.deliveryType = eDeliveryTypeRouter;
}
else
block.deliveryType = eDeliveryTypeLocal;
block.data = msg;
std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.SendTunnelDataMsg (block);
}
void OutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
{
std::unique_lock<std::mutex> l(m_SendMutex);
for (auto& it : msgs)
m_Gateway.PutTunnelDataMsg (it);
m_Gateway.SendBuffer ();
}
void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ());
}
Tunnels tunnels;
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
{
}
Tunnels::~Tunnels ()
{
for (auto& it : m_TransitTunnels)
delete it.second;
m_TransitTunnels.clear ();
}
std::shared_ptr<InboundTunnel> Tunnels::GetInboundTunnel (uint32_t tunnelID)
{
auto it = m_InboundTunnels.find(tunnelID);
if (it != m_InboundTunnels.end ())
return it->second;
return nullptr;
}
TransitTunnel * Tunnels::GetTransitTunnel (uint32_t tunnelID)
{
auto it = m_TransitTunnels.find(tunnelID);
if (it != m_TransitTunnels.end ())
return it->second;
return nullptr;
}
std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
{
return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
}
std::shared_ptr<OutboundTunnel> Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID)
{
return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels);
}
template<class TTunnel>
std::shared_ptr<TTunnel> Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels)
{
auto it = pendingTunnels.find(replyMsgID);
if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending)
{
it->second->SetState (eTunnelStateBuildReplyReceived);
return it->second;
}
return nullptr;
}
std::shared_ptr<InboundTunnel> Tunnels::GetNextInboundTunnel ()
{
std::shared_ptr<InboundTunnel> tunnel;
size_t minReceived = 0;
for (auto it : m_InboundTunnels)
{
if (!it.second->IsEstablished ()) continue;
if (!tunnel || it.second->GetNumReceivedBytes () < minReceived)
{
tunnel = it.second;
minReceived = it.second->GetNumReceivedBytes ();
}
}
return tunnel;
}
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
{
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
uint32_t ind = rnd.GenerateWord32 (0, m_OutboundTunnels.size () - 1), i = 0;
std::shared_ptr<OutboundTunnel> tunnel;
for (auto it: m_OutboundTunnels)
{
if (it->IsEstablished ())
{
tunnel = it;
i++;
}
if (i > ind && tunnel) break;
}
return tunnel;
}
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels)
{
auto pool = std::make_shared<TunnelPool> (localDestination, numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels);
std::unique_lock<std::mutex> l(m_PoolsMutex);
m_Pools.push_back (pool);
return pool;
}
void Tunnels::DeleteTunnelPool (std::shared_ptr<TunnelPool> pool)
{
if (pool)
{
StopTunnelPool (pool);
{
std::unique_lock<std::mutex> l(m_PoolsMutex);
m_Pools.remove (pool);
}
}
}
void Tunnels::StopTunnelPool (std::shared_ptr<TunnelPool> pool)
{
if (pool)
{
pool->SetActive (false);
pool->DetachTunnels ();
}
}
void Tunnels::AddTransitTunnel (TransitTunnel * tunnel)
{
std::unique_lock<std::mutex> l(m_TransitTunnelsMutex);
if (!m_TransitTunnels.insert (std::make_pair (tunnel->GetTunnelID (), tunnel)).second)
{
LogPrint (eLogError, "Transit tunnel ", tunnel->GetTunnelID (), " already exists");
delete tunnel;
}
}
void Tunnels::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
}
void Tunnels::Stop ()
{
m_IsRunning = false;
m_Queue.WakeUp ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
}
void Tunnels::Run ()
{
std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
uint64_t lastTs = 0;
while (m_IsRunning)
{
try
{
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
{
uint32_t prevTunnelID = 0, tunnelID = 0;
TunnelBase * prevTunnel = nullptr;
do
{
TunnelBase * tunnel = nullptr;
uint8_t typeID = msg->GetTypeID ();
switch (typeID)
{
case eI2NPTunnelData:
case eI2NPTunnelGateway:
{
tunnelID = bufbe32toh (msg->GetPayload ());
if (tunnelID == prevTunnelID)
tunnel = prevTunnel;
else if (prevTunnel)
prevTunnel->FlushTunnelDataMsgs ();
if (!tunnel && typeID == eI2NPTunnelData)
tunnel = GetInboundTunnel (tunnelID).get ();
if (!tunnel)
tunnel = GetTransitTunnel (tunnelID);
if (tunnel)
{
if (typeID == eI2NPTunnelData)
tunnel->HandleTunnelDataMsg (msg);
else // tunnel gateway assumed
HandleTunnelGatewayMsg (tunnel, msg);
}
else
LogPrint (eLogWarning, "Tunnel ", tunnelID, " not found");
break;
}
case eI2NPVariableTunnelBuild:
case eI2NPVariableTunnelBuildReply:
case eI2NPTunnelBuild:
case eI2NPTunnelBuildReply:
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
break;
default:
LogPrint (eLogError, "Unexpected messsage type ", (int)typeID);
}
msg = m_Queue.Get ();
if (msg)
{
prevTunnelID = tunnelID;
prevTunnel = tunnel;
}
else if (tunnel)
tunnel->FlushTunnelDataMsgs ();
}
while (msg);
}
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
{
ManageTunnels ();
lastTs = ts;
}
}
catch (std::exception& ex)
{
LogPrint ("Tunnels: ", ex.what ());
}
}
}
void Tunnels::HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr<I2NPMessage> msg)
{
if (!tunnel)
{
LogPrint (eLogError, "Missing tunnel for TunnelGateway");
return;
}
const uint8_t * payload = msg->GetPayload ();
uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET);
// we make payload as new I2NP message to send
msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
msg->len = msg->offset + len;
auto typeID = msg->GetTypeID ();
LogPrint (eLogDebug, "TunnelGateway of ", (int)len, " bytes for tunnel ", tunnel->GetTunnelID (), ". Msg type ", (int)typeID);
if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply)
// transit DatabaseStore my contain new/updated RI
// or DatabaseSearchReply with new routers
i2p::data::netdb.PostI2NPMsg (msg);
tunnel->SendTunnelDataMsg (msg);
}
void Tunnels::ManageTunnels ()
{
ManagePendingTunnels ();
ManageInboundTunnels ();
ManageOutboundTunnels ();
ManageTransitTunnels ();
ManageTunnelPools ();
}
void Tunnels::ManagePendingTunnels ()
{
ManagePendingTunnels (m_PendingInboundTunnels);
ManagePendingTunnels (m_PendingOutboundTunnels);
}
template<class PendingTunnels>
void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels)
{
// check pending tunnel. delete failed or timeout
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();)
{
auto tunnel = it->second;
switch (tunnel->GetState ())
{
case eTunnelStatePending:
if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT)
{
LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted");
// update stats
auto config = tunnel->GetTunnelConfig ();
if (config)
{
auto hop = config->GetFirstHop ();
while (hop)
{
if (hop->router)
hop->router->GetProfile ()->TunnelNonReplied ();
hop = hop->next;
}
}
// delete
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
}
else
it++;
break;
case eTunnelStateBuildFailed:
LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted");
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
break;
case eTunnelStateBuildReplyReceived:
// intermediate state, will be either established of build failed
it++;
break;
default:
// success
it = pendingTunnels.erase (it);
m_NumSuccesiveTunnelCreations++;
}
}
}
void Tunnels::ManageOutboundTunnels ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
{
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
it = m_OutboundTunnels.erase (it);
}
else
{
if (tunnel->IsEstablished ())
{
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
tunnel->SetIsRecreated ();
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->RecreateOutboundTunnel (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring);
}
it++;
}
}
}
if (m_OutboundTunnels.size () < 5)
{
// trying to create one more oubound tunnel
auto inboundTunnel = GetNextInboundTunnel ();
auto router = i2p::data::netdb.GetRandomRouter ();
if (!inboundTunnel || !router) return;
LogPrint ("Creating one hop outbound tunnel...");
CreateTunnel<OutboundTunnel> (
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::RouterInfo> > { router },
inboundTunnel->GetTunnelConfig ())
);
}
}
void Tunnels::ManageInboundTunnels ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
{
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
{
auto tunnel = it->second;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
it = m_InboundTunnels.erase (it);
}
else
{
if (tunnel->IsEstablished ())
{
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
tunnel->SetIsRecreated ();
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->RecreateInboundTunnel (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring);
}
it++;
}
}
}
if (m_InboundTunnels.empty ())
{
LogPrint ("Creating zero hops inbound tunnel...");
CreateZeroHopsInboundTunnel ();
if (!m_ExploratoryPool)
m_ExploratoryPool = CreateTunnelPool (&i2p::context, 2, 2, 5, 5); // 2-hop exploratory, 5 tunnels
return;
}
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5)
{
// trying to create one more inbound tunnel
auto router = i2p::data::netdb.GetRandomRouter ();
LogPrint ("Creating one hop inbound tunnel...");
CreateTunnel<InboundTunnel> (
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::RouterInfo> > { router })
);
}
}
void Tunnels::ManageTransitTunnels ()
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
{
if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
auto tmp = it->second;
LogPrint ("Transit tunnel ", tmp->GetTunnelID (), " expired");
{
std::unique_lock<std::mutex> l(m_TransitTunnelsMutex);
it = m_TransitTunnels.erase (it);
}
delete tmp;
}
else
it++;
}
}
void Tunnels::ManageTunnelPools ()
{
std::unique_lock<std::mutex> l(m_PoolsMutex);
for (auto it: m_Pools)
{
auto pool = it;
if (pool && pool->IsActive ())
{
pool->CreateTunnels ();
pool->TestTunnels ();
}
}
}
void Tunnels::PostTunnelData (std::shared_ptr<I2NPMessage> msg)
{
if (msg) m_Queue.Put (msg);
}
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Queue.Put (msgs);
}
template<class TTunnel>
std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel)
{
auto newTunnel = std::make_shared<TTunnel> (config);
uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 ();
AddPendingTunnel (replyMsgID, newTunnel);
newTunnel->Build (replyMsgID, outboundTunnel);
return newTunnel;
}
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel)
{
m_PendingInboundTunnels[replyMsgID] = tunnel;
}
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel)
{
m_PendingOutboundTunnels[replyMsgID] = tunnel;
}
void Tunnels::AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel)
{
m_OutboundTunnels.push_back (newTunnel);
auto pool = newTunnel->GetTunnelPool ();
if (pool && pool->IsActive ())
pool->TunnelCreated (newTunnel);
else
newTunnel->SetTunnelPool (nullptr);
}
void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
{
m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel;
auto pool = newTunnel->GetTunnelPool ();
if (!pool)
{
// build symmetric outbound tunnel
CreateTunnel<OutboundTunnel> (newTunnel->GetTunnelConfig ()->Invert (), GetNextOutboundTunnel ());
}
else
{
if (pool->IsActive ())
pool->TunnelCreated (newTunnel);
else
newTunnel->SetTunnelPool (nullptr);
}
}
void Tunnels::CreateZeroHopsInboundTunnel ()
{
CreateTunnel<InboundTunnel> (
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::RouterInfo> >
{
i2p::context.GetSharedRouterInfo ()
}));
}
int Tunnels::GetTransitTunnelsExpirationTimeout ()
{
int timeout = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::unique_lock<std::mutex> l(m_TransitTunnelsMutex);
for (auto it: m_TransitTunnels)
{
int t = it.second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
if (t > timeout) timeout = t;
}
return timeout;
}
}
}

203
core/tunnel/Tunnel.h Normal file
View file

@ -0,0 +1,203 @@
#ifndef TUNNEL_H__
#define TUNNEL_H__
#include <inttypes.h>
#include <map>
#include <list>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include "util/Queue.h"
#include "TunnelConfig.h"
#include "TunnelPool.h"
#include "TransitTunnel.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
#include "TunnelBase.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace tunnel
{
const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes
const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute
const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes
const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message
enum TunnelState
{
eTunnelStatePending,
eTunnelStateBuildReplyReceived,
eTunnelStateBuildFailed,
eTunnelStateEstablished,
eTunnelStateTestFailed,
eTunnelStateFailed,
eTunnelStateExpiring
};
class OutboundTunnel;
class InboundTunnel;
class Tunnel: public TunnelBase
{
public:
Tunnel (std::shared_ptr<const TunnelConfig> config);
~Tunnel ();
void Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
std::shared_ptr<const TunnelConfig> GetTunnelConfig () const { return m_Config; }
TunnelState GetState () const { return m_State; };
void SetState (TunnelState state) { m_State = state; };
bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
bool IsFailed () const { return m_State == eTunnelStateFailed; };
bool IsRecreated () const { return m_IsRecreated; };
void SetIsRecreated () { m_IsRecreated = true; };
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
bool HandleTunnelBuildResponse (uint8_t * msg, size_t len);
// implements TunnelBase
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; };
const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); };
private:
std::shared_ptr<const TunnelConfig> m_Config;
std::shared_ptr<TunnelPool> m_Pool; // pool, tunnel belongs to, or null
TunnelState m_State;
bool m_IsRecreated;
};
class OutboundTunnel: public Tunnel
{
public:
OutboundTunnel (std::shared_ptr<const TunnelConfig> config): Tunnel (config), m_Gateway (this) {};
void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
std::shared_ptr<const i2p::data::RouterInfo> GetEndpointRouter () const
{ return GetTunnelConfig ()->GetLastHop ()->router; };
size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); };
// implements TunnelBase
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
uint32_t GetTunnelID () const { return GetNextTunnelID (); };
private:
std::mutex m_SendMutex;
TunnelGateway m_Gateway;
};
class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
{
public:
InboundTunnel (std::shared_ptr<const TunnelConfig> config): Tunnel (config), m_Endpoint (true) {};
void HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg);
size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
// implements TunnelBase
uint32_t GetTunnelID () const { return GetTunnelConfig ()->GetLastHop ()->nextTunnelID; };
private:
TunnelEndpoint m_Endpoint;
};
class Tunnels
{
public:
Tunnels ();
~Tunnels ();
void Start ();
void Stop ();
std::shared_ptr<InboundTunnel> GetInboundTunnel (uint32_t tunnelID);
std::shared_ptr<InboundTunnel> GetPendingInboundTunnel (uint32_t replyMsgID);
std::shared_ptr<OutboundTunnel> GetPendingOutboundTunnel (uint32_t replyMsgID);
std::shared_ptr<InboundTunnel> GetNextInboundTunnel ();
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
TransitTunnel * GetTransitTunnel (uint32_t tunnelID);
int GetTransitTunnelsExpirationTimeout ();
void AddTransitTunnel (TransitTunnel * tunnel);
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
template<class TTunnel>
std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
std::shared_ptr<TunnelPool> CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels);
void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
private:
template<class TTunnel>
std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
void HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr<I2NPMessage> msg);
void Run ();
void ManageTunnels ();
void ManageOutboundTunnels ();
void ManageInboundTunnels ();
void ManageTransitTunnels ();
void ManagePendingTunnels ();
template<class PendingTunnels>
void ManagePendingTunnels (PendingTunnels& pendingTunnels);
void ManageTunnelPools ();
void CreateZeroHopsInboundTunnel ();
private:
bool m_IsRunning;
std::thread * m_Thread;
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_InboundTunnels;
std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
std::mutex m_TransitTunnelsMutex;
std::map<uint32_t, TransitTunnel *> m_TransitTunnels;
std::mutex m_PoolsMutex;
std::list<std::shared_ptr<TunnelPool>> m_Pools;
std::shared_ptr<TunnelPool> m_ExploratoryPool;
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
// some stats
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
public:
// for HTTP only
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
int GetQueueSize () { return m_Queue.GetSize (); };
int GetTunnelCreationSuccessRate () const // in percents
{
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
}
};
extern Tunnels tunnels;
}
}
#endif

69
core/tunnel/TunnelBase.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef TUNNEL_BASE_H__
#define TUNNEL_BASE_H__
#include <inttypes.h>
#include <memory>
#include "util/Timestamp.h"
#include "I2NPProtocol.h"
#include "Identity.h"
namespace i2p
{
namespace tunnel
{
const size_t TUNNEL_DATA_MSG_SIZE = 1028;
const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008;
const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003;
enum TunnelDeliveryType
{
eDeliveryTypeLocal = 0,
eDeliveryTypeTunnel = 1,
eDeliveryTypeRouter = 2
};
struct TunnelMessageBlock
{
TunnelDeliveryType deliveryType;
i2p::data::IdentHash hash;
uint32_t tunnelID;
std::shared_ptr<I2NPMessage> data;
};
class TunnelBase
{
public:
//WARNING!!! GetSecondsSinceEpoch() return uint64_t
TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
virtual ~TunnelBase () {};
virtual void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) = 0;
virtual void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) = 0;
virtual void FlushTunnelDataMsgs () {};
virtual void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) = 0;
virtual uint32_t GetNextTunnelID () const = 0;
virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0;
virtual uint32_t GetTunnelID () const = 0; // as known at our side
uint32_t GetCreationTime () const { return m_CreationTime; };
void SetCreationTime (uint32_t t) { m_CreationTime = t; };
private:
uint32_t m_CreationTime; // seconds since epoch
};
struct TunnelCreationTimeCmp
{
bool operator() (std::shared_ptr<const TunnelBase> t1, std::shared_ptr<const TunnelBase> t2) const
{
if (t1->GetCreationTime () != t2->GetCreationTime ())
return t1->GetCreationTime () > t2->GetCreationTime ();
else
return t1 < t2;
};
};
}
}
#endif

233
core/tunnel/TunnelConfig.h Normal file
View file

@ -0,0 +1,233 @@
#ifndef TUNNEL_CONFIG_H__
#define TUNNEL_CONFIG_H__
#include <inttypes.h>
#include <sstream>
#include <vector>
#include <memory>
#include "TunnelCrypto.h"
#include "RouterInfo.h"
#include "RouterContext.h"
#include "util/Timestamp.h"
namespace i2p
{
namespace tunnel
{
struct TunnelHopConfig
{
std::shared_ptr<const i2p::data::RouterInfo> router, nextRouter;
uint32_t tunnelID, nextTunnelID;
uint8_t layerKey[32];
uint8_t ivKey[32];
uint8_t replyKey[32];
uint8_t replyIV[16];
bool isGateway, isEndpoint;
TunnelHopConfig * next, * prev;
i2p::crypto::TunnelDecryption decryption;
int recordIndex; // record # in tunnel build message
TunnelHopConfig (std::shared_ptr<const i2p::data::RouterInfo> r)
{
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
rnd.GenerateBlock (layerKey, 32);
rnd.GenerateBlock (ivKey, 32);
rnd.GenerateBlock (replyIV, 16);
tunnelID = rnd.GenerateWord32 ();
isGateway = true;
isEndpoint = true;
router = r;
//nextRouter = nullptr;
nextTunnelID = 0;
next = nullptr;
prev = nullptr;
}
void SetNextRouter (std::shared_ptr<const i2p::data::RouterInfo> r)
{
nextRouter = r;
isEndpoint = false;
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
nextTunnelID = rnd.GenerateWord32 ();
}
void SetReplyHop (const TunnelHopConfig * replyFirstHop)
{
nextRouter = replyFirstHop->router;
nextTunnelID = replyFirstHop->tunnelID;
isEndpoint = true;
}
void SetNext (TunnelHopConfig * n)
{
next = n;
if (next)
{
next->prev = this;
next->isGateway = false;
isEndpoint = false;
nextRouter = next->router;
nextTunnelID = next->tunnelID;
}
}
void SetPrev (TunnelHopConfig * p)
{
prev = p;
if (prev)
{
prev->next = this;
prev->isEndpoint = false;
isGateway = false;
}
}
void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID) const
{
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE] = {};
htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, router->GetIdentHash (), 32);
htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextRouter->GetIdentHash (), 32);
memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16);
uint8_t flag = 0;
if (isGateway) flag |= 0x80;
if (isEndpoint) flag |= 0x40;
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag;
htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ());
htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
// TODO: fill padding
router->GetElGamalEncryption ()->Encrypt (clearText, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET);
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)router->GetIdentHash (), 16);
}
};
class TunnelConfig: public std::enable_shared_from_this<TunnelConfig>
{
public:
TunnelConfig (std::vector<std::shared_ptr<const i2p::data::RouterInfo> > peers,
std::shared_ptr<const TunnelConfig> replyTunnelConfig = nullptr) // replyTunnelConfig=nullptr means inbound
{
TunnelHopConfig * prev = nullptr;
for (auto it: peers)
{
auto hop = new TunnelHopConfig (it);
if (prev)
prev->SetNext (hop);
else
m_FirstHop = hop;
prev = hop;
}
m_LastHop = prev;
if (replyTunnelConfig) // outbound
{
m_FirstHop->isGateway = false;
m_LastHop->SetReplyHop (replyTunnelConfig->GetFirstHop ());
}
else // inbound
m_LastHop->SetNextRouter (i2p::context.GetSharedRouterInfo ());
}
~TunnelConfig ()
{
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
auto tmp = hop;
hop = hop->next;
delete tmp;
}
}
TunnelHopConfig * GetFirstHop () const
{
return m_FirstHop;
}
TunnelHopConfig * GetLastHop () const
{
return m_LastHop;
}
int GetNumHops () const
{
int num = 0;
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
num++;
hop = hop->next;
}
return num;
}
bool IsInbound () const { return m_FirstHop->isGateway; }
std::vector<std::shared_ptr<const i2p::data::RouterInfo> > GetPeers () const
{
std::vector<std::shared_ptr<const i2p::data::RouterInfo> > peers;
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
peers.push_back (hop->router);
hop = hop->next;
}
return peers;
}
void Print (std::stringstream& s) const
{
TunnelHopConfig * hop = m_FirstHop;
if (!IsInbound ()) // outbound
s << "me";
s << "-->" << m_FirstHop->tunnelID;
while (hop)
{
s << ":" << hop->router->GetIdentHashAbbreviation () << "-->";
if (!hop->isEndpoint)
s << hop->nextTunnelID;
else
return;
hop = hop->next;
}
// we didn't reach enpoint that mean we are last hop
s << ":me";
}
std::shared_ptr<TunnelConfig> Invert () const
{
auto peers = GetPeers ();
std::reverse (peers.begin (), peers.end ());
// we use ourself as reply tunnel for outbound tunnel
return IsInbound () ? std::make_shared<TunnelConfig>(peers, shared_from_this ()) : std::make_shared<TunnelConfig>(peers);
}
std::shared_ptr<TunnelConfig> Clone (std::shared_ptr<const TunnelConfig> replyTunnelConfig = nullptr) const
{
return std::make_shared<TunnelConfig> (GetPeers (), replyTunnelConfig);
}
private:
// this constructor can't be called from outside
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr)
{
}
private:
TunnelHopConfig * m_FirstHop, * m_LastHop;
};
}
}
#endif

View file

@ -0,0 +1,88 @@
#include "TunnelCrypto.h"
#include "TunnelBase.h"
#include "crypto/AESNIMacros.h"
namespace i2p {
namespace crypto {
void TunnelEncryption::SetKeys (const AESKey& layerKey, const AESKey& ivKey)
{
m_LayerEncryption.SetKey (layerKey);
m_IVEncryption.SetKey (ivKey);
}
void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
// encrypt IV
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
EncryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// encrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched_l)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "cc", "memory"
);
#else
m_IVEncryption.Encrypt ((const CipherBlock *)in, (CipherBlock *)out); // iv
m_LayerEncryption.SetIV (out);
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVEncryption.Encrypt ((CipherBlock *)out, (CipherBlock *)out); // double iv
#endif
}
void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
// decrypt IV
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
DecryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// decrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched_l)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
#else
m_IVDecryption.Decrypt ((const CipherBlock *)in, (CipherBlock *)out); // iv
m_LayerDecryption.SetIV (out);
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVDecryption.Decrypt ((CipherBlock *)out, (CipherBlock *)out); // double iv
#endif
}
} // crypto
} // i2p

View file

@ -0,0 +1,49 @@
#ifndef TUNNEL_CRYPTO_H__
#define TUNNEL_CRYPTO_H__
#include "crypto/aes.h"
namespace i2p {
namespace crypto {
class TunnelEncryption { // with double IV encryption
public:
void SetKeys (const AESKey& layerKey, const AESKey& ivKey);
void Encrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
private:
ECBEncryption m_IVEncryption;
#ifdef AESNI
ECBEncryption m_LayerEncryption;
#else
CBCEncryption m_LayerEncryption;
#endif
};
class TunnelDecryption { // with double IV encryption
public:
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
{
m_LayerDecryption.SetKey (layerKey);
m_IVDecryption.SetKey (ivKey);
}
void Decrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
private:
ECBDecryption m_IVDecryption;
#ifdef AESNI
ECBDecryption m_LayerDecryption;
#else
CBCDecryption m_LayerDecryption;
#endif
};
} // crypto
} // i2p
#endif

View file

@ -0,0 +1,259 @@
#include "util/I2PEndian.h"
#include <string.h>
#include "util/Log.h"
#include "NetDb.h"
#include "I2NPProtocol.h"
#include "transport/Transports.h"
#include "RouterContext.h"
#include "TunnelEndpoint.h"
namespace i2p
{
namespace tunnel
{
TunnelEndpoint::~TunnelEndpoint ()
{
}
void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg)
{
m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE;
uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16
uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum
if (zero)
{
uint8_t * fragment = zero + 1;
// verify checksum
memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end
uint8_t hash[32];
CryptoPP::SHA256().CalculateDigest (hash, fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16); // payload + iv
if (memcmp (hash, decrypted, 4))
{
LogPrint (eLogError, "TunnelMessage: checksum verification failed");
return;
}
// process fragments
while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
{
uint8_t flag = fragment[0];
fragment++;
bool isFollowOnFragment = flag & 0x80, isLastFragment = true;
uint32_t msgID = 0;
int fragmentNum = 0;
TunnelMessageBlockEx m;
if (!isFollowOnFragment)
{
// first fragment
m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03);
switch (m.deliveryType)
{
case eDeliveryTypeLocal: // 0
break;
case eDeliveryTypeTunnel: // 1
m.tunnelID = bufbe32toh (fragment);
fragment += 4; // tunnelID
m.hash = i2p::data::IdentHash (fragment);
fragment += 32; // hash
break;
case eDeliveryTypeRouter: // 2
m.hash = i2p::data::IdentHash (fragment);
fragment += 32; // to hash
break;
default:
;
}
bool isFragmented = flag & 0x08;
if (isFragmented)
{
// Message ID
msgID = bufbe32toh (fragment);
fragment += 4;
isLastFragment = false;
}
}
else
{
// follow on
msgID = bufbe32toh (fragment); // MessageID
fragment += 4;
fragmentNum = (flag >> 1) & 0x3F; // 6 bits
isLastFragment = flag & 0x01;
}
uint16_t size = bufbe16toh (fragment);
fragment += 2;
msg->offset = fragment - msg->buf;
msg->len = msg->offset + size;
if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
{
// this is not last message. we have to copy it
m.data = ToSharedI2NPMessage (NewI2NPShortMessage ());
m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header
m.data->len += TUNNEL_GATEWAY_HEADER_SIZE;
*(m.data) = *msg;
}
else
m.data = msg;
if (!isFollowOnFragment && isLastFragment)
HandleNextMessage (m);
else
{
if (msgID) // msgID is presented, assume message is fragmented
{
if (!isFollowOnFragment) // create new incomlete message
{
m.nextFragmentNum = 1;
auto ret = m_IncompleteMessages.insert (std::pair<uint32_t, TunnelMessageBlockEx>(msgID, m));
if (ret.second)
HandleOutOfSequenceFragment (msgID, ret.first->second);
else
LogPrint (eLogError, "Incomplete message ", msgID, "already exists");
}
else
{
m.nextFragmentNum = fragmentNum;
HandleFollowOnFragment (msgID, isLastFragment, m);
}
}
else
LogPrint (eLogError, "Message is fragmented, but msgID is not presented");
}
fragment += size;
}
}
else
LogPrint (eLogError, "TunnelMessage: zero not found");
}
void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m)
{
auto fragment = m.data->GetBuffer ();
auto size = m.data->GetLength ();
auto it = m_IncompleteMessages.find (msgID);
if (it != m_IncompleteMessages.end())
{
auto& msg = it->second;
if (m.nextFragmentNum == msg.nextFragmentNum)
{
if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long
{
if (msg.data->len + size > msg.data->maxLen)
{
LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ());
*newMsg = *(msg.data);
msg.data = newMsg;
}
memcpy (msg.data->buf + msg.data->len, fragment, size); // concatenate fragment
msg.data->len += size;
if (isLastFragment)
{
// message complete
HandleNextMessage (msg);
m_IncompleteMessages.erase (it);
}
else
{
msg.nextFragmentNum++;
HandleOutOfSequenceFragment (msgID, msg);
}
}
else
{
LogPrint (eLogError, "Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped");
m_IncompleteMessages.erase (it);
}
}
else
{
LogPrint (eLogInfo, "Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ". Saved");
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
}
}
else
{
LogPrint (eLogInfo, "First fragment of message ", msgID, " not found. Saved");
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
}
}
void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data)
{
auto it = m_OutOfSequenceFragments.find (msgID);
if (it == m_OutOfSequenceFragments.end ())
m_OutOfSequenceFragments.insert (std::pair<uint32_t, Fragment> (msgID, {fragmentNum, isLastFragment, data}));
}
void TunnelEndpoint::HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg)
{
auto it = m_OutOfSequenceFragments.find (msgID);
if (it != m_OutOfSequenceFragments.end ())
{
if (it->second.fragmentNum == msg.nextFragmentNum)
{
LogPrint (eLogInfo, "Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found");
auto size = it->second.data->GetLength ();
if (msg.data->len + size > msg.data->maxLen)
{
LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ());
*newMsg = *(msg.data);
msg.data = newMsg;
}
memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment
msg.data->len += size;
if (it->second.isLastFragment)
{
// message complete
HandleNextMessage (msg);
m_IncompleteMessages.erase (msgID);
}
else
msg.nextFragmentNum++;
m_OutOfSequenceFragments.erase (it);
}
}
}
void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg)
{
LogPrint (eLogInfo, "TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)msg.data->GetTypeID ());
switch (msg.deliveryType)
{
case eDeliveryTypeLocal:
i2p::HandleI2NPMessage (msg.data);
break;
case eDeliveryTypeTunnel:
i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
break;
case eDeliveryTypeRouter:
if (msg.hash == i2p::context.GetRouterInfo ().GetIdentHash ()) // check if message is sent to us
i2p::HandleI2NPMessage (msg.data);
else
{
// to somebody else
if (!m_IsInbound) // outbound transit tunnel
{
/* auto typeID = msg.data->GetTypeID ();
if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply )
// catch RI or reply with new list of routers
i2p::data::netdb.PostI2NPMsg (msg.data);*/
i2p::transport::transports.SendMessage (msg.hash, msg.data);
}
else // we shouldn't send this message. possible leakage
LogPrint (eLogError, "Message to another router arrived from an inbound tunnel. Dropped");
}
break;
default:
LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType);
};
}
}
}

View file

@ -0,0 +1,54 @@
#ifndef TUNNEL_ENDPOINT_H__
#define TUNNEL_ENDPOINT_H__
#include <inttypes.h>
#include <map>
#include <string>
#include "I2NPProtocol.h"
#include "TunnelBase.h"
namespace i2p
{
namespace tunnel
{
class TunnelEndpoint
{
struct TunnelMessageBlockEx: public TunnelMessageBlock
{
uint8_t nextFragmentNum;
};
struct Fragment
{
uint8_t fragmentNum;
bool isLastFragment;
std::shared_ptr<I2NPMessage> data;
};
public:
TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {};
~TunnelEndpoint ();
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
void HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg);
private:
void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m);
void HandleNextMessage (const TunnelMessageBlock& msg);
void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data);
void HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg);
private:
std::map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages;
std::map<uint32_t, Fragment> m_OutOfSequenceFragments;
bool m_IsInbound;
size_t m_NumReceivedBytes;
};
}
}
#endif

View file

@ -0,0 +1,212 @@
#include <string.h>
#include "util/I2PEndian.h"
#include <cryptopp/sha.h>
#include "util/Log.h"
#include "RouterContext.h"
#include "transport/Transports.h"
#include "TunnelGateway.h"
namespace i2p
{
namespace tunnel
{
TunnelGatewayBuffer::TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID),
m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0)
{
context.GetRandomNumberGenerator ().GenerateBlock (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE);
for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++)
if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1;
}
TunnelGatewayBuffer::~TunnelGatewayBuffer ()
{
}
void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block)
{
bool messageCreated = false;
if (!m_CurrentTunnelDataMsg)
{
CreateCurrentTunnelDataMessage ();
messageCreated = true;
}
// create delivery instructions
uint8_t di[43]; // max delivery instruction length is 43 for tunnel
size_t diLen = 1;// flag
if (block.deliveryType != eDeliveryTypeLocal) // tunnel or router
{
if (block.deliveryType == eDeliveryTypeTunnel)
{
htobe32buf (di + diLen, block.tunnelID);
diLen += 4; // tunnelID
}
memcpy (di + diLen, block.hash, 32);
diLen += 32; //len
}
di[0] = block.deliveryType << 5; // set delivery type
// create fragments
std::shared_ptr<I2NPMessage> msg = block.data;
auto fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
if (fullMsgLen <= m_RemainingSize)
{
// message fits. First and last fragment
htobe16buf (di + diLen, msg->GetLength ());
diLen += 2; // size
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), msg->GetLength ());
m_CurrentTunnelDataMsg->len += diLen + msg->GetLength ();
m_RemainingSize -= diLen + msg->GetLength ();
if (!m_RemainingSize)
CompleteCurrentTunnelDataMessage ();
}
else
{
if (!messageCreated) // check if we should complete previous message
{
auto numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
// length of bytes don't fit full tunnel message
// every follow-on fragment adds 7 bytes
auto nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE;
if (!nonFit || nonFit > m_RemainingSize)
{
CompleteCurrentTunnelDataMessage ();
CreateCurrentTunnelDataMessage ();
}
}
if (diLen + 6 <= m_RemainingSize)
{
// delivery instructions fit
uint32_t msgID;
memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // in network bytes order
size_t size = m_RemainingSize - diLen - 6; // 6 = 4 (msgID) + 2 (size)
// first fragment
di[0] |= 0x08; // fragmented
htobuf32 (di + diLen, msgID);
diLen += 4; // Message ID
htobe16buf (di + diLen, size);
diLen += 2; // size
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size);
m_CurrentTunnelDataMsg->len += diLen + size;
CompleteCurrentTunnelDataMessage ();
// follow on fragments
int fragmentNumber = 1;
while (size < msg->GetLength ())
{
CreateCurrentTunnelDataMessage ();
uint8_t * buf = m_CurrentTunnelDataMsg->GetBuffer ();
buf[0] = 0x80 | (fragmentNumber << 1); // frag
bool isLastFragment = false;
size_t s = msg->GetLength () - size;
if (s > TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7) // 7 follow on instructions
s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7;
else // last fragment
{
buf[0] |= 0x01;
isLastFragment = true;
}
htobuf32 (buf + 1, msgID); //Message ID
htobe16buf (buf + 5, s); // size
memcpy (buf + 7, msg->GetBuffer () + size, s);
m_CurrentTunnelDataMsg->len += s+7;
if (isLastFragment)
{
m_RemainingSize -= s+7;
if (!m_RemainingSize)
CompleteCurrentTunnelDataMessage ();
}
else
CompleteCurrentTunnelDataMessage ();
size += s;
fragmentNumber++;
}
}
else
{
// delivery instructions don't fit. Create new message
CompleteCurrentTunnelDataMessage ();
PutI2NPMsg (block);
// don't delete msg because it's taken care inside
}
}
}
void TunnelGatewayBuffer::ClearTunnelDataMsgs ()
{
m_TunnelDataMsgs.clear ();
}
void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage ()
{
m_CurrentTunnelDataMsg = ToSharedI2NPMessage (NewI2NPShortMessage ());
m_CurrentTunnelDataMsg->Align (12);
// we reserve space for padding
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE;
m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset;
m_RemainingSize = TUNNEL_DATA_MAX_PAYLOAD_SIZE;
}
void TunnelGatewayBuffer::CompleteCurrentTunnelDataMessage ()
{
if (!m_CurrentTunnelDataMsg) return;
uint8_t * payload = m_CurrentTunnelDataMsg->GetBuffer ();
size_t size = m_CurrentTunnelDataMsg->len - m_CurrentTunnelDataMsg->offset;
m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE;
uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload ();
htobe32buf (buf, m_TunnelID);
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
rnd.GenerateBlock (buf + 4, 16); // original IV
memcpy (payload + size, buf + 4, 16); // copy IV for checksum
uint8_t hash[32];
CryptoPP::SHA256().CalculateDigest (hash, payload, size+16);
memcpy (buf+20, hash, 4); // checksum
payload[-1] = 0; // zero
ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1
if (paddingSize > 0)
{
// non-zero padding
auto randomOffset = rnd.GenerateWord32 (0, TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize);
memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize);
}
// we can't fill message header yet because encryption is required
m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg);
m_CurrentTunnelDataMsg = nullptr;
}
void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block)
{
if (block.data)
{
PutTunnelDataMsg (block);
SendBuffer ();
}
}
void TunnelGateway::PutTunnelDataMsg (const TunnelMessageBlock& block)
{
if (block.data)
m_Buffer.PutI2NPMsg (block);
}
void TunnelGateway::SendBuffer ()
{
m_Buffer.CompleteCurrentTunnelDataMessage ();
auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs ();
for (auto tunnelMsg : tunnelMsgs)
{
m_Tunnel->EncryptTunnelMsg (tunnelMsg, tunnelMsg);
tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData);
m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
}
i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), tunnelMsgs);
m_Buffer.ClearTunnelDataMsgs ();
}
}
}

View file

@ -0,0 +1,57 @@
#ifndef TUNNEL_GATEWAY_H__
#define TUNNEL_GATEWAY_H__
#include <inttypes.h>
#include <vector>
#include <memory>
#include "I2NPProtocol.h"
#include "TunnelBase.h"
namespace i2p
{
namespace tunnel
{
class TunnelGatewayBuffer
{
public:
TunnelGatewayBuffer (uint32_t tunnelID);
~TunnelGatewayBuffer ();
void PutI2NPMsg (const TunnelMessageBlock& block);
const std::vector<std::shared_ptr<I2NPMessage> >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; };
void ClearTunnelDataMsgs ();
void CompleteCurrentTunnelDataMessage ();
private:
void CreateCurrentTunnelDataMessage ();
private:
uint32_t m_TunnelID;
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelDataMsgs;
std::shared_ptr<I2NPMessage> m_CurrentTunnelDataMsg;
size_t m_RemainingSize;
uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE];
};
class TunnelGateway
{
public:
TunnelGateway (TunnelBase * tunnel):
m_Tunnel (tunnel), m_Buffer (tunnel->GetNextTunnelID ()), m_NumSentBytes (0) {};
void SendTunnelDataMsg (const TunnelMessageBlock& block);
void PutTunnelDataMsg (const TunnelMessageBlock& block);
void SendBuffer ();
size_t GetNumSentBytes () const { return m_NumSentBytes; };
private:
TunnelBase * m_Tunnel;
TunnelGatewayBuffer m_Buffer;
size_t m_NumSentBytes;
};
}
}
#endif

435
core/tunnel/TunnelPool.cpp Normal file
View file

@ -0,0 +1,435 @@
#include <algorithm>
#include "util/I2PEndian.h"
#include "crypto/CryptoConst.h"
#include "Tunnel.h"
#include "NetDb.h"
#include "util/Timestamp.h"
#include "Garlic.h"
#include "transport/Transports.h"
#include "TunnelPool.h"
namespace i2p
{
namespace tunnel
{
TunnelPool::TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels):
m_LocalDestination (localDestination), m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops),
m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true)
{
}
TunnelPool::~TunnelPool ()
{
DetachTunnels ();
}
void TunnelPool::SetExplicitPeers (std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers)
{
m_ExplicitPeers = explicitPeers;
if (m_ExplicitPeers)
{
int size = m_ExplicitPeers->size ();
if (m_NumInboundHops > size)
{
m_NumInboundHops = size;
LogPrint (eLogInfo, "Inbound tunnel length has beed adjusted to ", size, " for explicit peers");
}
if (m_NumOutboundHops > size)
{
m_NumOutboundHops = size;
LogPrint (eLogInfo, "Outbound tunnel length has beed adjusted to ", size, " for explicit peers");
}
m_NumInboundTunnels = 1;
m_NumOutboundTunnels = 1;
}
}
void TunnelPool::DetachTunnels ()
{
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
for (auto it: m_InboundTunnels)
it->SetTunnelPool (nullptr);
m_InboundTunnels.clear ();
}
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
for (auto it: m_OutboundTunnels)
it->SetTunnelPool (nullptr);
m_OutboundTunnels.clear ();
}
m_Tests.clear ();
}
void TunnelPool::TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel)
{
if (!m_IsActive) return;
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.insert (createdTunnel);
}
if (m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated ();
}
void TunnelPool::TunnelExpired (std::shared_ptr<InboundTunnel> expiredTunnel)
{
if (expiredTunnel)
{
expiredTunnel->SetTunnelPool (nullptr);
for (auto it: m_Tests)
if (it.second.second == expiredTunnel) it.second.second = nullptr;
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.erase (expiredTunnel);
}
}
void TunnelPool::TunnelCreated (std::shared_ptr<OutboundTunnel> createdTunnel)
{
if (!m_IsActive) return;
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.insert (createdTunnel);
}
//CreatePairedInboundTunnel (createdTunnel);
}
void TunnelPool::TunnelExpired (std::shared_ptr<OutboundTunnel> expiredTunnel)
{
if (expiredTunnel)
{
expiredTunnel->SetTunnelPool (nullptr);
for (auto it: m_Tests)
if (it.second.first == expiredTunnel) it.second.first = nullptr;
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.erase (expiredTunnel);
}
}
std::vector<std::shared_ptr<InboundTunnel> > TunnelPool::GetInboundTunnels (int num) const
{
std::vector<std::shared_ptr<InboundTunnel> > v;
int i = 0;
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
for (auto it : m_InboundTunnels)
{
if (i >= num) break;
if (it->IsEstablished ())
{
v.push_back (it);
i++;
}
}
return v;
}
std::shared_ptr<OutboundTunnel> TunnelPool::GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded) const
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
return GetNextTunnel (m_OutboundTunnels, excluded);
}
std::shared_ptr<InboundTunnel> TunnelPool::GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded) const
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
return GetNextTunnel (m_InboundTunnels, excluded);
}
template<class TTunnels>
typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const
{
if (tunnels.empty ()) return nullptr;
CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator ();
uint32_t ind = rnd.GenerateWord32 (0, tunnels.size ()/2), i = 0;
typename TTunnels::value_type tunnel = nullptr;
for (auto it: tunnels)
{
if (it->IsEstablished () && it != excluded)
{
tunnel = it;
i++;
}
if (i > ind && tunnel) break;
}
if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded;
return tunnel;
}
std::shared_ptr<OutboundTunnel> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const
{
if (old && old->IsEstablished ()) return old;
std::shared_ptr<OutboundTunnel> tunnel;
if (old)
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
for (auto it: m_OutboundTunnels)
if (it->IsEstablished () && old->GetEndpointRouter ()->GetIdentHash () == it->GetEndpointRouter ()->GetIdentHash ())
{
tunnel = it;
break;
}
}
if (!tunnel)
tunnel = GetNextOutboundTunnel ();
return tunnel;
}
void TunnelPool::CreateTunnels ()
{
int num = 0;
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
for (auto it : m_InboundTunnels)
if (it->IsEstablished ()) num++;
}
for (int i = num; i < m_NumInboundTunnels; i++)
CreateInboundTunnel ();
num = 0;
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
for (auto it : m_OutboundTunnels)
if (it->IsEstablished ()) num++;
}
for (int i = num; i < m_NumOutboundTunnels; i++)
CreateOutboundTunnel ();
}
void TunnelPool::TestTunnels ()
{
auto& rnd = i2p::context.GetRandomNumberGenerator ();
for (auto it: m_Tests)
{
LogPrint ("Tunnel test ", (int)it.first, " failed");
// if test failed again with another tunnel we consider it failed
if (it.second.first)
{
if (it.second.first->GetState () == eTunnelStateTestFailed)
{
it.second.first->SetState (eTunnelStateFailed);
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.erase (it.second.first);
}
else
it.second.first->SetState (eTunnelStateTestFailed);
}
if (it.second.second)
{
if (it.second.second->GetState () == eTunnelStateTestFailed)
{
it.second.second->SetState (eTunnelStateFailed);
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.erase (it.second.second);
}
if (m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated ();
}
else
it.second.second->SetState (eTunnelStateTestFailed);
}
}
m_Tests.clear ();
// new tests
auto it1 = m_OutboundTunnels.begin ();
auto it2 = m_InboundTunnels.begin ();
while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ())
{
bool failed = false;
if ((*it1)->IsFailed ())
{
failed = true;
it1++;
}
if ((*it2)->IsFailed ())
{
failed = true;
it2++;
}
if (!failed)
{
uint32_t msgID = rnd.GenerateWord32 ();
m_Tests[msgID] = std::make_pair (*it1, *it2);
(*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (),
CreateDeliveryStatusMsg (msgID));
it1++; it2++;
}
}
}
void TunnelPool::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
if (m_LocalDestination)
m_LocalDestination->ProcessGarlicMessage (msg);
else
LogPrint (eLogWarning, "Local destination doesn't exist. Dropped");
}
void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg)
{
const uint8_t * buf = msg->GetPayload ();
uint32_t msgID = bufbe32toh (buf);
buf += 4;
uint64_t timestamp = bufbe64toh (buf);
auto it = m_Tests.find (msgID);
if (it != m_Tests.end ())
{
// restore from test failed state if any
if (it->second.first->GetState () == eTunnelStateTestFailed)
it->second.first->SetState (eTunnelStateEstablished);
if (it->second.second->GetState () == eTunnelStateTestFailed)
it->second.second->SetState (eTunnelStateEstablished);
LogPrint ("Tunnel test ", it->first, " successive. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds");
m_Tests.erase (it);
}
else
{
if (m_LocalDestination)
m_LocalDestination->ProcessDeliveryStatusMessage (msg);
else
LogPrint (eLogWarning, "Local destination doesn't exist. Dropped");
}
}
std::shared_ptr<const i2p::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop) const
{
bool isExploratory = (m_LocalDestination == &i2p::context); // TODO: implement it better
auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop):
i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop);
if (!hop || hop->GetProfile ()->IsBad ())
hop = i2p::data::netdb.GetRandomRouter ();
return hop;
}
bool TunnelPool::SelectPeers (std::vector<std::shared_ptr<const i2p::data::RouterInfo> >& hops, bool isInbound)
{
if (m_ExplicitPeers) return SelectExplicitPeers (hops, isInbound);
auto prevHop = i2p::context.GetSharedRouterInfo ();
int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops;
if (i2p::transport::transports.GetNumPeers () > 25)
{
auto r = i2p::transport::transports.GetRandomPeer ();
if (r && !r->GetProfile ()->IsBad ())
{
prevHop = r;
hops.push_back (r);
numHops--;
}
}
for (int i = 0; i < numHops; i++)
{
auto hop = SelectNextHop (prevHop);
if (!hop)
{
LogPrint (eLogError, "Can't select next hop");
return false;
}
prevHop = hop;
hops.push_back (hop);
}
return true;
}
bool TunnelPool::SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::RouterInfo> >& hops, bool isInbound)
{
int size = m_ExplicitPeers->size ();
std::vector<int> peerIndicies;
for (int i = 0; i < size; i++) peerIndicies.push_back(i);
std::random_shuffle (peerIndicies.begin(), peerIndicies.end());
int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops;
for (int i = 0; i < numHops; i++)
{
auto& ident = (*m_ExplicitPeers)[peerIndicies[i]];
auto r = i2p::data::netdb.FindRouter (ident);
if (r)
hops.push_back (r);
else
{
LogPrint (eLogInfo, "Can't find router for ", ident.ToBase64 ());
i2p::data::netdb.RequestDestination (ident);
return false;
}
}
return true;
}
void TunnelPool::CreateInboundTunnel ()
{
auto outboundTunnel = GetNextOutboundTunnel ();
if (!outboundTunnel)
outboundTunnel = tunnels.GetNextOutboundTunnel ();
LogPrint ("Creating destination inbound tunnel...");
std::vector<std::shared_ptr<const i2p::data::RouterInfo> > hops;
if (SelectPeers (hops, true))
{
std::reverse (hops.begin (), hops.end ());
auto tunnel = tunnels.CreateTunnel<InboundTunnel> (std::make_shared<TunnelConfig> (hops), outboundTunnel);
tunnel->SetTunnelPool (shared_from_this ());
}
else
LogPrint (eLogError, "Can't create inbound tunnel. No peers available");
}
void TunnelPool::RecreateInboundTunnel (std::shared_ptr<InboundTunnel> tunnel)
{
auto outboundTunnel = GetNextOutboundTunnel ();
if (!outboundTunnel)
outboundTunnel = tunnels.GetNextOutboundTunnel ();
LogPrint ("Re-creating destination inbound tunnel...");
auto newTunnel = tunnels.CreateTunnel<InboundTunnel> (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel);
newTunnel->SetTunnelPool (shared_from_this());
}
void TunnelPool::CreateOutboundTunnel ()
{
auto inboundTunnel = GetNextInboundTunnel ();
if (!inboundTunnel)
inboundTunnel = tunnels.GetNextInboundTunnel ();
if (inboundTunnel)
{
LogPrint ("Creating destination outbound tunnel...");
std::vector<std::shared_ptr<const i2p::data::RouterInfo> > hops;
if (SelectPeers (hops, false))
{
auto tunnel = tunnels.CreateTunnel<OutboundTunnel> (
std::make_shared<TunnelConfig> (hops, inboundTunnel->GetTunnelConfig ()));
tunnel->SetTunnelPool (shared_from_this ());
}
else
LogPrint (eLogError, "Can't create outbound tunnel. No peers available");
}
else
LogPrint (eLogError, "Can't create outbound tunnel. No inbound tunnels found");
}
void TunnelPool::RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel)
{
auto inboundTunnel = GetNextInboundTunnel ();
if (!inboundTunnel)
inboundTunnel = tunnels.GetNextInboundTunnel ();
if (inboundTunnel)
{
LogPrint ("Re-creating destination outbound tunnel...");
auto newTunnel = tunnels.CreateTunnel<OutboundTunnel> (
tunnel->GetTunnelConfig ()->Clone (inboundTunnel->GetTunnelConfig ()));
newTunnel->SetTunnelPool (shared_from_this ());
}
else
LogPrint ("Can't re-create outbound tunnel. No inbound tunnels found");
}
void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel)
{
LogPrint (eLogInfo, "Creating paired inbound tunnel...");
auto tunnel = tunnels.CreateTunnel<InboundTunnel> (outboundTunnel->GetTunnelConfig ()->Invert (), outboundTunnel);
tunnel->SetTunnelPool (shared_from_this ());
}
}
}

91
core/tunnel/TunnelPool.h Normal file
View file

@ -0,0 +1,91 @@
#ifndef TUNNEL_POOL__
#define TUNNEL_POOL__
#include <inttypes.h>
#include <set>
#include <vector>
#include <utility>
#include <mutex>
#include <memory>
#include "Identity.h"
#include "LeaseSet.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "TunnelBase.h"
#include "RouterContext.h"
#include "Garlic.h"
namespace i2p
{
namespace tunnel
{
class Tunnel;
class InboundTunnel;
class OutboundTunnel;
class TunnelPool: public std::enable_shared_from_this<TunnelPool> // per local destination
{
public:
TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels);
~TunnelPool ();
i2p::garlic::GarlicDestination * GetLocalDestination () const { return m_LocalDestination; };
void SetLocalDestination (i2p::garlic::GarlicDestination * destination) { m_LocalDestination = destination; };
void SetExplicitPeers (std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers);
void CreateTunnels ();
void TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel);
void TunnelExpired (std::shared_ptr<InboundTunnel> expiredTunnel);
void TunnelCreated (std::shared_ptr<OutboundTunnel> createdTunnel);
void TunnelExpired (std::shared_ptr<OutboundTunnel> expiredTunnel);
void RecreateInboundTunnel (std::shared_ptr<InboundTunnel> tunnel);
void RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel);
std::vector<std::shared_ptr<InboundTunnel> > GetInboundTunnels (int num) const;
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded = nullptr) const;
std::shared_ptr<InboundTunnel> GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded = nullptr) const;
std::shared_ptr<OutboundTunnel> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const;
void TestTunnels ();
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg);
bool IsActive () const { return m_IsActive; };
void SetActive (bool isActive) { m_IsActive = isActive; };
void DetachTunnels ();
private:
void CreateInboundTunnel ();
void CreateOutboundTunnel ();
void CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel);
template<class TTunnels>
typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const;
std::shared_ptr<const i2p::data::RouterInfo> SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop) const;
bool SelectPeers (std::vector<std::shared_ptr<const i2p::data::RouterInfo> >& hops, bool isInbound);
bool SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::RouterInfo> >& hops, bool isInbound);
private:
i2p::garlic::GarlicDestination * m_LocalDestination;
int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels;
std::shared_ptr<std::vector<i2p::data::IdentHash> > m_ExplicitPeers;
mutable std::mutex m_InboundTunnelsMutex;
std::set<std::shared_ptr<InboundTunnel>, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first
mutable std::mutex m_OutboundTunnelsMutex;
std::set<std::shared_ptr<OutboundTunnel>, TunnelCreationTimeCmp> m_OutboundTunnels;
std::map<uint32_t, std::pair<std::shared_ptr<OutboundTunnel>, std::shared_ptr<InboundTunnel> > > m_Tests;
bool m_IsActive;
public:
// for HTTP only
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
};
}
}
#endif

83
core/util/I2PEndian.cpp Normal file
View file

@ -0,0 +1,83 @@
#include "I2PEndian.h"
// http://habrahabr.ru/post/121811/
// http://codepad.org/2ycmkz2y
#include "LittleBigEndian.h"
#ifdef NEEDS_LOCAL_ENDIAN
uint16_t htobe16(uint16_t int16)
{
BigEndian<uint16_t> u16(int16);
return u16.raw_value;
}
uint32_t htobe32(uint32_t int32)
{
BigEndian<uint32_t> u32(int32);
return u32.raw_value;
}
uint64_t htobe64(uint64_t int64)
{
BigEndian<uint64_t> u64(int64);
return u64.raw_value;
}
uint16_t be16toh(uint16_t big16)
{
LittleEndian<uint16_t> u16(big16);
return u16.raw_value;
}
uint32_t be32toh(uint32_t big32)
{
LittleEndian<uint32_t> u32(big32);
return u32.raw_value;
}
uint64_t be64toh(uint64_t big64)
{
LittleEndian<uint64_t> u64(big64);
return u64.raw_value;
}
#endif
/* it can be used in Windows 8
#include <Winsock2.h>
uint16_t htobe16(uint16_t int16)
{
return htons(int16);
}
uint32_t htobe32(uint32_t int32)
{
return htonl(int32);
}
uint64_t htobe64(uint64_t int64)
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
//return htonll(int64);
return 0;
}
uint16_t be16toh(uint16_t big16)
{
return ntohs(big16);
}
uint32_t be32toh(uint32_t big32)
{
return ntohl(big32);
}
uint64_t be64toh(uint64_t big64)
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
//return ntohll(big64);
return 0;
}
*/

119
core/util/I2PEndian.h Normal file
View file

@ -0,0 +1,119 @@
#ifndef I2PENDIAN_H__
#define I2PENDIAN_H__
#include <inttypes.h>
#include <string.h>
#if defined(__linux__) || defined(__FreeBSD_kernel__)
#include <endian.h>
#elif __FreeBSD__
#include <sys/endian.h>
#elif defined(__APPLE__) && defined(__MACH__)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define htole16(x) OSSwapHostToLittleInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define le16toh(x) OSSwapLittleToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#define le32toh(x) OSSwapLittleToHostInt32(x)
#define htobe64(x) OSSwapHostToBigInt64(x)
#define htole64(x) OSSwapHostToLittleInt64(x)
#define be64toh(x) OSSwapBigToHostInt64(x)
#define le64toh(x) OSSwapLittleToHostInt64(x)
#else
#define NEEDS_LOCAL_ENDIAN
#include <cstdint>
uint16_t htobe16(uint16_t int16);
uint32_t htobe32(uint32_t int32);
uint64_t htobe64(uint64_t int64);
uint16_t be16toh(uint16_t big16);
uint32_t be32toh(uint32_t big32);
uint64_t be64toh(uint64_t big64);
// assume LittleEndine
#define htole16
#define htole32
#define htole64
#define le16toh
#define le32toh
#define le64toh
#endif
inline uint16_t buf16toh(const void *buf)
{
uint16_t b16;
memcpy(&b16, buf, sizeof(uint16_t));
return b16;
}
inline uint32_t buf32toh(const void *buf)
{
uint32_t b32;
memcpy(&b32, buf, sizeof(uint32_t));
return b32;
}
inline uint64_t buf64toh(const void *buf)
{
uint64_t b64;
memcpy(&b64, buf, sizeof(uint64_t));
return b64;
}
inline uint16_t bufbe16toh(const void *buf)
{
return be16toh(buf16toh(buf));
}
inline uint32_t bufbe32toh(const void *buf)
{
return be32toh(buf32toh(buf));
}
inline uint64_t bufbe64toh(const void *buf)
{
return be64toh(buf64toh(buf));
}
inline void htobuf16(void *buf, uint16_t b16)
{
memcpy(buf, &b16, sizeof(uint16_t));
}
inline void htobuf32(void *buf, uint32_t b32)
{
memcpy(buf, &b32, sizeof(uint32_t));
}
inline void htobuf64(void *buf, uint64_t b64)
{
memcpy(buf, &b64, sizeof(uint64_t));
}
inline void htobe16buf(void *buf, uint16_t big16)
{
htobuf16(buf, htobe16(big16));
}
inline void htobe32buf(void *buf, uint32_t big32)
{
htobuf32(buf, htobe32(big32));
}
inline void htobe64buf(void *buf, uint64_t big64)
{
htobuf64(buf, htobe64(big64));
}
#endif // I2PENDIAN_H__

242
core/util/LittleBigEndian.h Normal file
View file

@ -0,0 +1,242 @@
// LittleBigEndian.h fixed for 64-bits added union
//
#ifndef LITTLEBIGENDIAN_H
#define LITTLEBIGENDIAN_H
// Determine Little-Endian or Big-Endian
#define CURRENT_BYTE_ORDER (*(int *)"\x01\x02\x03\x04")
#define LITTLE_ENDIAN_BYTE_ORDER 0x04030201
#define BIG_ENDIAN_BYTE_ORDER 0x01020304
#define PDP_ENDIAN_BYTE_ORDER 0x02010403
#define IS_LITTLE_ENDIAN (CURRENT_BYTE_ORDER == LITTLE_ENDIAN_BYTE_ORDER)
#define IS_BIG_ENDIAN (CURRENT_BYTE_ORDER == BIG_ENDIAN_BYTE_ORDER)
#define IS_PDP_ENDIAN (CURRENT_BYTE_ORDER == PDP_ENDIAN_BYTE_ORDER)
// Forward declaration
template<typename T>
struct LittleEndian;
template<typename T>
struct BigEndian;
// Little-Endian template
#pragma pack(push,1)
template<typename T>
struct LittleEndian
{
union
{
unsigned char bytes[sizeof(T)];
T raw_value;
};
LittleEndian(T t = T())
{
operator =(t);
}
LittleEndian(const LittleEndian<T> & t)
{
raw_value = t.raw_value;
}
LittleEndian(const BigEndian<T> & t)
{
for (unsigned i = 0; i < sizeof(T); i++)
bytes[i] = t.bytes[sizeof(T)-1-i];
}
operator const T() const
{
T t = T();
for (unsigned i = 0; i < sizeof(T); i++)
t |= T(bytes[i]) << (i << 3);
return t;
}
const T operator = (const T t)
{
for (unsigned i = 0; i < sizeof(T); i++)
bytes[sizeof(T)-1 - i] = static_cast<unsigned char>(t >> (i << 3));
return t;
}
// operators
const T operator += (const T t)
{
return (*this = *this + t);
}
const T operator -= (const T t)
{
return (*this = *this - t);
}
const T operator *= (const T t)
{
return (*this = *this * t);
}
const T operator /= (const T t)
{
return (*this = *this / t);
}
const T operator %= (const T t)
{
return (*this = *this % t);
}
LittleEndian<T> operator ++ (int)
{
LittleEndian<T> tmp(*this);
operator ++ ();
return tmp;
}
LittleEndian<T> & operator ++ ()
{
for (unsigned i = 0; i < sizeof(T); i++)
{
++bytes[i];
if (bytes[i] != 0)
break;
}
return (*this);
}
LittleEndian<T> operator -- (int)
{
LittleEndian<T> tmp(*this);
operator -- ();
return tmp;
}
LittleEndian<T> & operator -- ()
{
for (unsigned i = 0; i < sizeof(T); i++)
{
--bytes[i];
if (bytes[i] != (T)(-1))
break;
}
return (*this);
}
};
#pragma pack(pop)
// Big-Endian template
#pragma pack(push,1)
template<typename T>
struct BigEndian
{
union
{
unsigned char bytes[sizeof(T)];
T raw_value;
};
BigEndian(T t = T())
{
operator =(t);
}
BigEndian(const BigEndian<T> & t)
{
raw_value = t.raw_value;
}
BigEndian(const LittleEndian<T> & t)
{
for (unsigned i = 0; i < sizeof(T); i++)
bytes[i] = t.bytes[sizeof(T)-1-i];
}
operator const T() const
{
T t = T();
for (unsigned i = 0; i < sizeof(T); i++)
t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3);
return t;
}
const T operator = (const T t)
{
for (unsigned i = 0; i < sizeof(T); i++)
bytes[sizeof(T) - 1 - i] = t >> (i << 3);
return t;
}
// operators
const T operator += (const T t)
{
return (*this = *this + t);
}
const T operator -= (const T t)
{
return (*this = *this - t);
}
const T operator *= (const T t)
{
return (*this = *this * t);
}
const T operator /= (const T t)
{
return (*this = *this / t);
}
const T operator %= (const T t)
{
return (*this = *this % t);
}
BigEndian<T> operator ++ (int)
{
BigEndian<T> tmp(*this);
operator ++ ();
return tmp;
}
BigEndian<T> & operator ++ ()
{
for (unsigned i = 0; i < sizeof(T); i++)
{
++bytes[sizeof(T) - 1 - i];
if (bytes[sizeof(T) - 1 - i] != 0)
break;
}
return (*this);
}
BigEndian<T> operator -- (int)
{
BigEndian<T> tmp(*this);
operator -- ();
return tmp;
}
BigEndian<T> & operator -- ()
{
for (unsigned i = 0; i < sizeof(T); i++)
{
--bytes[sizeof(T) - 1 - i];
if (bytes[sizeof(T) - 1 - i] != (T)(-1))
break;
}
return (*this);
}
};
#pragma pack(pop)
#endif // LITTLEBIGENDIAN_H

62
core/util/Log.cpp Normal file
View file

@ -0,0 +1,62 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Log.h"
Log * g_Log = nullptr;
static const char * g_LogLevelStr[eNumLogLevels] =
{
"error", // eLogError
"warn", // eLogWarning
"info", // eLogInfo
"debug" // eLogDebug
};
void LogMsg::Process()
{
auto& output = (log && log->GetLogStream ()) ? *log->GetLogStream () : std::cerr;
if (log)
output << log->GetTimestamp ();
else
output << boost::posix_time::second_clock::local_time().time_of_day ();
output << "/" << g_LogLevelStr[level] << " - ";
output << s.str();
}
const std::string& Log::GetTimestamp ()
{
#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__)
auto ts = std::chrono::monotonic_clock::now ();
#else
auto ts = std::chrono::steady_clock::now ();
#endif
if (ts > m_LastTimestampUpdate + std::chrono::milliseconds (500)) // 0.5 second
{
m_LastTimestampUpdate = ts;
m_Timestamp = boost::posix_time::to_simple_string (boost::posix_time::second_clock::local_time().time_of_day ());
}
return m_Timestamp;
}
void Log::Flush ()
{
if (m_LogStream)
m_LogStream->flush();
}
void Log::SetLogFile (const std::string& fullFilePath)
{
auto logFile = new std::ofstream (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
if (logFile->is_open ())
{
SetLogStream (logFile);
LogPrint("Logging to file ", fullFilePath, " enabled.");
}
else
delete logFile;
}
void Log::SetLogStream (std::ostream * logStream)
{
if (m_LogStream) delete m_LogStream;
m_LogStream = logStream;
}

129
core/util/Log.h Normal file
View file

@ -0,0 +1,129 @@
#ifndef LOG_H__
#define LOG_H__
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <functional>
#include <chrono>
#include "Queue.h"
enum LogLevel
{
eLogError = 0,
eLogWarning,
eLogInfo,
eLogDebug,
eNumLogLevels
};
class Log;
struct LogMsg
{
std::stringstream s;
Log * log;
LogLevel level;
LogMsg (Log * l = nullptr, LogLevel lv = eLogInfo): log (l), level (lv) {};
void Process();
};
class Log: public i2p::util::MsgQueue<LogMsg>
{
public:
Log (): m_LogStream (nullptr) { SetOnEmpty (std::bind (&Log::Flush, this)); };
~Log () { delete m_LogStream; };
void SetLogFile (const std::string& fullFilePath);
void SetLogStream (std::ostream * logStream);
std::ostream * GetLogStream () const { return m_LogStream; };
const std::string& GetTimestamp ();
private:
void Flush ();
private:
std::ostream * m_LogStream;
std::string m_Timestamp;
#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__)
std::chrono::monotonic_clock::time_point m_LastTimestampUpdate;
#else
std::chrono::steady_clock::time_point m_LastTimestampUpdate;
#endif
};
extern Log * g_Log;
inline void StartLog (const std::string& fullFilePath)
{
if (!g_Log)
{
auto log = new Log ();
if (fullFilePath.length () > 0)
log->SetLogFile (fullFilePath);
g_Log = log;
}
}
inline void StartLog (std::ostream * s)
{
if (!g_Log)
{
auto log = new Log ();
if (s)
log->SetLogStream (s);
g_Log = log;
}
}
inline void StopLog ()
{
if (g_Log)
{
auto log = g_Log;
g_Log = nullptr;
log->Stop ();
delete log;
}
}
template<typename TValue>
void LogPrint (std::stringstream& s, TValue arg)
{
s << arg;
}
template<typename TValue, typename... TArgs>
void LogPrint (std::stringstream& s, TValue arg, TArgs... args)
{
LogPrint (s, arg);
LogPrint (s, args...);
}
template<typename... TArgs>
void LogPrint (LogLevel level, TArgs... args)
{
LogMsg * msg = new LogMsg (g_Log, level);
LogPrint (msg->s, args...);
msg->s << std::endl;
if (g_Log)
g_Log->Put (msg);
else
{
msg->Process ();
delete msg;
}
}
template<typename... TArgs>
void LogPrint (TArgs... args)
{
LogPrint (eLogInfo, args...);
}
#endif

169
core/util/Queue.h Normal file
View file

@ -0,0 +1,169 @@
#ifndef QUEUE_H__
#define QUEUE_H__
#include <queue>
#include <vector>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <functional>
namespace i2p
{
namespace util
{
template<typename Element>
class Queue
{
public:
void Put (Element e)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_Queue.push (e);
m_NonEmpty.notify_one ();
}
void Put (const std::vector<Element>& vec)
{
if (!vec.empty ())
{
std::unique_lock<std::mutex> l(m_QueueMutex);
for (auto it: vec)
m_Queue.push (it);
m_NonEmpty.notify_one ();
}
}
Element GetNext ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
auto el = GetNonThreadSafe ();
if (!el)
{
m_NonEmpty.wait (l);
el = GetNonThreadSafe ();
}
return el;
}
Element GetNextWithTimeout (int usec)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
auto el = GetNonThreadSafe ();
if (!el)
{
m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec));
el = GetNonThreadSafe ();
}
return el;
}
void Wait ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_NonEmpty.wait (l);
}
bool Wait (int sec, int usec)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_NonEmpty.wait_for (l, std::chrono::seconds (sec) + std::chrono::milliseconds (usec)) != std::cv_status::timeout;
}
bool IsEmpty ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_Queue.empty ();
}
int GetSize ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_Queue.size ();
}
void WakeUp () { m_NonEmpty.notify_all (); };
Element Get ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return GetNonThreadSafe ();
}
Element Peek ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return GetNonThreadSafe (true);
}
private:
Element GetNonThreadSafe (bool peek = false)
{
if (!m_Queue.empty ())
{
auto el = m_Queue.front ();
if (!peek)
m_Queue.pop ();
return el;
}
return nullptr;
}
private:
std::queue<Element> m_Queue;
std::mutex m_QueueMutex;
std::condition_variable m_NonEmpty;
};
template<class Msg>
class MsgQueue: public Queue<Msg *>
{
public:
typedef std::function<void()> OnEmpty;
MsgQueue (): m_IsRunning (true), m_Thread (std::bind (&MsgQueue<Msg>::Run, this)) {};
~MsgQueue () { Stop (); };
void Stop()
{
if (m_IsRunning)
{
m_IsRunning = false;
Queue<Msg *>::WakeUp ();
m_Thread.join();
}
}
void SetOnEmpty (OnEmpty const & e) { m_OnEmpty = e; };
private:
void Run ()
{
while (m_IsRunning)
{
while (auto msg = Queue<Msg *>::Get ())
{
msg->Process ();
delete msg;
}
if (m_OnEmpty != nullptr)
m_OnEmpty ();
if (m_IsRunning)
Queue<Msg *>::Wait ();
}
}
private:
volatile bool m_IsRunning;
OnEmpty m_OnEmpty;
std::thread m_Thread;
};
}
}
#endif

32
core/util/Timestamp.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef TIMESTAMP_H__
#define TIMESTAMP_H__
#include <inttypes.h>
#include <chrono>
namespace i2p
{
namespace util
{
inline uint64_t GetMillisecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
inline uint32_t GetHoursSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::hours>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
inline uint64_t GetSecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
}
}
#endif

239
core/util/base64.cpp Normal file
View file

@ -0,0 +1,239 @@
#include <stdlib.h>
#include "base64.h"
namespace i2p
{
namespace util
{
static void iT64Build(void);
/*
*
* BASE64 Substitution Table
* -------------------------
*
* Direct Substitution Table
*/
static char T64[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '-', '~'
};
const char * GetBase64SubstitutionTable ()
{
return T64;
}
/*
* Reverse Substitution Table (built in run time)
*/
static char iT64[256];
static int isFirstTime = 1;
/*
* Padding
*/
static char P64 = '=';
size_t ByteStreamToBase64(const uint8_t* InBuffer, size_t InCount, char* OutBuffer, size_t len)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
ps = (unsigned char *)InBuffer;
n = InCount/3;
m = InCount%3;
if (!m)
outCount = 4*n;
else
outCount = 4*(n+1);
if (outCount > len) return 0;
pd = (unsigned char *)OutBuffer;
for ( i = 0; i<n; i++ ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x30;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2;
acc_2 = *ps++;
acc_1 |= acc_2>>6; /* base64 digit #3 */
*pd++ = T64[acc_1];
acc_2 &= 0x3f; /* base64 digit #4 */
*pd++ = T64[acc_2];
}
if ( m == 1 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
*pd++ = T64[acc_2];
*pd++ = P64;
*pd++ = P64;
}
else if ( m == 2 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2; /* base64 digit #3 */
*pd++ = T64[acc_1];
*pd++ = P64;
}
return outCount;
}
size_t Base64ToByteStream(const char * InBuffer, size_t InCount, uint8_t* OutBuffer, size_t len)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
if (isFirstTime) iT64Build();
n = InCount/4;
m = InCount%4;
if(InCount && !m)
outCount = 3*n;
else {
outCount = 0;
return 0;
}
ps = (unsigned char *)(InBuffer + InCount - 1);
while ( *ps-- == P64 ) outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len) return 0;
pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount;
for ( i = 0; i < n; i++ ){
acc_1 = iT64[*ps++];
acc_2 = iT64[*ps++];
acc_1 <<= 2;
acc_1 |= acc_2>>4;
*pd++ = acc_1;
if (pd >= endOfOutBuffer) break;
acc_2 <<= 4;
acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2;
*pd++ = acc_2;
if (pd >= endOfOutBuffer) break;
acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6;
*pd++ = acc_2;
}
return outCount;
}
/*
*
* iT64
* ----
* Reverse table builder. P64 character is replaced with 0
*
*
*/
static void iT64Build()
{
int i;
isFirstTime = 0;
for ( i=0; i<256; i++ ) iT64[i] = -1;
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
iT64[(int)P64] = 0;
}
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
{
int tmp = 0, bits = 0;
size_t ret = 0;
for (size_t i = 0; i < len; i++)
{
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z')
ch = ch - 'a'; // a = 0
else
return 0; // unexpected character
tmp |= ch;
bits += 5;
if (bits >= 8)
{
if (ret >= outLen) return ret;
outBuf[ret] = tmp >> (bits - 8);
bits -= 8;
ret++;
}
tmp <<= 5;
}
return ret;
}
size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
{
if(!len)
return 0; // No data given
size_t ret = 0, pos = 1;
int bits = 8, tmp = inBuf[0];
while (ret < outLen && (bits > 0 || pos < len))
{
if (bits < 5)
{
if (pos < len)
{
tmp <<= 8;
tmp |= inBuf[pos] & 0xFF;
pos++;
bits += 8;
}
else // last byte
{
tmp <<= (5 - bits);
bits = 5;
}
}
bits -= 5;
int ind = (tmp >> bits) & 0x1F;
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
}
return ret;
}
}
}

63
core/util/base64.h Normal file
View file

@ -0,0 +1,63 @@
#ifndef BASE64_H
#define BASE64_H
#include <inttypes.h>
#include <string.h>
namespace i2p
{
namespace util
{
/*
* Base64 encodes an array of bytes.
* @return the number of characters written to the output buffer
* @param InBuffer array of input bytes to be encoded
* @param InCount length of the input array
* @param OutBuffer array to store output characters
* @param len length of the output buffer
* @note zero is returned when the output buffer is too small
*/
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
/**
* Decodes base 64 encoded data to an array of bytes.
* @return the number of bytes written to the output buffer
* @param InBuffer array of input characters to be decoded
* @param InCount length of the input array
* @param OutBuffer array to store output bytes
* @param len length of the output buffer
* @todo Do not return a negative value on failure, size_t could be unsigned.
* @note zero is returned when the output buffer is too small
*/
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
const char * GetBase64SubstitutionTable ();
/**
* Decodes base 32 encoded data to an array of bytes.
* @return the number of bytes written to the output buffer
* @param inBuf array of input characters to be decoded
* @param len length of the input buffer
* @param outBuf array to store output bytes
* @param outLen length of the output array
* @note zero is returned when the output buffer is too small
*/
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
/**
* Base 32 encodes an array of bytes.
* @return the number of bytes written to the output buffer
* @param inBuf array of input bytes to be encoded
* @param len length of the input buffer
* @param outBuf array to store output characters
* @param outLen length of the output array
* @note zero is returned when the output buffer is too small
*/
size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen);
}
}
#endif

676
core/util/util.cpp Normal file
View file

@ -0,0 +1,676 @@
#include <cstdlib>
#include <string>
#include <algorithm>
#include <cctype>
#include <functional>
#include <fstream>
#include <set>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options/detail/config_file.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/algorithm/string.hpp>
#include "util.h"
#include "Log.h"
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
#include <sys/types.h>
#include <ifaddrs.h>
#elif defined(WIN32)
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <shlobj.h>
#pragma comment(lib, "IPHLPAPI.lib")
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
int inet_pton(int af, const char *src, void *dst)
{ /* This function was written by Petar Korponai?. See
http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */
struct sockaddr_storage ss;
int size = sizeof (ss);
char src_copy[INET6_ADDRSTRLEN + 1];
ZeroMemory (&ss, sizeof (ss));
strncpy_s (src_copy, src, INET6_ADDRSTRLEN + 1);
src_copy[INET6_ADDRSTRLEN] = 0;
if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0)
{
switch (af)
{
case AF_INET:
*(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
return 1;
case AF_INET6:
*(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
return 1;
}
}
return 0;
}
#endif
namespace i2p {
namespace util {
namespace config {
std::map<std::string, std::string> mapArgs;
std::map<std::string, std::vector<std::string> > mapMultiArgs;
void OptionParser(int argc, const char* const argv[])
{
mapArgs.clear();
mapMultiArgs.clear();
for(int i = 1; i < argc; ++i) {
std::string strKey (argv[i]);
std::string strValue;
size_t has_data = strKey.find('=');
if(has_data != std::string::npos) {
strValue = strKey.substr(has_data+1);
strKey = strKey.substr(0, has_data);
}
#ifdef WIN32
boost::to_lower(strKey);
if(boost::algorithm::starts_with(strKey, "/"))
strKey = "-" + strKey.substr(1);
#endif
if(strKey[0] != '-')
break;
mapArgs[strKey] = strValue;
mapMultiArgs[strKey].push_back(strValue);
}
for(auto& entry : mapArgs) {
std::string name = entry.first;
// interpret --foo as -foo (as long as both are not set)
if (name.find("--") == 0) {
std::string singleDash(name.begin()+1, name.end());
if (mapArgs.count(singleDash) == 0)
mapArgs[singleDash] = entry.second;
name = singleDash;
}
}
}
const char* GetCharArg(const std::string& strArg, const std::string& nDefault)
{
if(mapArgs.count(strArg))
return mapArgs[strArg].c_str();
return nDefault.c_str();
}
std::string GetArg(const std::string& strArg, const std::string& strDefault)
{
if(mapArgs.count(strArg))
return mapArgs[strArg];
return strDefault;
}
int GetArg(const std::string& strArg, int nDefault)
{
if(mapArgs.count(strArg))
return stoi(mapArgs[strArg]);
return nDefault;
}
}
namespace filesystem
{
std::string appName("i2pd");
void SetAppName(const std::string& name)
{
appName = name;
}
std::string GetAppName()
{
return appName;
}
const boost::filesystem::path& GetDataDir()
{
static boost::filesystem::path path;
// TODO: datadir parameter is useless because GetDataDir is called before OptionParser
// and mapArgs is not initialized yet
/*if (i2p::util::config::mapArgs.count("-datadir"))
path = boost::filesystem::system_complete(i2p::util::config::mapArgs["-datadir"]);
else */
path = GetDefaultDataDir();
if(!boost::filesystem::exists(path)) {
// Create data directory
if(!boost::filesystem::create_directory(path)) {
LogPrint("Failed to create data directory!");
path = "";
return path;
}
}
if(!boost::filesystem::is_directory(path))
path = GetDefaultDataDir();
return path;
}
std::string GetFullPath(const std::string& filename)
{
std::string fullPath = GetDataDir().string();
#ifndef _WIN32
fullPath.append("/");
#else
fullPath.append("\\");
#endif
fullPath.append(filename);
return fullPath;
}
boost::filesystem::path GetConfigFile()
{
boost::filesystem::path pathConfigFile(i2p::util::config::GetArg("-conf", "i2p.conf"));
if(!pathConfigFile.is_complete())
pathConfigFile = GetDataDir() / pathConfigFile;
return pathConfigFile;
}
boost::filesystem::path GetTunnelsConfigFile()
{
boost::filesystem::path pathTunnelsConfigFile(i2p::util::config::GetArg("-tunnelscfg", "tunnels.cfg"));
if(!pathTunnelsConfigFile.is_complete())
pathTunnelsConfigFile = GetDataDir() / pathTunnelsConfigFile;
return pathTunnelsConfigFile;
}
boost::filesystem::path GetDefaultDataDir()
{
// Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd
// Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd
// Mac: ~/Library/Application Support/i2pd
// Unix: ~/.i2pd or /var/lib/i2pd is system=1
#ifdef WIN32
// Windows
char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
return boost::filesystem::path(std::string(localAppData) + "\\" + appName);
#else
if(i2p::util::config::GetArg("-service", 0)) // use system folder
return boost::filesystem::path(std::string ("/var/lib/") + appName);
boost::filesystem::path pathRet;
char* pszHome = getenv("HOME");
if(pszHome == NULL || strlen(pszHome) == 0)
pathRet = boost::filesystem::path("/");
else
pathRet = boost::filesystem::path(pszHome);
#ifdef MAC_OSX
// Mac
pathRet /= "Library/Application Support";
boost::filesystem::create_directory(pathRet);
return pathRet / appName;
#else
// Unix
return pathRet / (std::string (".") + appName);
#endif
#endif
}
void ReadConfigFile(std::map<std::string, std::string>& mapSettingsRet,
std::map<std::string, std::vector<std::string> >& mapMultiSettingsRet)
{
boost::filesystem::ifstream streamConfig(GetConfigFile());
if(!streamConfig.good())
return; // No i2pd.conf file is OK
std::set<std::string> setOptions;
setOptions.insert("*");
for(boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end;
it != end; ++it) {
// Don't overwrite existing settings so command line settings override i2pd.conf
std::string strKey = std::string("-") + it->string_key;
if(mapSettingsRet.count(strKey) == 0) {
mapSettingsRet[strKey] = it->value[0];
}
mapMultiSettingsRet[strKey].push_back(it->value[0]);
}
}
boost::filesystem::path GetCertificatesDir()
{
return GetDataDir () / "certificates";
}
}
namespace http
{
std::string httpRequest(const std::string& address)
{
try {
i2p::util::http::url u(address);
boost::asio::ip::tcp::iostream site;
// please don't uncomment following line because it's not compatible with boost 1.46
// 1.46 is default boost for Ubuntu 12.04 LTS
//site.expires_from_now (boost::posix_time::seconds(30));
if(u.port_ == 80)
site.connect(u.host_, "http");
else {
std::stringstream ss; ss << u.port_;
site.connect(u.host_, ss.str());
}
if(site) {
// User-Agent is needed to get the server list routerInfo files.
site << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n"
<< "Connection: close\r\n\r\n";
// read response and extract content
return GetHttpContent(site);
} else {
LogPrint("Can't connect to ", address);
return "";
}
} catch(const std::exception& ex) {
LogPrint("Failed to download ", address, " : ", ex.what());
return "";
}
}
std::string GetHttpContent (std::istream& response)
{
std::string version, statusMessage;
response >> version; // HTTP version
int status;
response >> status; // status
std::getline (response, statusMessage);
if(status == 200) { // OK
bool isChunked = false;
std::string header;
while(!response.eof() && header != "\r") {
std::getline(response, header);
auto colon = header.find (':');
if(colon != std::string::npos) {
std::string field = header.substr (0, colon);
if(field == i2p::util::http::TRANSFER_ENCODING)
isChunked = (header.find("chunked", colon + 1) != std::string::npos);
}
}
std::stringstream ss;
if(isChunked)
MergeChunkedResponse(response, ss);
else
ss << response.rdbuf();
return ss.str();
} else {
LogPrint("HTTP response ", status);
return "";
}
}
void MergeChunkedResponse(std::istream& response, std::ostream& merged)
{
while(!response.eof()) {
std::string hexLen;
int len;
std::getline(response, hexLen);
std::istringstream iss(hexLen);
iss >> std::hex >> len;
if(!len)
break;
char* buf = new char[len];
response.read(buf, len);
merged.write(buf, len);
delete[] buf;
std::getline(response, hexLen); // read \r\n after chunk
}
}
int httpRequestViaI2pProxy(const std::string& address, std::string &content)
{
content = "";
try {
boost::asio::ip::tcp::iostream site;
// please don't uncomment following line because it's not compatible with boost 1.46
// 1.46 is default boost for Ubuntu 12.04 LTS
//site.expires_from_now (boost::posix_time::seconds(30));
{
std::stringstream ss; ss << i2p::util::config::GetArg("-httpproxyport", 4446);
site.connect("127.0.0.1", ss.str());
}
if(site) {
i2p::util::http::url u(address);
std::stringstream ss;
ss << "GET " << address << " HTTP/1.0" << std::endl;
ss << "Host: " << u.host_ << std::endl;
ss << "Accept: */*" << std::endl;
ss << "User - Agent: Wget / 1.11.4" << std::endl;
ss << "Connection: close" << std::endl;
ss << std::endl;
site << ss.str();
// read response
std::string version, statusMessage;
site >> version; // HTTP version
int status;
site >> status; // status
std::getline(site, statusMessage);
if(status == 200) { // OK
std::string header;
while(std::getline(site, header) && header != "\r"){}
std::stringstream ss;
ss << site.rdbuf();
content = ss.str();
return status;
} else {
LogPrint("HTTP response ", status);
return status;
}
} else {
LogPrint("Can't connect to proxy");
return 408;
}
} catch (std::exception& ex) {
LogPrint("Failed to download ", address, " : ", ex.what());
return 408;
}
}
url::url(const std::string& url_s)
{
portstr_ = "80";
port_ = 80;
user_ = "";
pass_ = "";
parse(url_s);
}
void url::parse(const std::string& url_s)
{
const std::string prot_end("://");
std::string::const_iterator prot_i = search(
url_s.begin(), url_s.end(), prot_end.begin(), prot_end.end()
);
protocol_.reserve(distance(url_s.begin(), prot_i));
// Make portocol lowercase
transform(
url_s.begin(), prot_i, back_inserter(protocol_), std::ptr_fun<int, int>(std::tolower)
);
if(prot_i == url_s.end())
return;
advance(prot_i, prot_end.length());
std::string::const_iterator path_i = find(prot_i, url_s.end(), '/');
host_.reserve(distance(prot_i, path_i));
// Make host lowerase
transform(prot_i, path_i, back_inserter(host_), std::ptr_fun<int, int>(std::tolower));
// parse user/password
auto user_pass_i = find(host_.begin(), host_.end(), '@');
if(user_pass_i != host_.end()) {
std::string user_pass = std::string(host_.begin(), user_pass_i);
auto pass_i = find(user_pass.begin(), user_pass.end(), ':');
if (pass_i != user_pass.end()) {
user_ = std::string(user_pass.begin(), pass_i);
pass_ = std::string(pass_i + 1, user_pass.end());
} else
user_ = user_pass;
host_.assign(user_pass_i + 1, host_.end());
}
// parse port
auto port_i = find(host_.begin(), host_.end(), ':');
if(port_i != host_.end()) {
portstr_ = std::string(port_i + 1, host_.end());
host_.assign(host_.begin(), port_i);
try {
port_ = boost::lexical_cast<decltype(port_)>(portstr_);
} catch(const std::exception& e) {
port_ = 80;
}
}
std::string::const_iterator query_i = find(path_i, url_s.end(), '?');
path_.assign(path_i, query_i);
if( query_i != url_s.end() )
++query_i;
query_.assign(query_i, url_s.end());
}
std::string urlDecode(const std::string& data)
{
std::string res(data);
for(size_t pos = res.find('%'); pos != std::string::npos; pos = res.find('%', pos + 1)) {
const char c = strtol(res.substr(pos + 1, 2).c_str(), NULL, 16);
res.replace(pos, 3, 1, c);
}
return res;
}
}
namespace net {
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback)
{
ifaddrs* ifaddr, *ifa = nullptr;
if(getifaddrs(&ifaddr) == -1) {
LogPrint(eLogError, "Can't excute getifaddrs");
return fallback;
}
int family = 0;
// look for interface matching local address
for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
if(!ifa->ifa_addr)
continue;
family = ifa->ifa_addr->sa_family;
if(family == AF_INET && localAddress.is_v4()) {
sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr;
if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4))
break; // address matches
} else if(family == AF_INET6 && localAddress.is_v6()) {
sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr;
if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16))
break; // address matches
}
}
int mtu = fallback;
if(ifa && family) { // interface found?
int fd = socket(family, SOCK_DGRAM, 0);
if(fd > 0) {
ifreq ifr;
strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query
if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0)
mtu = ifr.ifr_mtu; // MTU
else
LogPrint (eLogError, "Failed to run ioctl");
close(fd);
} else
LogPrint(eLogError, "Failed to create datagram socket");
} else {
LogPrint(
eLogWarning, "Interface for local address",
localAddress.to_string(), " not found"
);
}
freeifaddrs(ifaddr);
return mtu;
}
#elif defined(WIN32)
int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback)
{
ULONG outBufLen = 0;
PIP_ADAPTER_ADDRESSES pAddresses = nullptr;
PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr;
if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen)
== ERROR_BUFFER_OVERFLOW) {
FREE(pAddresses);
pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen);
}
DWORD dwRetVal = GetAdaptersAddresses(
AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen
);
if(dwRetVal != NO_ERROR) {
LogPrint(
eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"
);
FREE(pAddresses);
return fallback;
}
pCurrAddresses = pAddresses;
while(pCurrAddresses) {
PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress;
pUnicast = pCurrAddresses->FirstUnicastAddress;
if(pUnicast == nullptr) {
LogPrint(
eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported"
);
}
for(int i = 0; pUnicast != nullptr; ++i) {
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr;
if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) {
result = pAddresses->Mtu;
FREE(pAddresses);
return result;
}
pUnicast = pUnicast->Next;
}
pCurrAddresses = pCurrAddresses->Next;
}
LogPrint(eLogError, "GetMTU() error: no usable unicast ipv4 addresses found");
FREE(pAddresses);
return fallback;
}
int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback)
{
ULONG outBufLen = 0;
PIP_ADAPTER_ADDRESSES pAddresses = nullptr;
PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr;
if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen)
== ERROR_BUFFER_OVERFLOW) {
FREE(pAddresses);
pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen);
}
DWORD dwRetVal = GetAdaptersAddresses(
AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen
);
if(dwRetVal != NO_ERROR) {
LogPrint(
eLogError,
"GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"
);
FREE(pAddresses);
return fallback;
}
bool found_address = false;
pCurrAddresses = pAddresses;
while(pCurrAddresses) {
PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress;
pUnicast = pCurrAddresses->FirstUnicastAddress;
if(pUnicast == nullptr) {
LogPrint(
eLogError,
"GetMTU() has failed: not a unicast ipv6 address, this is not supported"
);
}
for(int i = 0; pUnicast != nullptr; ++i) {
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr;
for (int j = 0; j != 8; ++j) {
if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) {
break;
} else {
found_address = true;
}
} if (found_address) {
result = pAddresses->Mtu;
FREE(pAddresses);
pAddresses = nullptr;
return result;
}
pUnicast = pUnicast->Next;
}
pCurrAddresses = pCurrAddresses->Next;
}
LogPrint(eLogError, "GetMTU() error: no usable unicast ipv6 addresses found");
FREE(pAddresses);
return fallback;
}
int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback)
{
#ifdef UNICODE
string localAddress_temporary = localAddress.to_string();
wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end());
#else
std::string localAddressUniversal = localAddress.to_string();
#endif
if(localAddress.is_v4()) {
sockaddr_in inputAddress;
inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr));
return GetMTUWindowsIpv4(inputAddress, fallback);
} else if(localAddress.is_v6()) {
sockaddr_in6 inputAddress;
inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr));
return GetMTUWindowsIpv6(inputAddress, fallback);
} else {
LogPrint(eLogError, "GetMTU() has failed: address family is not supported");
return fallback;
}
}
#endif // WIN32
int GetMTU(const boost::asio::ip::address& localAddress)
{
const int fallback = 576; // fallback MTU
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
return GetMTUUnix(localAddress, fallback);
#elif defined(WIN32)
return GetMTUWindows(localAddress, fallback);
#endif
return fallback;
}
}
} // util
} // i2p

162
core/util/util.h Normal file
View file

@ -0,0 +1,162 @@
#ifndef UTIL_H
#define UTIL_H
#include <map>
#include <string>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#define PAIRTYPE(t1, t2) std::pair<t1, t2>
namespace i2p
{
namespace util
{
namespace config
{
extern std::map<std::string, std::string> mapArgs;
extern std::map<std::string, std::vector<std::string> > mapMultiArgs;
/**
* Parses command line arguments, i.e. stores them in config::mapArgs.
*/
void OptionParser(int argc, const char* const argv[]);
/**
* @return a command line argument from config::mapArgs as an int
* @param nDefault the default value to be returned
*/
int GetArg(const std::string& strArg, int nDefault);
/**
* @return a command line argument from config::mapArgs as a std::string
* @param strDefault the default value to be returned
*/
std::string GetArg(const std::string& strArg, const std::string& strDefault);
/**
* @return a command line argument from config::mapArgs as a C-style string
* @param nDefault the default value to be returned
*/
const char* GetCharArg(const std::string& strArg, const std::string& nDefault);
}
namespace filesystem
{
/**
* Change the application name.
*/
void SetAppName(const std::string& name);
/**
* @return the application name.
*/
std::string GetAppName();
/**
* @return the path of the i2pd directory
*/
const boost::filesystem::path& GetDataDir();
/**
* @return the full path of a file within the i2pd directory
*/
std::string GetFullPath(const std::string& filename);
/**
* @return the path of the configuration file
*/
boost::filesystem::path GetConfigFile();
/**
* @return the path of the tunnels configuration file
*/
boost::filesystem::path GetTunnelsConfigFile();
/**
* @return the default directory for i2pd data
*/
boost::filesystem::path GetDefaultDataDir();
/**
* Read a configuration file and store its contents in the given maps.
*/
void ReadConfigFile(std::map<std::string, std::string>& mapSettingsRet,
std::map<std::string, std::vector<std::string> >& mapMultiSettingsRet);
/**
* @return the path of the certificates directory
*/
boost::filesystem::path GetCertificatesDir();
}
namespace http
{
const char ETAG[] = "ETag";
const char IF_NONE_MATCH[] = "If-None-Match";
const char IF_MODIFIED_SINCE[] = "If-Modified-Since";
const char LAST_MODIFIED[] = "Last-Modified";
const char TRANSFER_ENCODING[] = "Transfer-Encoding";
/**
* Perform an HTTP request.
* @return the result of the request, or an empty string if it fails
*/
std::string httpRequest(const std::string& address);
/**
* @return the content of the given HTTP stream without headers
*/
std::string GetHttpContent(std::istream& response);
/**
* Merge chunks of a HTTP response into the gien std:ostream object.
*/
void MergeChunkedResponse(std::istream& response, std::ostream& merged);
/**
* Send an HTTP request through the i2p proxy.
* @return the HTTP status code
*/
int httpRequestViaI2pProxy(const std::string& address, std::string &content);
/**
* @return the decoded url
*/
std::string urlDecode(const std::string& data);
/**
* Provides functionality for parsing URLs.
*/
struct url {
/**
* Parse a url given as a string.
*/
url(const std::string& url_s);
private:
void parse(const std::string& url_s);
public:
std::string protocol_, host_, path_, query_;
std::string portstr_;
unsigned int port_;
std::string user_;
std::string pass_;
};
}
namespace net
{
/**
* @return the maximum transmission unit, or 576 on failure
*/
int GetMTU(const boost::asio::ip::address& localAddress);
}
}
}
#endif

8
core/version.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef _VERSION_H_
#define _VERSION_H_
#define CODENAME "Purple"
#define VERSION "0.10.0"
#define I2P_VERSION "0.9.20"
#endif