Browse Source

Merge pull request #438 from PurpleI2P/openssl

transort resolvers
pull/464/head
orignal 8 years ago
parent
commit
31ff1372ae
  1. 81
      AddressBook.cpp
  2. 26
      AddressBook.h
  3. 1
      Config.cpp
  4. 8
      Daemon.cpp
  5. 12
      HTTPServer.cpp
  6. 14
      NTCPSession.cpp
  7. 43
      NetDb.cpp
  8. 3
      NetDb.h
  9. 48
      RouterContext.cpp
  10. 3
      RouterContext.h
  11. 130
      RouterInfo.cpp
  12. 15
      RouterInfo.h
  13. 60
      Transports.cpp
  14. 14
      UPnP.cpp
  15. 5
      UPnP.h
  16. 11
      Win32/Win32App.cpp
  17. 2
      docs/build_requirements.md
  18. 10
      docs/family.md
  19. 8
      util.cpp
  20. 2
      version.h

81
AddressBook.cpp

@ -330,8 +330,7 @@ namespace client
LoadHostsFromStream (f); LoadHostsFromStream (f);
m_IsLoaded = true; m_IsLoaded = true;
} }
// load local LoadLocal ();
m_Storage->LoadLocal (m_Addresses);
} }
void AddressBook::LoadHostsFromStream (std::istream& f) void AddressBook::LoadHostsFromStream (std::istream& f)
@ -395,6 +394,40 @@ namespace client
LogPrint (eLogError, "Addressbook: subscriptions already loaded"); LogPrint (eLogError, "Addressbook: subscriptions already loaded");
} }
void AddressBook::LoadLocal ()
{
std::map<std::string, i2p::data::IdentHash> localAddresses;
m_Storage->LoadLocal (localAddresses);
for (auto it: localAddresses)
{
auto dot = it.first.find ('.');
if (dot != std::string::npos)
{
auto domain = it.first.substr (dot + 1);
auto it1 = m_Addresses.find (domain); // find domain in our addressbook
if (it1 != m_Addresses.end ())
{
auto dest = context.FindLocalDestination (it1->second);
if (dest)
{
// address is ours
std::shared_ptr<AddressResolver> resolver;
auto it2 = m_Resolvers.find (it1->second);
if (it2 != m_Resolvers.end ())
resolver = it2->second; // resolver exists
else
{
// create new resolver
resolver = std::make_shared<AddressResolver>(dest);
m_Resolvers.insert (std::make_pair(it1->second, resolver));
}
resolver->AddAddress (it.first, it.second);
}
}
}
}
}
bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified)
{ {
if (m_Storage) if (m_Storage)
@ -653,6 +686,50 @@ namespace client
m_Book.LoadHostsFromStream (s); m_Book.LoadHostsFromStream (s);
return true; return true;
} }
AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination):
m_LocalDestination (destination)
{
if (m_LocalDestination)
{
auto datagram = m_LocalDestination->GetDatagramDestination ();
if (!datagram)
datagram = m_LocalDestination->CreateDatagramDestination ();
datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
ADDRESS_RESOLVER_DATAGRAM_PORT);
}
}
void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (len < 9 || len < buf[8] + 9U)
{
LogPrint (eLogError, "Address request is too short ", len);
return;
}
// read requested address
uint8_t l = buf[8];
char address[255];
memcpy (address, buf + 9, l);
address[l] = 0;
// send response
uint8_t response[40];
memset (response, 0, 4); // reserved
memcpy (response + 4, buf + 4, 4); // nonce
auto it = m_LocalAddresses.find (address); // address lookup
if (it != m_LocalAddresses.end ())
memcpy (response + 8, it->second, 32); // ident
else
memset (response + 8, 0, 32); // not found
m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 40, from.GetIdentHash (), toPort, fromPort);
}
void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident)
{
m_LocalAddresses[name] = ident;
}
} }
} }

26
AddressBook.h

@ -12,6 +12,7 @@
#include "Base.h" #include "Base.h"
#include "Identity.h" #include "Identity.h"
#include "Log.h" #include "Log.h"
#include "Destination.h"
namespace i2p namespace i2p
{ {
@ -23,7 +24,7 @@ namespace client
const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)
const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes
const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second
inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); }
class AddressBookStorage // interface for storage class AddressBookStorage // interface for storage
@ -45,6 +46,7 @@ namespace client
}; };
class AddressBookSubscription; class AddressBookSubscription;
class AddressResolver;
class AddressBook class AddressBook
{ {
public: public:
@ -74,13 +76,15 @@ namespace client
void LoadHosts (); void LoadHosts ();
void LoadSubscriptions (); void LoadSubscriptions ();
void LoadLocal ();
void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode);
private: private:
std::mutex m_AddressBookMutex; std::mutex m_AddressBookMutex;
std::map<std::string, i2p::data::IdentHash> m_Addresses, m_LocalAddresses; std::map<std::string, i2p::data::IdentHash> m_Addresses;
std::map<i2p::data::IdentHash, std::shared_ptr<AddressResolver> > m_Resolvers; // local destination->resolver
AddressBookStorage * m_Storage; AddressBookStorage * m_Storage;
volatile bool m_IsLoaded, m_IsDownloading; volatile bool m_IsLoaded, m_IsDownloading;
std::vector<AddressBookSubscription *> m_Subscriptions; std::vector<AddressBookSubscription *> m_Subscriptions;
@ -106,6 +110,24 @@ namespace client
std::string m_Link, m_Etag, m_LastModified; std::string m_Link, m_Etag, m_LastModified;
// m_Etag must be surrounded by "" // m_Etag must be surrounded by ""
}; };
const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53;
class AddressResolver
{
public:
AddressResolver (std::shared_ptr<ClientDestination> destination);
void AddAddress (const std::string& name, const i2p::data::IdentHash& ident);
private:
void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
std::map<std::string, i2p::data::IdentHash> m_LocalAddresses;
};
} }
} }

1
Config.cpp

@ -117,6 +117,7 @@ namespace config {
("datadir", value<std::string>()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("datadir", value<std::string>()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
("host", value<std::string>()->default_value("0.0.0.0"), "External IP") ("host", value<std::string>()->default_value("0.0.0.0"), "External IP")
("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)") ("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)")
("ipv4", value<bool>()->zero_tokens()->default_value(true), "Enable communication through ipv4")
("ipv6", value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("ipv6", value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6")
("daemon", value<bool>()->zero_tokens()->default_value(false), "Router will go to background after start") ("daemon", value<bool>()->zero_tokens()->default_value(false), "Router will go to background after start")
("service", value<bool>()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("service", value<bool>()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'")

8
Daemon.cpp

@ -72,7 +72,7 @@ namespace i2p
if (config == "") if (config == "")
{ {
config = i2p::fs::DataDirPath("i2p.conf"); config = i2p::fs::DataDirPath("i2p.conf");
// use i2p.cong only if exists // use i2p.conf only if exists
if (!i2p::fs::Exists (config)) config = ""; /* reset */ if (!i2p::fs::Exists (config)) config = ""; /* reset */
} }
@ -104,9 +104,11 @@ namespace i2p
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));
} }
bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ipv6; i2p::config::GetOption("ipv6", ipv6);
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
bool transit; i2p::config::GetOption("notransit", transit); bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV6 (ipv6);
i2p::context.SetSupportsV4 (ipv4);
i2p::context.SetAcceptsTunnels (!transit); i2p::context.SetAcceptsTunnels (!transit);
bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill);

12
HTTPServer.cpp

@ -427,18 +427,18 @@ namespace util
s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)<br>\r\n"; s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)<br>\r\n";
s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n"; s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n";
s << "<b>Our external address:</b>" << "<br>\r\n" ; s << "<b>Our external address:</b>" << "<br>\r\n" ;
for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) for (auto address : i2p::context.GetRouterInfo().GetAddresses())
{ {
switch (address.transportStyle) switch (address->transportStyle)
{ {
case i2p::data::RouterInfo::eTransportNTCP: case i2p::data::RouterInfo::eTransportNTCP:
if (address.host.is_v6 ()) if (address->host.is_v6 ())
s << "NTCP6&nbsp;&nbsp;"; s << "NTCP6&nbsp;&nbsp;";
else else
s << "NTCP&nbsp;&nbsp;"; s << "NTCP&nbsp;&nbsp;";
break; break;
case i2p::data::RouterInfo::eTransportSSU: case i2p::data::RouterInfo::eTransportSSU:
if (address.host.is_v6 ()) if (address->host.is_v6 ())
s << "SSU6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"; s << "SSU6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
else else
s << "SSU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"; s << "SSU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
@ -446,7 +446,7 @@ namespace util
default: default:
s << "Unknown&nbsp;&nbsp;"; s << "Unknown&nbsp;&nbsp;";
} }
s << address.host.to_string() << ":" << address.port << "<br>\r\n"; s << address->host.to_string() << ":" << address->port << "<br>\r\n";
} }
s << "<br>\r\n<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " "; s << "<br>\r\n<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " ";
s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " "; s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " ";
@ -534,7 +534,7 @@ namespace util
auto dest = i2p::client::context.FindLocalDestination (ident); auto dest = i2p::client::context.FindLocalDestination (ident);
if (dest) if (dest)
{ {
s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"1\" wrap=\"off\">"; s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n"; s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>\r\n"; s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>\r\n";
auto pool = dest->GetTunnelPool (); auto pool = dest->GetTunnelPool ();

14
NTCPSession.cpp

@ -759,15 +759,15 @@ namespace transport
m_IsRunning = true; m_IsRunning = true;
m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); m_Thread = new std::thread (std::bind (&NTCPServer::Run, this));
// create acceptors // create acceptors
auto addresses = context.GetRouterInfo ().GetAddresses (); auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (auto& address : addresses) for (auto address: addresses)
{ {
if (address.transportStyle == i2p::data::RouterInfo::eTransportNTCP && address.host.is_v4 ()) if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ())
{ {
m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port));
LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address.port); LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port);
auto conn = std::make_shared<NTCPSession>(*this); auto conn = std::make_shared<NTCPSession>(*this);
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
conn, std::placeholders::_1)); conn, std::placeholders::_1));
@ -777,10 +777,10 @@ namespace transport
m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);
m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); 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->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port));
m_NTCPV6Acceptor->listen (); m_NTCPV6Acceptor->listen ();
LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address.port); LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port);
auto conn = std::make_shared<NTCPSession> (*this); auto conn = std::make_shared<NTCPSession> (*this);
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
this, conn, std::placeholders::_1)); this, conn, std::placeholders::_1));

43
NetDb.cpp

@ -118,6 +118,7 @@ namespace data
{ {
SaveUpdated (); SaveUpdated ();
ManageLeaseSets (); ManageLeaseSets ();
ManageLookupResponses ();
} }
lastSave = ts; lastSave = ts;
} }
@ -671,13 +672,31 @@ namespace data
if (!replyMsg) if (!replyMsg)
{ {
LogPrint (eLogWarning, "NetDb: Requested ", key, " not found. ", numExcluded, " excluded"); LogPrint (eLogWarning, "NetDb: Requested ", key, " not found. ", numExcluded, " excluded");
std::set<IdentHash> excludedRouters; // find or cleate response
for (int i = 0; i < numExcluded; i++) std::vector<IdentHash> closestFloodfills;
{ bool found = false;
excludedRouters.insert (excluded); if (!numExcluded)
excluded += 32; {
auto it = m_LookupResponses.find (ident);
if (it != m_LookupResponses.end ())
{
closestFloodfills = it->second.first;
found = true;
}
}
if (!found)
{
std::set<IdentHash> excludedRouters;
for (int i = 0; i < numExcluded; i++)
{
excludedRouters.insert (excluded);
excluded += 32;
}
closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true);
if (!numExcluded) // save if no excluded
m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ());
} }
replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters, true)); replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills);
} }
} }
@ -972,5 +991,17 @@ namespace data
it++; it++;
} }
} }
void NetDb::ManageLookupResponses ()
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_LookupResponses.begin (); it != m_LookupResponses.end ();)
{
if (ts > it->second.second + 180) // 3 minutes
it = m_LookupResponses.erase (it);
else
it++;
}
}
} }
} }

3
NetDb.h

@ -84,6 +84,7 @@ namespace data
void Publish (); void Publish ();
void ManageLeaseSets (); void ManageLeaseSets ();
void ManageRequests (); void ManageRequests ();
void ManageLookupResponses ();
template<typename Filter> template<typename Filter>
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const; std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
@ -108,6 +109,8 @@ namespace data
friend class NetDbRequests; friend class NetDbRequests;
NetDbRequests m_Requests; NetDbRequests m_Requests;
std::map<IdentHash, std::pair<std::vector<IdentHash>, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp)
}; };
extern NetDb netdb; extern NetDb netdb;

48
RouterContext.cpp

@ -92,11 +92,11 @@ namespace i2p
void RouterContext::UpdatePort (int port) void RouterContext::UpdatePort (int port)
{ {
bool updated = false; bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ()) for (auto address : m_RouterInfo.GetAddresses ())
{ {
if (address.port != port) if (address->port != port)
{ {
address.port = port; address->port = port;
updated = true; updated = true;
} }
} }
@ -107,11 +107,11 @@ namespace i2p
void RouterContext::UpdateAddress (const boost::asio::ip::address& host) void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
{ {
bool updated = false; bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ()) for (auto address : m_RouterInfo.GetAddresses ())
{ {
if (address.host != host && address.IsCompatible (host)) if (address->host != host && address->IsCompatible (host))
{ {
address.host = host; address->host = host;
updated = true; updated = true;
} }
} }
@ -206,15 +206,15 @@ namespace i2p
auto& addresses = m_RouterInfo.GetAddresses (); auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++) for (size_t i = 0; i < addresses.size (); i++)
{ {
if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP) if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{ {
addresses.erase (addresses.begin () + i); addresses.erase (addresses.begin () + i);
break; break;
} }
} }
// delete previous introducers // delete previous introducers
for (auto& addr : addresses) for (auto addr : addresses)
addr.introducers.clear (); addr->introducers.clear ();
// update // update
UpdateRouterInfo (); UpdateRouterInfo ();
@ -235,16 +235,16 @@ namespace i2p
auto& addresses = m_RouterInfo.GetAddresses (); auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++) for (size_t i = 0; i < addresses.size (); i++)
{ {
if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU) if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU)
{ {
// insert NTCP address with host/port from SSU // insert NTCP address with host/port from SSU
m_RouterInfo.AddNTCPAddress (addresses[i].host.to_string ().c_str (), addresses[i].port); m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port);
break; break;
} }
} }
// delete previous introducers // delete previous introducers
for (auto& addr : addresses) for (auto addr : addresses)
addr.introducers.clear (); addr->introducers.clear ();
// update // update
UpdateRouterInfo (); UpdateRouterInfo ();
@ -257,26 +257,36 @@ namespace i2p
else else
m_RouterInfo.DisableV6 (); m_RouterInfo.DisableV6 ();
UpdateRouterInfo (); UpdateRouterInfo ();
} }
void RouterContext::SetSupportsV4 (bool supportsV4)
{
if (supportsV4)
m_RouterInfo.EnableV4 ();
else
m_RouterInfo.DisableV4 ();
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host) void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
{ {
bool updated = false, found = false; bool updated = false, found = false;
int port = 0; int port = 0;
auto& addresses = m_RouterInfo.GetAddresses (); auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses) for (auto addr: addresses)
{ {
if (addr.host.is_v6 () && addr.transportStyle == i2p::data::RouterInfo::eTransportNTCP) if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{ {
if (addr.host != host) if (addr->host != host)
{ {
addr.host = host; addr->host = host;
updated = true; updated = true;
} }
found = true; found = true;
} }
else else
port = addr.port; port = addr->port;
} }
if (!found) if (!found)
{ {

3
RouterContext.h

@ -64,7 +64,10 @@ namespace i2p
bool AcceptsTunnels () const { return m_AcceptsTunnels; }; bool AcceptsTunnels () const { return m_AcceptsTunnels; };
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
void SetSupportsV6 (bool supportsV6); void SetSupportsV6 (bool supportsV6);
void SetSupportsV4 (bool supportsV4);
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
void UpdateStats (); void UpdateStats ();

130
RouterInfo.cpp

@ -232,7 +232,7 @@ namespace data
} }
if (isValidAddress) if (isValidAddress)
{ {
m_Addresses.push_back(address); m_Addresses.push_back(std::make_shared<Address>(address));
m_SupportedTransports |= supportedTransports; m_SupportedTransports |= supportedTransports;
} }
} }
@ -359,8 +359,9 @@ namespace data
// addresses // addresses
uint8_t numAddresses = m_Addresses.size (); uint8_t numAddresses = m_Addresses.size ();
s.write ((char *)&numAddresses, sizeof (numAddresses)); s.write ((char *)&numAddresses, sizeof (numAddresses));
for (auto& address : m_Addresses) for (auto addr : m_Addresses)
{ {
Address& address = *addr;
s.write ((char *)&address.cost, sizeof (address.cost)); s.write ((char *)&address.cost, sizeof (address.cost));
s.write ((char *)&address.date, sizeof (address.date)); s.write ((char *)&address.date, sizeof (address.date));
std::stringstream properties; std::stringstream properties;
@ -543,46 +544,46 @@ namespace data
void RouterInfo::AddNTCPAddress (const char * host, int port) void RouterInfo::AddNTCPAddress (const char * host, int port)
{ {
Address addr; auto addr = std::make_shared<Address>();
addr.host = boost::asio::ip::address::from_string (host); addr->host = boost::asio::ip::address::from_string (host);
addr.port = port; addr->port = port;
addr.transportStyle = eTransportNTCP; addr->transportStyle = eTransportNTCP;
addr.cost = 2; addr->cost = 2;
addr.date = 0; addr->date = 0;
addr.mtu = 0; addr->mtu = 0;
for (auto it: m_Addresses) // don't insert same address twice for (auto it: m_Addresses) // don't insert same address twice
if (it == addr) return; if (*it == *addr) return;
m_Addresses.push_back(addr); m_Addresses.push_back(addr);
m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eNTCPV4; m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4;
} }
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
{ {
Address addr; auto addr = std::make_shared<Address>();
addr.host = boost::asio::ip::address::from_string (host); addr->host = boost::asio::ip::address::from_string (host);
addr.port = port; addr->port = port;
addr.transportStyle = eTransportSSU; addr->transportStyle = eTransportSSU;
addr.cost = 10; // NTCP should have priority over SSU addr->cost = 10; // NTCP should have priority over SSU
addr.date = 0; addr->date = 0;
addr.mtu = mtu; addr->mtu = mtu;
memcpy (addr.key, key, 32); memcpy (addr->key, key, 32);
for (auto it: m_Addresses) // don't insert same address twice for (auto it: m_Addresses) // don't insert same address twice
if (it == addr) return; if (*it == *addr) return;
m_Addresses.push_back(addr); m_Addresses.push_back(addr);
m_SupportedTransports |= addr.host.is_v6 () ? eSSUV6 : eSSUV4; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
m_Caps |= eSSUTesting; m_Caps |= eSSUTesting;
m_Caps |= eSSUIntroducer; m_Caps |= eSSUIntroducer;
} }
bool RouterInfo::AddIntroducer (const Introducer& introducer) bool RouterInfo::AddIntroducer (const Introducer& introducer)
{ {
for (auto& addr : m_Addresses) for (auto addr : m_Addresses)
{ {
if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ()) if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{ {
for (auto intro: addr.introducers) for (auto intro: addr->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented if (intro.iTag == introducer.iTag) return false; // already presented
addr.introducers.push_back (introducer); addr->introducers.push_back (introducer);
return true; return true;
} }
} }
@ -591,14 +592,14 @@ namespace data
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{ {
for (auto& addr : m_Addresses) for (auto addr: m_Addresses)
{ {
if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ()) if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{ {
for (std::vector<Introducer>::iterator it = addr.introducers.begin (); it != addr.introducers.end (); it++) 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) if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
{ {
addr.introducers.erase (it); addr->introducers.erase (it);
return true; return true;
} }
} }
@ -650,12 +651,24 @@ namespace data
return m_SupportedTransports & (eNTCPV6 | eSSUV6); return m_SupportedTransports & (eNTCPV6 | eSSUV6);
} }
bool RouterInfo::IsV4 () const
{
return m_SupportedTransports & (eNTCPV4 | eSSUV4);
}
void RouterInfo::EnableV6 () void RouterInfo::EnableV6 ()
{ {
if (!IsV6 ()) if (!IsV6 ())
m_SupportedTransports |= eNTCPV6 | eSSUV6; m_SupportedTransports |= eNTCPV6 | eSSUV6;
} }
void RouterInfo::EnableV4 ()
{
if (!IsV4 ())
m_SupportedTransports |= eNTCPV4 | eSSUV4;
}
void RouterInfo::DisableV6 () void RouterInfo::DisableV6 ()
{ {
if (IsV6 ()) if (IsV6 ())
@ -664,8 +677,8 @@ namespace data
m_SupportedTransports &= ~eNTCPV6; m_SupportedTransports &= ~eNTCPV6;
for (size_t i = 0; i < m_Addresses.size (); i++) for (size_t i = 0; i < m_Addresses.size (); i++)
{ {
if (m_Addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP && if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
m_Addresses[i].host.is_v6 ()) m_Addresses[i]->host.is_v6 ())
{ {
m_Addresses.erase (m_Addresses.begin () + i); m_Addresses.erase (m_Addresses.begin () + i);
break; break;
@ -676,8 +689,8 @@ namespace data
m_SupportedTransports &= ~eSSUV6; m_SupportedTransports &= ~eSSUV6;
for (size_t i = 0; i < m_Addresses.size (); i++) for (size_t i = 0; i < m_Addresses.size (); i++)
{ {
if (m_Addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU && if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
m_Addresses[i].host.is_v6 ()) m_Addresses[i]->host.is_v6 ())
{ {
m_Addresses.erase (m_Addresses.begin () + i); m_Addresses.erase (m_Addresses.begin () + i);
break; break;
@ -685,35 +698,66 @@ namespace data
} }
} }
} }
void RouterInfo::DisableV4 ()
{
if (IsV4 ())
{
// NTCP
m_SupportedTransports &= ~eNTCPV4;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
m_Addresses[i]->host.is_v4 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
// SSU
m_SupportedTransports &= ~eSSUV4;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
m_Addresses[i]->host.is_v4 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
}
}
bool RouterInfo::UsesIntroducer () const bool RouterInfo::UsesIntroducer () const
{ {
return m_Caps & Caps::eUnreachable; // non-reachable return m_Caps & Caps::eUnreachable; // non-reachable
} }
const RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) const std::shared_ptr<const RouterInfo::Address> RouterInfo::GetNTCPAddress (bool v4only) const
{ {
return GetAddress (eTransportNTCP, v4only); return GetAddress (eTransportNTCP, v4only);
} }
const RouterInfo::Address * RouterInfo::GetSSUAddress (bool v4only) const std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
{ {
return GetAddress (eTransportSSU, v4only); return GetAddress (eTransportSSU, v4only);
} }
const RouterInfo::Address * RouterInfo::GetSSUV6Address () const std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
{ {
return GetAddress (eTransportSSU, false, true); return GetAddress (eTransportSSU, false, true);
} }
const RouterInfo::Address * RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const
{ {
for (auto& address : m_Addresses) for (auto address : m_Addresses)
{ {
if (address.transportStyle == s) if (address->transportStyle == s)
{ {
if ((!v4only || address.host.is_v4 ()) && (!v6only || address.host.is_v6 ())) if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ()))
return &address; return address;
} }
} }
return nullptr; return nullptr;

15
RouterInfo.h

@ -116,10 +116,10 @@ namespace data
void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity); void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity);
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; }; uint64_t GetTimestamp () const { return m_Timestamp; };
std::vector<Address>& GetAddresses () { return m_Addresses; }; std::vector<std::shared_ptr<Address> >& GetAddresses () { return m_Addresses; };
const Address * GetNTCPAddress (bool v4only = true) const; std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const;
const Address * GetSSUAddress (bool v4only = true) const; std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
const Address * GetSSUV6Address () const; std::shared_ptr<const Address> GetSSUV6Address () const;
void AddNTCPAddress (const char * host, int port); void AddNTCPAddress (const char * host, int port);
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
@ -133,8 +133,11 @@ namespace data
bool IsNTCP (bool v4only = true) const; bool IsNTCP (bool v4only = true) const;
bool IsSSU (bool v4only = true) const; bool IsSSU (bool v4only = true) const;
bool IsV6 () const; bool IsV6 () const;
bool IsV4 () const;
void EnableV6 (); void EnableV6 ();
void DisableV6 (); void DisableV6 ();
void EnableV4 ();
void DisableV4 ();
bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; };
bool UsesIntroducer () const; bool UsesIntroducer () const;
bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; bool IsIntroducer () const { return m_Caps & eSSUIntroducer; };
@ -182,7 +185,7 @@ namespace data
size_t ReadString (char * str, std::istream& s); size_t ReadString (char * str, std::istream& s);
void WriteString (const std::string& str, std::ostream& s); void WriteString (const std::string& str, std::ostream& s);
void ExtractCaps (const char * value); void ExtractCaps (const char * value);
const Address * GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; std::shared_ptr<const Address> GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
void UpdateCapsProperty (); void UpdateCapsProperty ();
private: private:
@ -192,7 +195,7 @@ namespace data
uint8_t * m_Buffer; uint8_t * m_Buffer;
size_t m_BufferLen; size_t m_BufferLen;
uint64_t m_Timestamp; uint64_t m_Timestamp;
std::vector<Address> m_Addresses; std::vector<std::shared_ptr<Address> > m_Addresses;
std::map<std::string, std::string> m_Properties; std::map<std::string, std::string> m_Properties;
bool m_IsUpdated, m_IsUnreachable; bool m_IsUpdated, m_IsUnreachable;
uint8_t m_SupportedTransports, m_Caps; uint8_t m_SupportedTransports, m_Caps;

60
Transports.cpp

@ -112,8 +112,8 @@ namespace transport
m_IsRunning = true; m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Transports::Run, this)); m_Thread = new std::thread (std::bind (&Transports::Run, this));
// create acceptors // create acceptors
auto addresses = context.GetRouterInfo ().GetAddresses (); auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (auto& address : addresses) for (auto address : addresses)
{ {
if (!m_NTCPServer) if (!m_NTCPServer)
{ {
@ -121,12 +121,12 @@ namespace transport
m_NTCPServer->Start (); m_NTCPServer->Start ();
} }
if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ()) if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ())
{ {
if (!m_SSUServer) if (!m_SSUServer)
{ {
m_SSUServer = new SSUServer (address.port); m_SSUServer = new SSUServer (address->port);
LogPrint (eLogInfo, "Transports: Start listening UDP port ", address.port); LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port);
m_SSUServer->Start (); m_SSUServer->Start ();
DetectExternalIP (); DetectExternalIP ();
} }
@ -376,14 +376,24 @@ namespace transport
auto& peer = it1->second; auto& peer = it1->second;
if (!ecode && peer.router) if (!ecode && peer.router)
{ {
auto address = (*it).endpoint ().address (); while (it != boost::asio::ip::tcp::resolver::iterator())
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); {
auto addr = peer.router->GetNTCPAddress (); auto address = (*it).endpoint ().address ();
if (addr) LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
{ if (address.is_v4 () || context.SupportsV6 ())
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router); {
m_NTCPServer->Connect (address, addr->port, s); auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested
return; if (addr)
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
m_NTCPServer->Connect (address, addr->port, s);
return;
}
break;
}
else
LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported");
it++;
} }
} }
LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
@ -409,13 +419,23 @@ namespace transport
auto& peer = it1->second; auto& peer = it1->second;
if (!ecode && peer.router) if (!ecode && peer.router)
{ {
auto address = (*it).endpoint ().address (); while (it != boost::asio::ip::tcp::resolver::iterator())
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); {
auto addr = peer.router->GetSSUAddress (!context.SupportsV6 ());; auto address = (*it).endpoint ().address ();
if (addr) LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
{ if (address.is_v4 () || context.SupportsV6 ())
m_SSUServer->CreateSession (peer.router, address, addr->port); {
return; auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested
if (addr)
{
m_SSUServer->CreateSession (peer.router, address, addr->port);
return;
}
break;
}
else
LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported");
it++;
} }
} }
LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());

14
UPnP.cpp

@ -101,19 +101,19 @@ namespace transport
void UPnP::Run () void UPnP::Run ()
{ {
std::vector<data::RouterInfo::Address> a = context.GetRouterInfo().GetAddresses(); const std::vector<std::shared_ptr<i2p::data::RouterInfo::Address> > a = context.GetRouterInfo().GetAddresses();
for (auto& address : a) for (auto address : a)
{ {
if (!address.host.is_v6 ()) if (!address->host.is_v6 ())
{ {
Discover (); Discover ();
if (address.transportStyle == data::RouterInfo::eTransportSSU ) if (address->transportStyle == data::RouterInfo::eTransportSSU )
{ {
TryPortMapping (I2P_UPNP_UDP, address.port); TryPortMapping (I2P_UPNP_UDP, address->port);
} }
else if (address.transportStyle == data::RouterInfo::eTransportNTCP ) else if (address->transportStyle == data::RouterInfo::eTransportNTCP )
{ {
TryPortMapping (I2P_UPNP_TCP, address.port); TryPortMapping (I2P_UPNP_TCP, address->port);
} }
} }
} }

5
UPnP.h

@ -58,6 +58,5 @@ namespace transport
} }
} }
#endif #endif // USE_UPNP
#endif // __UPNP_H__
#endif

11
Win32/Win32App.cpp

@ -2,6 +2,7 @@
#include <windows.h> #include <windows.h>
#include <shellapi.h> #include <shellapi.h>
#include "../Config.h" #include "../Config.h"
#include "../version.h"
#include "resource.h" #include "resource.h"
#include "Win32App.h" #include "Win32App.h"
#include <stdio.h> #include <stdio.h>
@ -90,7 +91,9 @@ namespace win32
{ {
case ID_ABOUT: case ID_ABOUT:
{ {
MessageBox( hWnd, TEXT("i2pd"), TEXT("About"), MB_ICONINFORMATION | MB_OK ); std::stringstream text;
text << "Version: " << I2PD_VERSION << " " << CODENAME;
MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK );
return 0; return 0;
} }
case ID_EXIT: case ID_EXIT:
@ -171,12 +174,12 @@ namespace win32
auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT)); auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT));
auto mascotDC = CreateCompatibleDC (hDC); auto mascotDC = CreateCompatibleDC (hDC);
SelectObject (mascotDC, mascot); SelectObject (mascotDC, mascot);
BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY);
DeleteDC (mascotDC); DeleteDC (mascotDC);
DeleteObject (mascot); DeleteObject (mascot);
EndPaint (hWnd, &ps); EndPaint (hWnd, &ps);
break; break;
} }
} }
return DefWindowProc( hWnd, uMsg, wParam, lParam); return DefWindowProc( hWnd, uMsg, wParam, lParam);
} }
@ -205,7 +208,7 @@ namespace win32
wclx.lpszClassName = I2PD_WIN32_CLASSNAME; wclx.lpszClassName = I2PD_WIN32_CLASSNAME;
RegisterClassEx (&wclx); RegisterClassEx (&wclx);
// create new window // create new window
if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 533, 700, NULL, NULL, hInst, NULL)) if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 549, 738, NULL, NULL, hInst, NULL))
{ {
MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST);
return false; return false;

2
docs/build_requirements.md

@ -4,7 +4,7 @@ Build requirements
Linux/FreeBSD/OSX Linux/FreeBSD/OSX
----------------- -----------------
GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. GCC 4.8 or newer, Boost 1.49 or newer, openssl, zlib. Clang can be used instead of GCC.
Windows Windows
------- -------

10
docs/family.md

@ -8,11 +8,11 @@ New family
----------- -----------
You must create family self-signed certificate and key. You must create family self-signed certificate and key.
The only key type supposted is prime256v1. The only key type supposted is prime256v1.
Use the following list of commands: Use the following list of commands:
openssl ecparam -name prime256v1 -genkey -out <your family name>.key openssl ecparam -name prime256v1 -genkey -out <your family name>.key
openssl req -new -key <your family name>.key -out <your family name>.csr openssl req -new -key <your family name>.key -out <your family name>.csr
touch v3.ext touch v3.ext
openssl x509 -req -days 3650 -in <your family name>.csr -signkey <your family name>.key -out <your family name>.crt -extfile v3.ext openssl x509 -req -days 3650 -in <your family name>.csr -signkey <your family name>.key -out <your family name>.crt -extfile v3.ext
specify <your family name>.family.i2p.net for CN. specify <your family name>.family.i2p.net for CN.

8
util.cpp

@ -106,11 +106,15 @@ namespace http
while (!response.eof ()) while (!response.eof ())
{ {
std::string hexLen; std::string hexLen;
int len; size_t len;
std::getline (response, hexLen); std::getline (response, hexLen);
std::istringstream iss (hexLen); std::istringstream iss (hexLen);
iss >> std::hex >> len; iss >> std::hex >> len;
if (!len) break; if (!len || len > 10000000L) // 10M
{
LogPrint (eLogError, "Unexpected chunk length ", len);
break;
}
char * buf = new char[len]; char * buf = new char[len];
response.read (buf, len); response.read (buf, len);
merged.write (buf, len); merged.write (buf, len);

2
version.h

@ -16,7 +16,7 @@
#define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MAJOR 0
#define I2P_VERSION_MINOR 9 #define I2P_VERSION_MINOR 9
#define I2P_VERSION_MICRO 24 #define I2P_VERSION_MICRO 25
#define I2P_VERSION_PATCH 0 #define I2P_VERSION_PATCH 0
#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)

Loading…
Cancel
Save