Merge pull request #1656 from PurpleI2P/openssl

2.38.0
This commit is contained in:
orignal 2021-05-17 12:12:26 -04:00 committed by GitHub
commit 1b3c3fae89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1306 additions and 688 deletions

View File

@ -1,6 +1,37 @@
# for this file format description, # for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog # see https://github.com/olivierlacan/keep-a-changelog
## [2.38.0] - 2021-03-17
### Added
- Publish ipv6 introducers
- Bind ipv6 or yggdrasil NTCP2 acceptor to specified address
- Support .b32.i2p addresses and hostnames for SAM STREAM CREATE
- ipv6 peer tests
- Publish iexp param for introducers
- Show ipv6 network status on the webconsole
- EdDSA signing keys can also be blinded
- Show router version on the webconsole
### Changed
- Rekey of all routers but floodfills to ECIES
- Increased number of precalculated x25519 keys to 15
- Don't publish LeaseSet without inbound tunnels
- Reseed from compatible address(ipv4 or ipv6)
- Recongnize v4 and v6 SSU addresses without host
- Inbound tunnel gateway must be ipv4 compatible
- Don't select next introducers from existing sessions
- Set X bandwidth for floodfill by default
### Fixed
- Incoming ECIES-x25519 session doesn't send updated LeaseSet
- Unique local address for server tunnels
- Race condition for LeaseSet creation in I2CP
- Relay tag for ipv6 introducer
- Already expired introducers
- Find connected router for first peer in tunnel
- Failed outgoing ECIES-x25519 session's tagset stays forever
- Yggdrasil address disappears if router becomes unreachable through ipv6
- Ignore SSU address/introducers if port is not specified
- Check identity and signature length for SSU SessionConfirmed
## [2.37.0] - 2021-03-15 ## [2.37.0] - 2021-03-15
### Added ### Added
- Address registration line for reg.i2p and stats.i2p through the web console - Address registration line for reg.i2p and stats.i2p through the web console

View File

@ -142,25 +142,47 @@ namespace win32
s << bytes << " Bytes\n"; s << bytes << " Bytes\n";
} }
static void ShowNetworkStatus (std::stringstream& s, RouterStatus status)
{
switch (status)
{
case eRouterStatusOK: s << "OK"; break;
case eRouterStatusTesting: s << "Test"; break;
case eRouterStatusFirewalled: s << "FW"; break;
case eRouterStatusUnknown: s << "Unk"; break;
case eRouterStatusProxy: s << "Proxy"; break;
case eRouterStatusMesh: s << "Mesh"; break;
case eRouterStatusError:
{
s << "Err";
switch (i2p::context.GetError ())
{
case eRouterErrorClockSkew:
s << " - Clock skew";
break;
case eRouterErrorOffline:
s << " - Offline";
break;
case eRouterErrorSymmetricNAT:
s << " - Symmetric NAT";
break;
default: ;
}
break;
}
default: s << "Unk";
}
}
static void PrintMainWindowText (std::stringstream& s) static void PrintMainWindowText (std::stringstream& s)
{ {
s << "\n"; s << "\n";
s << "Status: "; s << "Status: ";
switch (i2p::context.GetStatus()) ShowNetworkStatus (s, i2p::context.GetStatus ());
if (i2p::context.SupportsV6 ())
{ {
case eRouterStatusOK: s << "OK"; break; s << " / ";
case eRouterStatusTesting: s << "Testing"; break; ShowNetworkStatus (s, i2p::context.GetStatusV6 ());
case eRouterStatusFirewalled: s << "Firewalled"; break;
case eRouterStatusError:
{
switch (i2p::context.GetError())
{
case eRouterErrorClockSkew: s << "Clock skew"; break;
default: s << "Error";
}
break;
}
default: s << "Unknown";
} }
s << "; "; s << "; ";
s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n";

View File

@ -1,4 +1,4 @@
version: 2.37.0.{build} version: 2.38.0.{build}
pull_requests: pull_requests:
do_not_increment_build_number: true do_not_increment_build_number: true
branches: branches:

View File

@ -20,6 +20,7 @@
## Logging configuration section ## Logging configuration section
## By default logs go to stdout with level 'info' and higher ## By default logs go to stdout with level 'info' and higher
## For Windows OS by default logs go to file with level 'warn' and higher
## ##
## Logs destination (valid values: stdout, file, syslog) ## Logs destination (valid values: stdout, file, syslog)
## * stdout - print log entries to stdout ## * stdout - print log entries to stdout
@ -34,14 +35,30 @@
## Write full CLF-formatted date and time to log (default: write only time) ## Write full CLF-formatted date and time to log (default: write only time)
# logclftime = true # logclftime = true
## Daemon mode. Router will go to background after start ## Daemon mode. Router will go to background after start. Ignored on Windows
# daemon = true # daemon = true
## Specify a family, router belongs to (default - none) ## Specify a family, router belongs to (default - none)
# family = # family =
## External IP address to listen for connections ## Network interface to bind to
## Updates address4/6 options if they are not set
# ifname =
## You can specify different interfaces for IPv4 and IPv6
# ifname4 =
# ifname6 =
## Local address to bind transport sockets to
## Overrides host option if:
## For ipv4: if ipv4 = true and nat = false
## For ipv6: if 'host' is not set or ipv4 = true
# address4 =
# address6 =
## External IPv4 or IPv6 address to listen for connections
## By default i2pd sets IP automatically ## By default i2pd sets IP automatically
## Sets published NTCP2v4/SSUv4 address to 'host' value if nat = true
## Sets published NTCP2v6/SSUv6 address to 'host' value if ipv4 = false
# host = 1.2.3.4 # host = 1.2.3.4
## Port to listen for connections ## Port to listen for connections
@ -54,23 +71,9 @@ ipv4 = true
## Enable communication through ipv6 ## Enable communication through ipv6
ipv6 = false ipv6 = false
## Network interface to bind to
# ifname =
## You can specify different interfaces for IPv4 and IPv6
# ifname4 =
# ifname6 =
## Enable NTCP transport (default = true)
# ntcp = true
## If you run i2pd behind a proxy server, you can only use NTCP transport with ntcpproxy option
## Should be http://address:port or socks://address:port
# ntcpproxy = http://127.0.0.1:8118
## Enable SSU transport (default = true) ## Enable SSU transport (default = true)
# ssu = true # ssu = true
## Should we assume we are behind NAT? (false only in MeshNet)
# nat = true
## Bandwidth configuration ## Bandwidth configuration
## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, ## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec,
## X - unlimited ## X - unlimited
@ -84,6 +87,7 @@ ipv6 = false
# notransit = true # notransit = true
## Router will be floodfill ## Router will be floodfill
## Note: that mode uses much more network connections and CPU!
# floodfill = true # floodfill = true
[http] [http]
@ -131,7 +135,7 @@ port = 4447
## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. ## socksproxy section also accepts I2CP parameters, like "inbound.length" etc.
[sam] [sam]
## Uncomment and set to 'true' to enable SAM Bridge ## Comment or set to 'false' to disable SAM Bridge
enabled = true enabled = true
## Address and port service will listen on ## Address and port service will listen on
# address = 127.0.0.1 # address = 127.0.0.1
@ -171,6 +175,13 @@ enabled = true
## Name i2pd appears in UPnP forwardings list (default = I2Pd) ## Name i2pd appears in UPnP forwardings list (default = I2Pd)
# name = I2Pd # name = I2Pd
[meshnets]
## Enable connectivity over the Yggdrasil network
# yggdrasil = false
## You can bind address from your Yggdrasil subnet 300::/64
## The address must first be added to the network interface
# yggaddress =
[reseed] [reseed]
## Options for bootstrapping into I2P network, aka reseeding ## Options for bootstrapping into I2P network, aka reseeding
## Enable or disable reseed data verification. ## Enable or disable reseed data verification.
@ -178,6 +189,8 @@ verify = true
## URLs to request reseed data from, separated by comma ## URLs to request reseed data from, separated by comma
## Default: "mainline" I2P Network reseeds ## Default: "mainline" I2P Network reseeds
# urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/
## Reseed URLs through the Yggdrasil, separated by comma
# yggurls = http://[324:9de3:fea4:f6ac::ace]:7070/
## Path to local reseed data file (.su3) for manual reseeding ## Path to local reseed data file (.su3) for manual reseeding
# file = /path/to/i2pseeds.su3 # file = /path/to/i2pseeds.su3
## or HTTPS URL to reseed from ## or HTTPS URL to reseed from
@ -195,7 +208,7 @@ verify = true
## Default: reg.i2p at "mainline" I2P Network ## Default: reg.i2p at "mainline" I2P Network
# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt
## Optional subscriptions URLs, separated by comma ## Optional subscriptions URLs, separated by comma
# subscriptions = http://inr.i2p/export/alive-hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt # subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
[limits] [limits]
## Maximum active transit sessions (default:2500) ## Maximum active transit sessions (default:2500)
@ -204,10 +217,6 @@ verify = true
# openfiles = 0 # openfiles = 0
## Maximum size of corefile in Kb (0 - use system limit) ## Maximum size of corefile in Kb (0 - use system limit)
# coresize = 0 # coresize = 0
## Threshold to start probabalistic backoff with ntcp sessions (0 - use system limit)
# ntcpsoft = 0
## Maximum number of ntcp sessions (0 - use system limit)
# ntcphard = 0
[trust] [trust]
## Enable explicit trust options. false by default ## Enable explicit trust options. false by default
@ -229,6 +238,8 @@ verify = true
[persist] [persist]
## Save peer profiles on disk (default: true) ## Save peer profiles on disk (default: true)
# profiles = true # profiles = true
## Save full addresses on disk (default: true)
# addressbook = true
[cpuext] [cpuext]
## Use CPU AES-NI instructions set when work with cryptography when available (default: true) ## Use CPU AES-NI instructions set when work with cryptography when available (default: true)

View File

@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7) %define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git Name: i2pd-git
Version: 2.37.0 Version: 2.38.0
Release: git%{git_hash}%{?dist} Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++ Summary: I2P router written in C++
Conflicts: i2pd Conflicts: i2pd
@ -137,6 +137,9 @@ getent passwd i2pd >/dev/null || \
%changelog %changelog
* Mon May 17 2021 orignal <i2porignal@yandex.ru> - 2.38.0
- update to 2.38.0
* Mon Mar 15 2021 orignal <i2porignal@yandex.ru> - 2.37.0 * Mon Mar 15 2021 orignal <i2porignal@yandex.ru> - 2.37.0
- update to 2.37.0 - update to 2.37.0

View File

@ -1,5 +1,5 @@
Name: i2pd Name: i2pd
Version: 2.37.0 Version: 2.38.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: I2P router written in C++ Summary: I2P router written in C++
Conflicts: i2pd-git Conflicts: i2pd-git
@ -135,6 +135,9 @@ getent passwd i2pd >/dev/null || \
%changelog %changelog
* Mon May 17 2021 orignal <i2porignal@yandex.ru> - 2.38.0
- update to 2.38.0
* Mon Mar 15 2021 orignal <i2porignal@yandex.ru> - 2.37.0 * Mon Mar 15 2021 orignal <i2porignal@yandex.ru> - 2.37.0
- update to 2.37.0 - update to 2.37.0

View File

@ -218,7 +218,7 @@ namespace util
{ {
uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port);
if (!ntcp2port) ntcp2port = port; // use standard port if (!ntcp2port) ntcp2port = port; // use standard port
i2p::context.PublishNTCP2Address (ntcp2port, true); // publish i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish
if (ipv6) if (ipv6)
{ {
std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr);
@ -228,12 +228,11 @@ namespace util
} }
} }
else else
i2p::context.PublishNTCP2Address (port, false); // unpublish i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish
} }
if (ygg) if (ygg)
{ {
if (!ntcp2) i2p::context.PublishNTCP2Address (port, true, false, false, true);
i2p::context.PublishNTCP2Address (port, true);
i2p::context.UpdateNTCP2V6Address (yggaddr); i2p::context.UpdateNTCP2V6Address (yggaddr);
if (!ipv4 && !ipv6) if (!ipv4 && !ipv6)
i2p::context.SetStatus (eRouterStatusMesh); i2p::context.SetStatus (eRouterStatusMesh);
@ -281,7 +280,7 @@ namespace util
else if (isFloodfill) else if (isFloodfill)
{ {
LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'");
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2);
} }
else else
{ {

View File

@ -242,13 +242,9 @@ namespace http {
s << "<b>ERROR:</b>&nbsp;" << string << "<br>\r\n"; s << "<b>ERROR:</b>&nbsp;" << string << "<br>\r\n";
} }
void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) static void ShowNetworkStatus (std::stringstream& s, RouterStatus status)
{ {
s << "<b>Uptime:</b> "; switch (status)
ShowUptime(s, i2p::context.GetUptime ());
s << "<br>\r\n";
s << "<b>Network status:</b> ";
switch (i2p::context.GetStatus ())
{ {
case eRouterStatusOK: s << "OK"; break; case eRouterStatusOK: s << "OK"; break;
case eRouterStatusTesting: s << "Testing"; break; case eRouterStatusTesting: s << "Testing"; break;
@ -276,7 +272,22 @@ namespace http {
} }
default: s << "Unknown"; default: s << "Unknown";
} }
}
void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat)
{
s << "<b>Uptime:</b> ";
ShowUptime(s, i2p::context.GetUptime ());
s << "<br>\r\n"; s << "<br>\r\n";
s << "<b>Network status:</b> ";
ShowNetworkStatus (s, i2p::context.GetStatus ());
s << "<br>\r\n";
if (i2p::context.SupportsV6 ())
{
s << "<b>Network status 6:</b> ";
ShowNetworkStatus (s, i2p::context.GetStatusV6 ());
s << "<br>\r\n";
}
#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
if (auto remains = Daemon.gracefulShutdownInterval) { if (auto remains = Daemon.gracefulShutdownInterval) {
s << "<b>Stopping in:</b> "; s << "<b>Stopping in:</b> ";
@ -314,6 +325,7 @@ namespace http {
if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) if (!i2p::context.GetRouterInfo().GetProperty("family").empty())
s << "<b>Router Family:</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n"; s << "<b>Router Family:</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n";
s << "<b>Router Caps:</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n"; s << "<b>Router Caps:</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n";
s << "<b>Version:</b> " VERSION "<br>\r\n";
s << "<b>Our external address:</b>" << "<br>\r\n<table class=\"extaddr\"><tbody>\r\n"; s << "<b>Our external address:</b>" << "<br>\r\n<table class=\"extaddr\"><tbody>\r\n";
for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) for (const auto& address : i2p::context.GetRouterInfo().GetAddresses())
{ {
@ -831,7 +843,7 @@ namespace http {
s << "<b>SAM Sessions:</b><br>\r\n<div class=\"list\">\r\n"; s << "<b>SAM Sessions:</b><br>\r\n<div class=\"list\">\r\n";
for (auto& it: sam->GetSessions ()) for (auto& it: sam->GetSessions ())
{ {
auto& name = it.second->localDestination->GetNickname (); auto& name = it.second->GetLocalDestination ()->GetNickname ();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << "\">"; s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << "\">";
s << name << " (" << it.first << ")</a></div>\r\n" << std::endl; s << name << " (" << it.first << ")</a></div>\r\n" << std::endl;
} }
@ -857,7 +869,7 @@ namespace http {
std::string webroot; i2p::config::GetOption("http.webroot", webroot); std::string webroot; i2p::config::GetOption("http.webroot", webroot);
s << "<b>SAM Session:</b><br>\r\n<div class=\"list\">\r\n"; s << "<b>SAM Session:</b><br>\r\n<div class=\"list\">\r\n";
auto& ident = session->localDestination->GetIdentHash(); auto& ident = session->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n"; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n";
s << "<br>\r\n"; s << "<br>\r\n";

View File

@ -2,6 +2,10 @@
#include <sstream> #include <sstream>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/pem.h> #include <openssl/pem.h>
// Use global placeholders from boost introduced when local_time.hpp is loaded
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/date_time/local_time/local_time.hpp> #include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
@ -714,8 +718,8 @@ namespace client
for (auto& it: sam->GetSessions ()) for (auto& it: sam->GetSessions ())
{ {
boost::property_tree::ptree sam_session, sam_session_sockets; boost::property_tree::ptree sam_session, sam_session_sockets;
auto& name = it.second->localDestination->GetNickname (); auto& name = it.second->GetLocalDestination ()->GetNickname ();
auto& ident = it.second->localDestination->GetIdentHash(); auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
sam_session.put("name", name); sam_session.put("name", name);
sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i2pd (2.38.0-1) unstable; urgency=medium
* updated to version 2.38.0/0.9.50
-- orignal <orignal@i2pmail.org> Mon, 17 May 2021 16:00:00 +0000
i2pd (2.37.0-1) unstable; urgency=medium i2pd (2.37.0-1) unstable; urgency=medium
* updated to version 2.37.0 * updated to version 2.37.0

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2020, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -277,6 +277,14 @@ namespace data
i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub);
publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH;
break; break;
case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
{
uint8_t exp[64];
i2p::crypto::Ed25519::ExpandPrivateKey (priv, exp);
i2p::crypto::GetEd25519 ()->BlindPrivateKey (exp, seed, blindedPriv, blindedPub);
publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH;
break;
}
default: default:
LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType);
} }

View File

@ -62,11 +62,11 @@ namespace config {
("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)")
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") ("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)")
("ntcp", bool_switch()->default_value(false), "Ignored. Always false") ("ntcp", bool_switch()->default_value(false), "Deprecated option. Always false")
("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)")
("ntcpproxy", value<std::string>()->default_value(""), "Ignored") ("ntcpproxy", value<std::string>()->default_value(""), "Deprecated option")
#ifdef _WIN32 #ifdef _WIN32
("svcctl", value<std::string>()->default_value(""), "Ignored") ("svcctl", value<std::string>()->default_value(""), "Deprecated option")
("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)")
("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask") ("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask")
#endif #endif
@ -77,9 +77,9 @@ namespace config {
("limits.coresize", value<uint32_t>()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") ("limits.coresize", value<uint32_t>()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)")
("limits.openfiles", value<uint16_t>()->default_value(0), "Maximum number of open files (0 - use system default)") ("limits.openfiles", value<uint16_t>()->default_value(0), "Maximum number of open files (0 - use system default)")
("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)") ("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)")
("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)") ("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Deprecated option")
("limits.ntcphard", value<uint16_t>()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") ("limits.ntcphard", value<uint16_t>()->default_value(0), "Deprecated option")
("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)") ("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Deprecated option")
; ;
options_description httpserver("HTTP Server options"); options_description httpserver("HTTP Server options");
@ -281,7 +281,7 @@ namespace config {
options_description meshnets("Meshnet transports options"); options_description meshnets("Meshnet transports options");
meshnets.add_options() meshnets.add_options()
("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (deafult: false)") ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (default: false)")
("meshnets.yggaddress", value<std::string>()->default_value(""), "Yggdrasil address to publish") ("meshnets.yggaddress", value<std::string>()->default_value(""), "Yggdrasil address to publish")
; ;

View File

@ -300,7 +300,11 @@ namespace client
{ {
int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels
if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum
CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels)); auto tunnels = m_Pool->GetInboundTunnels (numTunnels);
if (!tunnels.empty ())
CreateNewLeaseSet (tunnels);
else
LogPrint (eLogInfo, "Destination: No inbound tunnels for LeaseSet");
} }
bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
@ -386,7 +390,7 @@ namespace client
if (leaseSet->IsNewer (buf + offset, len - offset)) if (leaseSet->IsNewer (buf + offset, len - offset))
{ {
leaseSet->Update (buf + offset, len - offset); leaseSet->Update (buf + offset, len - offset);
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); LogPrint (eLogDebug, "Destination: Remote LeaseSet updated");
else else
{ {
@ -405,7 +409,7 @@ namespace client
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
else else
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2 leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
{ {
if (leaseSet->GetIdentHash () != GetIdentHash ()) if (leaseSet->GetIdentHash () != GetIdentHash ())
{ {
@ -589,7 +593,6 @@ namespace client
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1)); shared_from_this (), std::placeholders::_1));
} }
} }
} }
@ -1127,6 +1130,21 @@ namespace client
return dest; return dest;
} }
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::RemoveStreamingDestination (int port)
{
if (port)
{
auto it = m_StreamingDestinationsByPorts.find (port);
if (it != m_StreamingDestinationsByPorts.end ())
{
auto ret = it->second;
m_StreamingDestinationsByPorts.erase (it);
return ret;
}
}
return nullptr;
}
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip)
{ {
if (m_DatagramDestination == nullptr) if (m_DatagramDestination == nullptr)
@ -1176,7 +1194,7 @@ namespace client
LogPrint(eLogError, "Destinations: Can't save keys to ", path); LogPrint(eLogError, "Destinations: Can't save keys to ", path);
} }
void ClientDestination::CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
{ {
std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet; std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)

View File

@ -152,7 +152,7 @@ namespace client
virtual void CleanupDestination () {}; // additional clean up in derived classes virtual void CleanupDestination () {}; // additional clean up in derived classes
// I2CP // I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
virtual void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) = 0; virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0;
private: private:
@ -236,6 +236,7 @@ namespace client
// streaming // streaming
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const; std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
std::shared_ptr<i2p::stream::StreamingDestination> RemoveStreamingDestination (int port);
// following methods operate with default streaming destination // following methods operate with default streaming destination
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);
void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> dest, int port = 0);
@ -262,7 +263,7 @@ namespace client
void CleanupDestination (); void CleanupDestination ();
// I2CP // I2CP
void HandleDataMessage (const uint8_t * buf, size_t len); void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels); void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private: private:

View File

@ -160,9 +160,10 @@ namespace garlic
return true; return true;
} }
ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS):
GarlicRoutingSession (owner, attachLeaseSet) GarlicRoutingSession (owner, true)
{ {
if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate);
RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0;
} }
@ -195,7 +196,7 @@ namespace garlic
i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys);
} }
// we still didn't find elligator eligible pair // we still didn't find elligator eligible pair
for (int i = 0; i < 10; i++) for (int i = 0; i < 25; i++)
{ {
// create new // create new
m_EphemeralKeys = std::make_shared<i2p::crypto::X25519Keys>(); m_EphemeralKeys = std::make_shared<i2p::crypto::X25519Keys>();
@ -511,6 +512,7 @@ namespace garlic
{ {
auto tagsetNsr = std::make_shared<ReceiveRatchetTagSet>(shared_from_this (), true); auto tagsetNsr = std::make_shared<ReceiveRatchetTagSet>(shared_from_this (), true);
InitNewSessionTagset (tagsetNsr); InitNewSessionTagset (tagsetNsr);
tagsetNsr->Expire (); // let non-replied session expire
GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS); GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS);
} }
} }
@ -813,7 +815,6 @@ namespace garlic
case eSessionStateNew: case eSessionStateNew:
return HandleNewIncomingSession (buf, len); return HandleNewIncomingSession (buf, len);
case eSessionStateNewSessionSent: case eSessionStateNewSessionSent:
receiveTagset->Expire (); // NSR tagset
return HandleNewOutgoingSessionReply (buf, len); return HandleNewOutgoingSessionReply (buf, len);
default: default:
return false; return false;
@ -1101,6 +1102,7 @@ namespace garlic
RouterIncomingRatchetSession::RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState): RouterIncomingRatchetSession::RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState):
ECIESX25519AEADRatchetSession (&i2p::context, false) ECIESX25519AEADRatchetSession (&i2p::context, false)
{ {
SetLeaseSetUpdateStatus (eLeaseSetDoNotSend);
SetNoiseState (initState); SetNoiseState (initState);
} }

View File

@ -88,6 +88,7 @@ namespace garlic
bool IsNS () const { return m_IsNS; }; bool IsNS () const { return m_IsNS; };
std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; }; std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; };
void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; };
int GetTrimBehind () const { return m_TrimBehindIndex; };
void Expire (); void Expire ();
bool IsExpired (uint64_t ts) const; bool IsExpired (uint64_t ts) const;
@ -160,7 +161,7 @@ namespace garlic
public: public:
ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS);
~ECIESX25519AEADRatchetSession (); ~ECIESX25519AEADRatchetSession ();
bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0);

View File

@ -553,9 +553,10 @@ namespace garlic
if (!session->HandleNextMessage (buf, length, nullptr, 0)) if (!session->HandleNextMessage (buf, length, nullptr, 0))
{ {
// try to gererate more tags for last tagset // try to gererate more tags for last tagset
if (m_LastTagset && m_LastTagset->GetNextIndex () < 2*ECIESX25519_TAGSET_MAX_NUM_TAGS) if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS))
{ {
auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS);
LogPrint (eLogWarning, "Garlic: trying to generate more ECIES-X25519-AEAD-Ratchet tags");
for (int i = 0; i < maxTags; i++) for (int i = 0; i < maxTags; i++)
{ {
auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset);
@ -879,7 +880,11 @@ namespace garlic
{ {
auto session = it->second.tagset->GetSession (); auto session = it->second.tagset->GetSession ();
if (!session || session->IsTerminated()) if (!session || session->IsTerminated())
it->second.tagset->Expire (); {
it = m_ECIESx25519Tags.erase (it);
numExpiredTags++;
}
else
++it; ++it;
} }
} }

View File

@ -371,10 +371,7 @@ namespace i2p
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{ {
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
BN_CTX * ctx = BN_CTX_new (); if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false;
bool success = i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx);
BN_CTX_free (ctx);
if(!success) return false;
uint8_t retCode = 0; uint8_t retCode = 0;
bool isECIES = i2p::context.IsECIES (); bool isECIES = i2p::context.IsECIES ();
// replace record to reply // replace record to reply

View File

@ -72,6 +72,12 @@ namespace data
} }
size += 256; // encryption key size += 256; // encryption key
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
if (size + 1 > m_BufferLen)
{
LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen);
m_IsValid = false;
return;
}
uint8_t num = m_Buffer[size]; uint8_t num = m_Buffer[size];
size++; // num size++; // num
LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); LogPrint (eLogDebug, "LeaseSet: read num=", (int)num);
@ -81,9 +87,14 @@ namespace data
m_IsValid = false; m_IsValid = false;
return; return;
} }
if (size + num*LEASE_SIZE > m_BufferLen)
{
LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen);
m_IsValid = false;
return;
}
UpdateLeasesBegin (); UpdateLeasesBegin ();
// process leases // process leases
m_ExpirationTime = 0; m_ExpirationTime = 0;
auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ts = i2p::util::GetMillisecondsSinceEpoch ();
@ -106,16 +117,24 @@ namespace data
return; return;
} }
m_ExpirationTime += LEASE_ENDDATE_THRESHOLD; m_ExpirationTime += LEASE_ENDDATE_THRESHOLD;
UpdateLeasesEnd (); UpdateLeasesEnd ();
// verify // verify
if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) if (verifySignature)
{
auto signedSize = leases - m_Buffer;
if (signedSize + m_Identity->GetSignatureLen () > m_BufferLen)
{
LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", m_BufferLen);
m_IsValid = false;
}
else if (!m_Identity->Verify (m_Buffer, signedSize, leases))
{ {
LogPrint (eLogWarning, "LeaseSet: verification failed"); LogPrint (eLogWarning, "LeaseSet: verification failed");
m_IsValid = false; m_IsValid = false;
} }
} }
}
void LeaseSet::UpdateLeasesBegin () void LeaseSet::UpdateLeasesBegin ()
{ {
@ -778,7 +797,7 @@ namespace data
} }
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const KeySections& encryptionKeys, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels, const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, bool isPublishedEncrypted): bool isPublic, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0) LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{ {
@ -843,9 +862,18 @@ namespace data
offset += 4; // end date offset += 4; // end date
} }
// update expiration // update expiration
if (expirationTime)
{
SetExpirationTime (expirationTime*1000LL); SetExpirationTime (expirationTime*1000LL);
auto expires = expirationTime - timestamp; auto expires = (int)expirationTime - timestamp;
htobe16buf (expiresBuf, expires > 0 ? expires : 0); htobe16buf (expiresBuf, expires > 0 ? expires : 0);
}
else
{
// no tunnels or withdraw
SetExpirationTime (timestamp*1000LL);
memset (expiresBuf, 0, 2); // expires immeditely
}
// sign // sign
keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type
} }

View File

@ -251,7 +251,7 @@ namespace data
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const KeySections& encryptionKeys, const KeySections& encryptionKeys,
std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, bool isPublishedEncrypted = false); bool isPublic, bool isPublishedEncrypted = false);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP

View File

@ -1193,7 +1193,12 @@ namespace transport
m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6());
m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true));
m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true));
m_NTCP2V6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); auto ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port);
if (m_Address6 && !context.SupportsMesh ())
ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port);
else if (m_YggdrasilAddress && !context.SupportsV6 ())
ep = boost::asio::ip::tcp::endpoint (m_YggdrasilAddress->address(), address->port);
m_NTCP2V6Acceptor->bind (ep);
m_NTCP2V6Acceptor->listen (); m_NTCP2V6Acceptor->listen ();
LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port);

View File

@ -336,7 +336,7 @@ namespace data
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{ {
if (leaseSet->IsPublic ()) if (leaseSet->IsPublic () && !leaseSet->IsExpired ())
{ {
// TODO: implement actual update // TODO: implement actual update
LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
@ -345,7 +345,7 @@ namespace data
} }
else else
{ {
LogPrint (eLogWarning, "NetDb: Unpublished LeaseSet2 received: ", ident.ToBase32()); LogPrint (eLogWarning, "NetDb: Unpublished or expired LeaseSet2 received: ", ident.ToBase32());
m_LeaseSets.erase (ident); m_LeaseSets.erase (ident);
} }
} }
@ -454,7 +454,7 @@ namespace data
{ {
auto r = std::make_shared<RouterInfo>(path); auto r = std::make_shared<RouterInfo>(path);
if (r->GetRouterIdentity () && !r->IsUnreachable () && if (r->GetRouterIdentity () && !r->IsUnreachable () &&
(!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour (r->IsReachable () || !r->IsSSU (false) || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour
{ {
r->DeleteBuffer (); r->DeleteBuffer ();
r->ClearProperties (); // properties are not used for regular routers r->ClearProperties (); // properties are not used for regular routers
@ -584,7 +584,7 @@ namespace data
if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS)
it.second->SetUnreachable (false); it.second->SetUnreachable (false);
// find & mark expired routers // find & mark expired routers
if (it.second->UsesIntroducer ()) if (!it.second->IsReachable () && it.second->IsSSU (false))
{ {
if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)
// RouterInfo expires after 1 hour if uses introducer // RouterInfo expires after 1 hour if uses introducer
@ -1149,12 +1149,13 @@ namespace data
}); });
} }
std::shared_ptr<const RouterInfo> NetDb::GetRandomPeerTestRouter (bool v4only) const std::shared_ptr<const RouterInfo> NetDb::GetRandomPeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const
{ {
return GetRandomRouter ( return GetRandomRouter (
[v4only](std::shared_ptr<const RouterInfo> router)->bool [v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
{ {
return !router->IsHidden () && router->IsPeerTesting (v4only); return !router->IsHidden () && router->IsPeerTesting (v4) &&
!excluded.count (router->GetIdentHash ());
}); });
} }
@ -1167,12 +1168,13 @@ namespace data
}); });
} }
std::shared_ptr<const RouterInfo> NetDb::GetRandomIntroducer () const std::shared_ptr<const RouterInfo> NetDb::GetRandomIntroducer (bool v4, const std::set<IdentHash>& excluded) const
{ {
return GetRandomRouter ( return GetRandomRouter (
[](std::shared_ptr<const RouterInfo> router)->bool [v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
{ {
return router->IsIntroducer () && !router->IsHidden () && !router->IsFloodfill (); // floodfills don't send relay tag return router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()) &&
!router->IsHidden () && !router->IsFloodfill (); // floodfills don't send relay tag
}); });
} }

View File

@ -85,9 +85,9 @@ namespace data
std::shared_ptr<const RouterInfo> GetRandomRouter () const; std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const; std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const; std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4only = true) const; std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSUV6Router () const; // TODO: change to v6 peer test later std::shared_ptr<const RouterInfo> GetRandomSSUV6Router () const; // TODO: change to v6 peer test later
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const; std::shared_ptr<const RouterInfo> GetRandomIntroducer (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const; std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num, std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const; std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;

View File

@ -679,7 +679,30 @@ namespace data
auto it = boost::asio::ip::tcp::resolver(service).resolve ( auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
if (!ecode) if (!ecode)
s.lowest_layer().connect (*it, ecode); {
bool connected = false;
boost::asio::ip::tcp::resolver::iterator end;
while (it != end)
{
boost::asio::ip::tcp::endpoint ep = *it;
if ((ep.address ().is_v4 () && i2p::context.SupportsV4 ()) ||
(ep.address ().is_v6 () && i2p::context.SupportsV6 ()))
{
s.lowest_layer().connect (ep, ecode);
if (!ecode)
{
connected = true;
break;
}
}
it++;
}
if (!connected)
{
LogPrint(eLogError, "Reseed: Failed to connect to ", url.host);
return "";
}
}
} }
if (!ecode) if (!ecode)
{ {

View File

@ -28,7 +28,7 @@ namespace i2p
RouterContext::RouterContext (): RouterContext::RouterContext ():
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown),
m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID)
{ {
} }
@ -199,10 +199,29 @@ namespace i2p
switch (m_Status) switch (m_Status)
{ {
case eRouterStatusOK: case eRouterStatusOK:
SetReachable (); SetReachable (true, false); // ipv4
break; break;
case eRouterStatusFirewalled: case eRouterStatusFirewalled:
SetUnreachable (); SetUnreachable (true, false); // ipv4
break;
default:
;
}
}
}
void RouterContext::SetStatusV6 (RouterStatus status)
{
if (status != m_StatusV6)
{
m_StatusV6 = status;
switch (m_StatusV6)
{
case eRouterStatusOK:
SetReachable (false, true); // ipv6
break;
case eRouterStatusFirewalled:
SetUnreachable (false, true); // ipv6
break; break;
default: default:
; ;
@ -225,13 +244,23 @@ namespace i2p
UpdateRouterInfo (); UpdateRouterInfo ();
} }
void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4only) void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg)
{ {
if (!m_NTCP2Keys) return; if (!m_NTCP2Keys) return;
bool updated = false; bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ()) for (auto& address : m_RouterInfo.GetAddresses ())
{ {
if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish) && (!v4only || address->IsV4 ())) if (address->IsNTCP2 () && (address->port != port || address->published != publish))
{
bool isAddr = v4 && address->IsV4 ();
if (!isAddr && (v6 || ygg))
{
if (i2p::util::net::IsYggdrasilAddress (address->host))
isAddr = ygg;
else
isAddr = v6 && address->IsV6 ();
}
if (isAddr)
{ {
if (!port && !address->port) if (!port && !address->port)
{ {
@ -240,12 +269,12 @@ namespace i2p
if (port == 9150) port = 9151; // Tor browser if (port == 9150) port = 9151; // Tor browser
} }
if (port) address->port = port; if (port) address->port = port;
address->cost = publish ? i2p::data::COST_NTCP2_PUBLISHED : i2p::data::COST_NTCP2_NON_PUBLISHED; address->published = publish;
address->ntcp2->isPublished = publish;
address->ntcp2->iv = m_NTCP2Keys->iv; address->ntcp2->iv = m_NTCP2Keys->iv;
updated = true; updated = true;
} }
} }
}
if (updated) if (updated)
UpdateRouterInfo (); UpdateRouterInfo ();
} }
@ -435,7 +464,9 @@ namespace i2p
} }
} }
void RouterContext::SetUnreachable () void RouterContext::SetUnreachable (bool v4, bool v6)
{
if (v4 || (v6 && !SupportsV4 ()))
{ {
// set caps // set caps
uint8_t caps = m_RouterInfo.GetCaps (); uint8_t caps = m_RouterInfo.GetCaps ();
@ -443,26 +474,29 @@ namespace i2p
caps |= i2p::data::RouterInfo::eUnreachable; caps |= i2p::data::RouterInfo::eUnreachable;
caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill
m_RouterInfo.SetCaps (caps); m_RouterInfo.SetCaps (caps);
}
uint16_t port = 0; uint16_t port = 0;
// delete previous introducers // delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses (); auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses) for (auto& addr : addresses)
if (addr->ssu) if (addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{ {
addr->cost = i2p::data::COST_SSU_THROUGH_INTRODUCERS; addr->published = false;
addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer
addr->ssu->introducers.clear (); addr->ssu->introducers.clear ();
port = addr->port; port = addr->port;
} }
// remove NTCP2 v4 address // unpiblish NTCP2 addreeses
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2) if (ntcp2)
PublishNTCP2Address (port, false, true); PublishNTCP2Address (port, false, v4, v6, false);
// update // update
UpdateRouterInfo (); UpdateRouterInfo ();
} }
void RouterContext::SetReachable () void RouterContext::SetReachable (bool v4, bool v6)
{
if (v4 || (v6 && !SupportsV4 ()))
{ {
// update caps // update caps
uint8_t caps = m_RouterInfo.GetCaps (); uint8_t caps = m_RouterInfo.GetCaps ();
@ -471,18 +505,19 @@ namespace i2p
if (m_IsFloodfill) if (m_IsFloodfill)
caps |= i2p::data::RouterInfo::eFloodfill; caps |= i2p::data::RouterInfo::eFloodfill;
m_RouterInfo.SetCaps (caps); m_RouterInfo.SetCaps (caps);
}
uint16_t port = 0; uint16_t port = 0;
// delete previous introducers // delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses (); auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses) for (auto& addr : addresses)
if (addr->ssu) if (addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{ {
addr->cost = i2p::data::COST_SSU_DIRECT; addr->published = true;
addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; addr->caps |= i2p::data::RouterInfo::eSSUIntroducer;
addr->ssu->introducers.clear (); addr->ssu->introducers.clear ();
port = addr->port; port = addr->port;
} }
// insert NTCP2 back // publish NTCP2
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2) if (ntcp2)
{ {
@ -491,7 +526,7 @@ namespace i2p
{ {
uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port);
if (!ntcp2Port) ntcp2Port = port; if (!ntcp2Port) ntcp2Port = port;
PublishNTCP2Address (ntcp2Port, true, true); PublishNTCP2Address (ntcp2Port, true, v4, v6, false);
} }
} }
// update // update
@ -512,7 +547,7 @@ namespace i2p
{ {
if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU) if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU)
foundSSU = true; foundSSU = true;
else if (addr->IsPublishedNTCP2 ()) else if (addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
foundNTCP2 = true; foundNTCP2 = true;
} }
port = addr->port; port = addr->port;
@ -705,8 +740,8 @@ namespace i2p
if (!rekey && m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) if (!rekey && m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
{ {
// rekey routers with bandwidth = L (or default) this time // rekey routers with bandwidth = L (or default) this time
std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill);
if (bandwidth.empty () || bandwidth[0] == 'L') rekey = true; if (!isFloodfill) rekey = true;
} }
if (rekey) if (rekey)
{ {
@ -749,7 +784,7 @@ namespace i2p
} }
if (IsUnreachable ()) if (IsUnreachable ())
SetReachable (); // we assume reachable until we discover firewall through peer tests SetReachable (true, true); // we assume reachable until we discover firewall through peer tests
// read NTCP2 // read NTCP2
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
@ -844,7 +879,7 @@ namespace i2p
return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false;
} }
bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data)
{ {
if (!m_TunnelDecryptor) return false; if (!m_TunnelDecryptor) return false;
if (IsECIES ()) if (IsECIES ())
@ -854,7 +889,7 @@ namespace i2p
m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState));
m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk)
uint8_t sharedSecret[32]; uint8_t sharedSecret[32];
if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, ctx, false)) if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false))
{ {
LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); LogPrint (eLogWarning, "Router: Incorrect ephemeral public key");
return false; return false;
@ -873,7 +908,12 @@ namespace i2p
return true; return true;
} }
else else
return m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); {
BN_CTX * ctx = BN_CTX_new ();
bool success = m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false);
BN_CTX_free (ctx);
return success;
}
} }
i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () i2p::crypto::X25519Keys& RouterContext::GetStaticKeys ()

View File

@ -91,20 +91,22 @@ namespace garlic
void SetStatus (RouterStatus status); void SetStatus (RouterStatus status);
RouterError GetError () const { return m_Error; }; RouterError GetError () const { return m_Error; };
void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; };
RouterStatus GetStatusV6 () const { return m_StatusV6; };
void SetStatusV6 (RouterStatus status);
int GetNetID () const { return m_NetID; }; int GetNetID () const { return m_NetID; };
void SetNetID (int netID) { m_NetID = netID; }; void SetNetID (int netID) { m_NetID = netID; };
bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data);
void UpdatePort (int port); // called from Daemon void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
void PublishNTCP2Address (int port, bool publish = true, bool v4only = false); void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg);
void UpdateNTCP2Address (bool enable); void UpdateNTCP2Address (bool enable);
void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
bool IsUnreachable () const; bool IsUnreachable () const;
void SetUnreachable (); void SetUnreachable (bool v4, bool v6);
void SetReachable (); void SetReachable (bool v4, bool v6);
bool IsFloodfill () const { return m_IsFloodfill; }; bool IsFloodfill () const { return m_IsFloodfill; };
void SetFloodfill (bool floodfill); void SetFloodfill (bool floodfill);
void SetFamily (const std::string& family); void SetFamily (const std::string& family);
@ -168,7 +170,7 @@ namespace garlic
std::chrono::time_point<std::chrono::steady_clock> m_StartupTime; std::chrono::time_point<std::chrono::steady_clock> m_StartupTime;
uint64_t m_BandwidthLimit; // allowed bandwidth uint64_t m_BandwidthLimit; // allowed bandwidth
int m_ShareRatio; int m_ShareRatio;
RouterStatus m_Status; RouterStatus m_Status, m_StatusV6;
RouterError m_Error; RouterError m_Error;
int m_NetID; int m_NetID;
std::mutex m_GarlicMutex; std::mutex m_GarlicMutex;

View File

@ -197,7 +197,8 @@ namespace data
{ {
uint8_t supportedTransports = 0; uint8_t supportedTransports = 0;
auto address = std::make_shared<Address>(); auto address = std::make_shared<Address>();
s.read ((char *)&address->cost, sizeof (address->cost)); uint8_t cost; // ignore
s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date)); s.read ((char *)&address->date, sizeof (address->date));
bool isHost = false, isIntroKey = false, isStaticKey = false; bool isHost = false, isIntroKey = false, isStaticKey = false;
char transportStyle[6]; char transportStyle[6];
@ -260,7 +261,7 @@ namespace data
else if (!strcmp (key, "i")) // ntcp2 iv else if (!strcmp (key, "i")) // ntcp2 iv
{ {
Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16);
address->ntcp2->isPublished = true; // presence if "i" means "published" address->published = true; // presence if "i" means "published"
} }
else if (key[0] == 'i') else if (key[0] == 'i')
{ {
@ -308,7 +309,7 @@ namespace data
else else
supportedTransports |= eNTCP2V4; supportedTransports |= eNTCP2V4;
} }
else if (!address->ntcp2->isPublished) else if (!address->published)
{ {
if (address->caps) if (address->caps)
{ {
@ -333,6 +334,23 @@ namespace data
} }
else else
supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4 supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4
if (address->ssu && !address->ssu->introducers.empty ())
{
// exclude invalid introducers
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
int numValid = 0;
for (auto& it: address->ssu->introducers)
{
if ((!it.iExp || ts <= it.iExp) && it.iPort > 0 &&
((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ())))
numValid++;
else
it.iPort = 0;
}
if (!numValid) address->ssu->introducers.resize (0);
}
else if (isHost && address->port)
address->published = true;
} }
} }
if (supportedTransports) if (supportedTransports)
@ -513,7 +531,13 @@ namespace data
for (const auto& addr_ptr : *m_Addresses) for (const auto& addr_ptr : *m_Addresses)
{ {
const Address& address = *addr_ptr; const Address& address = *addr_ptr;
s.write ((const char *)&address.cost, sizeof (address.cost)); // calculate cost
uint8_t cost = 0x7f;
if (address.transportStyle == eTransportNTCP)
cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED;
else if (address.transportStyle == eTransportSSU)
cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS;
s.write ((const char *)&cost, sizeof (cost));
s.write ((const char *)&address.date, sizeof (address.date)); s.write ((const char *)&address.date, sizeof (address.date));
std::stringstream properties; std::stringstream properties;
bool isPublished = false; bool isPublished = false;
@ -529,8 +553,8 @@ namespace data
WriteString ("caps", properties); WriteString ("caps", properties);
properties << '='; properties << '=';
std::string caps; std::string caps;
if (address.caps & AddressCaps::eV4) caps += CAPS_FLAG_V4; if (address.IsV4 ()) caps += CAPS_FLAG_V4;
if (address.caps & AddressCaps::eV6) caps += CAPS_FLAG_V6; if (address.IsV6 ()) caps += CAPS_FLAG_V6;
if (caps.empty ()) caps += CAPS_FLAG_V4; if (caps.empty ()) caps += CAPS_FLAG_V4;
WriteString (caps, properties); WriteString (caps, properties);
properties << ';'; properties << ';';
@ -549,7 +573,7 @@ namespace data
if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
if (address.host.is_v4 ()) if (address.host.is_v4 ())
{ {
if (IsReachable ()) if (address.published)
{ {
isPublished = true; isPublished = true;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
@ -558,11 +582,19 @@ namespace data
caps += CAPS_FLAG_V4; caps += CAPS_FLAG_V4;
} }
else if (address.host.is_v6 ()) else if (address.host.is_v6 ())
{
if (address.published)
{
isPublished = true; isPublished = true;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
}
else
caps += CAPS_FLAG_V6;
}
else else
{ {
if (address.caps & AddressCaps::eV4) caps += CAPS_FLAG_V4; if (address.IsV4 ()) caps += CAPS_FLAG_V4;
if (address.caps & AddressCaps::eV6) caps += CAPS_FLAG_V6; if (address.IsV6 ()) caps += CAPS_FLAG_V6;
if (caps.empty ()) caps += CAPS_FLAG_V4; if (caps.empty ()) caps += CAPS_FLAG_V4;
} }
WriteString (caps, properties); WriteString (caps, properties);
@ -585,6 +617,18 @@ namespace data
{ {
int i = 0; int i = 0;
for (const auto& introducer: address.ssu->introducers) for (const auto& introducer: address.ssu->introducers)
{
if (introducer.iExp) // expiration is specified
{
WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
properties << ';';
}
i++;
}
i = 0;
for (const auto& introducer: address.ssu->introducers)
{ {
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties); WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
@ -622,18 +666,6 @@ namespace data
properties << ';'; properties << ';';
i++; i++;
} }
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
if (introducer.iExp) // expiration is specified
{
WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
properties << ';';
}
i++;
}
} }
// write intro key // write intro key
WriteString ("key", properties); WriteString ("key", properties);
@ -788,7 +820,7 @@ namespace data
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 = COST_SSU_DIRECT; // NTCP2 should have priority over SSU addr->published = true;
addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC;
addr->date = 0; addr->date = 0;
addr->ssu.reset (new SSUExt ()); addr->ssu.reset (new SSUExt ());
@ -809,11 +841,10 @@ namespace data
addr->host = host; addr->host = host;
addr->port = port; addr->port = port;
addr->transportStyle = eTransportNTCP; addr->transportStyle = eTransportNTCP;
addr->cost = port ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; // override from RouterContext::PublishNTCP2Address
addr->caps = 0; addr->caps = 0;
addr->date = 0; addr->date = 0;
addr->ntcp2.reset (new NTCP2Ext ()); addr->ntcp2.reset (new NTCP2Ext ());
if (port) addr->ntcp2->isPublished = true; if (port) addr->published = true;
memcpy (addr->ntcp2->staticKey, staticKey, 32); memcpy (addr->ntcp2->staticKey, staticKey, 32);
memcpy (addr->ntcp2->iv, iv, 16); memcpy (addr->ntcp2->iv, iv, 16);
m_Addresses->push_back(std::move(addr)); m_Addresses->push_back(std::move(addr));
@ -823,7 +854,8 @@ namespace data
{ {
for (auto& addr : *m_Addresses) for (auto& addr : *m_Addresses)
{ {
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) if (addr->transportStyle == eTransportSSU &&
((addr->IsV4 () && introducer.iHost.is_v4 ()) || (addr->IsV6 () && introducer.iHost.is_v6 ())))
{ {
for (auto& intro: addr->ssu->introducers) for (auto& intro: addr->ssu->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented if (intro.iTag == introducer.iTag) return false; // already presented
@ -838,7 +870,8 @@ namespace data
{ {
for (auto& addr: *m_Addresses) for (auto& addr: *m_Addresses)
{ {
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) if (addr->transportStyle == eTransportSSU &&
((addr->IsV4 () && e.address ().is_v4 ()) || (addr->IsV6 () && e.address ().is_v6 ())))
{ {
for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->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)
@ -1002,17 +1035,12 @@ namespace data
} }
} }
bool RouterInfo::UsesIntroducer () const
{
return m_Caps & Caps::eUnreachable; // non-reachable
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
{ {
return GetAddress ( return GetAddress (
[v4only](std::shared_ptr<const RouterInfo::Address> address)->bool [v4only](std::shared_ptr<const RouterInfo::Address> address)->bool
{ {
return (address->transportStyle == eTransportSSU) && (!v4only || address->host.is_v4 ()); return (address->transportStyle == eTransportSSU) && (!v4only || address->IsV4 ());
}); });
} }
@ -1021,7 +1049,7 @@ namespace data
return GetAddress ( return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool [](std::shared_ptr<const RouterInfo::Address> address)->bool
{ {
return (address->transportStyle == eTransportSSU) && address->host.is_v6 (); return (address->transportStyle == eTransportSSU) && address->IsV6();
}); });
} }
@ -1100,26 +1128,25 @@ namespace data
GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1;
} }
bool RouterInfo::IsPeerTesting (bool v4only) const bool RouterInfo::IsPeerTesting (bool v4) const
{ {
auto supportedTransports = m_SupportedTransports & (eSSUV4 | eSSUV6); if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false;
if (!supportedTransports) return false; // no SSU
if (v4only && !(supportedTransports & eSSUV4)) return false; // no SSU v4
return (bool)GetAddress ( return (bool)GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool [v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{ {
return (address->transportStyle == eTransportSSU) && address->IsPeerTesting (); return (address->transportStyle == eTransportSSU) && address->IsPeerTesting () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU ();
}); });
} }
bool RouterInfo::IsIntroducer () const bool RouterInfo::IsIntroducer (bool v4) const
{ {
// TODO: support ipv6 if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false;
if (!(m_SupportedTransports & eSSUV4)) return false;
return (bool)GetAddress ( return (bool)GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool [v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{ {
return (address->transportStyle == eTransportSSU) && address->IsIntroducer (); return (address->transportStyle == eTransportSSU) && address->IsIntroducer () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified ();
}); });
} }

View File

@ -96,7 +96,7 @@ namespace data
typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey
struct Introducer struct Introducer
{ {
Introducer (): iExp (0) {}; Introducer (): iPort (0), iExp (0) {};
boost::asio::ip::address iHost; boost::asio::ip::address iHost;
int iPort; int iPort;
IntroKey iKey; IntroKey iKey;
@ -115,7 +115,6 @@ namespace data
{ {
Tag<32> staticKey; Tag<32> staticKey;
Tag<16> iv; Tag<16> iv;
bool isPublished = false;
}; };
struct Address struct Address
@ -124,7 +123,8 @@ namespace data
boost::asio::ip::address host; boost::asio::ip::address host;
int port; int port;
uint64_t date; uint64_t date;
uint8_t cost, caps; uint8_t caps;
bool published = false;
std::unique_ptr<SSUExt> ssu; // not null for SSU std::unique_ptr<SSUExt> ssu; // not null for SSU
std::unique_ptr<NTCP2Ext> ntcp2; // not null for NTCP2 std::unique_ptr<NTCP2Ext> ntcp2; // not null for NTCP2
@ -146,14 +146,15 @@ namespace data
} }
bool IsNTCP2 () const { return (bool)ntcp2; }; bool IsNTCP2 () const { return (bool)ntcp2; };
bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; };
bool IsReachableSSU () const { return (bool)ssu && (!host.is_unspecified () || !ssu->introducers.empty ()); }; bool IsReachableSSU () const { return (bool)ssu && (!host.is_unspecified () || !ssu->introducers.empty ()); };
bool UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); };
bool IsIntroducer () const { return caps & eSSUIntroducer; }; bool IsIntroducer () const { return caps & eSSUIntroducer; };
bool IsPeerTesting () const { return caps & eSSUTesting; }; bool IsPeerTesting () const { return caps & eSSUTesting; };
bool IsV4 () const { return (caps & AddressCaps::eV4) || host.is_v4 (); }; bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); };
bool IsV6 () const { return (caps & AddressCaps::eV6) || host.is_v6 (); }; bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); };
}; };
typedef std::list<std::shared_ptr<Address> > Addresses; typedef std::list<std::shared_ptr<Address> > Addresses;
@ -204,13 +205,12 @@ namespace data
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 IsReachableFrom (const RouterInfo& other) const; bool IsReachableFrom (const RouterInfo& other) const;
bool HasValidAddresses () const { return m_SupportedTransports; }; bool HasValidAddresses () const { return m_SupportedTransports; };
bool UsesIntroducer () const;
bool IsHidden () const { return m_Caps & eHidden; }; bool IsHidden () const { return m_Caps & eHidden; };
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
bool IsEligibleFloodfill () const; bool IsEligibleFloodfill () const;
bool IsPeerTesting (bool v4only) const; bool IsPeerTesting (bool v4) const;
bool IsIntroducer () const; bool IsIntroducer (bool v4) const;
uint8_t GetCaps () const { return m_Caps; }; uint8_t GetCaps () const { return m_Caps; };
void SetCaps (uint8_t caps); void SetCaps (uint8_t caps);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2020, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -28,8 +28,8 @@ namespace transport
m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6),
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6),
m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service),
m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service)
{ {
} }
@ -82,6 +82,7 @@ namespace transport
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
ScheduleTermination (); ScheduleTermination ();
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
} }
if (context.SupportsV6 ()) if (context.SupportsV6 ())
{ {
@ -89,9 +90,9 @@ namespace transport
m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this));
m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this));
ScheduleTerminationV6 (); ScheduleTerminationV6 ();
ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers
} }
SchedulePeerTestsCleanupTimer (); SchedulePeerTestsCleanupTimer ();
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
} }
void SSUServer::Stop () void SSUServer::Stop ()
@ -100,6 +101,8 @@ namespace transport
m_IsRunning = false; m_IsRunning = false;
m_TerminationTimer.cancel (); m_TerminationTimer.cancel ();
m_TerminationTimerV6.cancel (); m_TerminationTimerV6.cancel ();
m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimerV6.cancel ();
m_Service.stop (); m_Service.stop ();
m_Socket.close (); m_Socket.close ();
m_SocketV6.close (); m_SocketV6.close ();
@ -385,7 +388,7 @@ namespace transport
auto it = sessions->find (packet->from); auto it = sessions->find (packet->from);
if (it != sessions->end ()) if (it != sessions->end ())
session = it->second; session = it->second;
if (!session) if (!session && packet->len > 0)
{ {
session = std::make_shared<SSUSession> (*this, packet->from); session = std::make_shared<SSUSession> (*this, packet->from);
session->WaitForConnect (); session->WaitForConnect ();
@ -393,6 +396,7 @@ namespace transport
LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
} }
} }
if (session)
session->ProcessNextMessage (packet->buf, packet->len, packet->from); session->ProcessNextMessage (packet->buf, packet->len, packet->from);
} }
catch (std::exception& ex) catch (std::exception& ex)
@ -406,20 +410,6 @@ namespace transport
if (session) session->FlushData (); 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 std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
{ {
auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
@ -430,28 +420,33 @@ namespace transport
return nullptr; return nullptr;
} }
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest, bool v4only) bool SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest, bool v4only)
{ {
auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ());
if (address) if (address)
CreateSession (router, address, peerTest); return CreateSession (router, address, peerTest);
else else
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
return false;
} }
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest) std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
{ {
if (router && address) if (router && address)
{ {
if (router->UsesIntroducer ()) if (address->UsesIntroducer ())
m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread
else else
{ {
if (address->host.is_unspecified () || !address->port) return false;
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest));
} }
} }
else
return false;
return true;
} }
void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest)
@ -480,14 +475,18 @@ namespace transport
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest) std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
{ {
if (router && router->UsesIntroducer () && address) if (router && address && address->UsesIntroducer ())
{ {
if (address->IsV4 () && !i2p::context.SupportsV4 ()) return;
if (address->IsV6 () && !i2p::context.SupportsV6 ()) return;
if (!address->host.is_unspecified () && address->port) if (!address->host.is_unspecified () && address->port)
{ {
// we rarely come here
auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions;
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto it = m_Sessions.find (remoteEndpoint); auto it = sessions.find (remoteEndpoint);
// check if session is presented already // check if session is presented already
if (it != m_Sessions.end ()) if (it != sessions.end ())
{ {
auto session = it->second; auto session = it->second;
if (peerTest && session->GetState () == eSessionStateEstablished) if (peerTest && session->GetState () == eSessionStateEstablished)
@ -503,14 +502,16 @@ namespace transport
std::shared_ptr<SSUSession> introducerSession; std::shared_ptr<SSUSession> introducerSession;
const i2p::data::RouterInfo::Introducer * introducer = nullptr; const i2p::data::RouterInfo::Introducer * introducer = nullptr;
// we might have a session to introducer already // we might have a session to introducer already
auto offset = rand ();
for (int i = 0; i < numIntroducers; i++) for (int i = 0; i < numIntroducers; i++)
{ {
auto intr = &(address->ssu->introducers[i]); auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]);
if (!intr->iPort) continue; // skip invalid introducer
if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer
boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort);
if (ep.address ().is_v4 ()) // ipv4 only if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4
{ {
if (!introducer) introducer = intr; // we pick first one for now if (!introducer) introducer = intr;
auto it = m_Sessions.find (ep); auto it = m_Sessions.find (ep);
if (it != m_Sessions.end ()) if (it != m_Sessions.end ())
{ {
@ -518,10 +519,20 @@ namespace transport
break; break;
} }
} }
if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6
{
if (!introducer) introducer = intr;
auto it = m_SessionsV6.find (ep);
if (it != m_SessionsV6.end ())
{
introducerSession = it->second;
break;
}
}
} }
if (!introducer) if (!introducer)
{ {
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 non-expired introducers presented"); LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented");
return; return;
} }
@ -532,20 +543,27 @@ namespace transport
LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost);
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router); introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
if (introducerEndpoint.address ().is_v4 ())
m_Sessions[introducerEndpoint] = introducerSession; m_Sessions[introducerEndpoint] = introducerSession;
else if (introducerEndpoint.address ().is_v6 ())
m_SessionsV6[introducerEndpoint] = introducerSession;
} }
if (!address->host.is_unspecified () && address->port) if (!address->host.is_unspecified () && address->port)
{ {
// create session // create session
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest); auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
if (address->host.is_v4 ())
m_Sessions[remoteEndpoint] = session; m_Sessions[remoteEndpoint] = session;
else if (address->host.is_v6 ())
m_SessionsV6[remoteEndpoint] = session;
// introduce // introduce
LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
"] through introducer ", introducer->iHost, ":", introducer->iPort); "] through introducer ", introducer->iHost, ":", introducer->iPort);
session->WaitForIntroduction (); session->WaitForIntroduction ();
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) ||
(address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled))
{ {
uint8_t buf[1]; uint8_t buf[1];
Send (buf, 0, remoteEndpoint); // send HolePunch Send (buf, 0, remoteEndpoint); // send HolePunch
@ -630,24 +648,30 @@ namespace transport
); );
} }
std::set<SSUSession *> SSUServer::FindIntroducers (int maxNumIntroducers) std::list<std::shared_ptr<SSUSession> > SSUServer::FindIntroducers (int maxNumIntroducers,
bool v4, std::set<i2p::data::IdentHash>& excluded)
{ {
uint32_t ts = i2p::util::GetSecondsSinceEpoch (); uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::set<SSUSession *> ret; std::list<std::shared_ptr<SSUSession> > ret;
for (int i = 0; i < maxNumIntroducers; i++) const auto& sessions = v4 ? m_Sessions : m_SessionsV6;
for (const auto& s : sessions)
{ {
auto session = GetRandomV4Session ( if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished &&
[&ret, ts](std::shared_ptr<SSUSession> session)->bool ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION)
{ ret.push_back (s.second);
return session->GetRelayTag () && !ret.count (session.get ()) && else if (s.second->GetRemoteIdentity ())
session->GetState () == eSessionStateEstablished && excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ());
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
} }
); if ((int)ret.size () > maxNumIntroducers)
if (session)
{ {
ret.insert (session.get ()); // shink ret randomly
break; int sz = ret.size () - maxNumIntroducers;
for (int i = 0; i < sz; i++)
{
auto ind = rand () % ret.size ();
auto it = ret.begin ();
std::advance (it, ind);
ret.erase (it);
} }
} }
return ret; return ret;
@ -658,21 +682,38 @@ namespace transport
m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1)); this, std::placeholders::_1, true));
} }
void SSUServer::ScheduleIntroducersUpdateTimer () void SSUServer::ScheduleIntroducersUpdateTimer ()
{ {
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1)); this, std::placeholders::_1, true));
} }
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode) void SSUServer::RescheduleIntroducersUpdateTimerV6 ()
{
m_IntroducersUpdateTimerV6.cancel ();
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}
void SSUServer::ScheduleIntroducersUpdateTimerV6 ()
{
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4)
{ {
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
{ {
// timeout expired // timeout expired
if (v4)
{
if (i2p::context.GetStatus () == eRouterStatusTesting) if (i2p::context.GetStatus () == eRouterStatusTesting)
{ {
// we still don't know if we need introducers // we still don't know if we need introducers
@ -686,28 +727,72 @@ namespace transport
return; return;
} }
// we are firewalled // we are firewalled
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4
}
else
{
if (i2p::context.GetStatusV6 () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimerV6 ();
return;
}
if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled)
{
// we don't need introducers
m_IntroducersV6.clear ();
return;
}
// we are firewalled
auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address ();
if (addr && addr->ssu && addr->ssu->introducers.empty ())
i2p::context.SetUnreachable (false, true); // v6
}
std::list<boost::asio::ip::udp::endpoint> newList; std::list<boost::asio::ip::udp::endpoint> newList;
size_t numIntroducers = 0; size_t numIntroducers = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch (); uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (const auto& it : m_Introducers) std::set<i2p::data::IdentHash> excluded;
auto& introducers = v4 ? m_Introducers : m_IntroducersV6;
for (const auto& it : introducers)
{ {
auto session = FindSession (it); auto session = FindSession (it);
if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) if (session)
{ {
if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION)
session->SendKeepAlive (); session->SendKeepAlive ();
if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
{
newList.push_back (it); newList.push_back (it);
numIntroducers++; numIntroducers++;
if (session->GetRemoteIdentity ())
excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ());
} }
else else
session = nullptr;
}
if (!session)
i2p::context.RemoveIntroducer (it); i2p::context.RemoveIntroducer (it);
} }
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
{ {
// create new // create new
auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates
for (const auto& it1: introducers) if (sessions.empty () && !introducers.empty ())
{
// bump creation time for previous introducers if no new sessions found
LogPrint (eLogDebug, "SSU: no new introducers found. Trying to reuse existing");
for (const auto& it : introducers)
{
auto session = FindSession (it);
if (session)
session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION);
}
// try again
excluded.clear ();
sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded);
}
for (const auto& it1: sessions)
{ {
const auto& ep = it1->GetRemoteEndpoint (); const auto& ep = it1->GetRemoteEndpoint ();
i2p::data::RouterInfo::Introducer introducer; i2p::data::RouterInfo::Introducer introducer;
@ -715,36 +800,46 @@ namespace transport
introducer.iPort = ep.port (); introducer.iPort = ep.port ();
introducer.iTag = it1->GetRelayTag (); introducer.iTag = it1->GetRelayTag ();
introducer.iKey = it1->GetIntroKey (); introducer.iKey = it1->GetIntroKey ();
introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION;
if (i2p::context.AddIntroducer (introducer)) if (i2p::context.AddIntroducer (introducer))
{ {
newList.push_back (ep); newList.push_back (ep);
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
} }
if (it1->GetRemoteIdentity ())
excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ());
} }
} }
m_Introducers = newList; introducers = newList;
if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) if (introducers.size () < SSU_MAX_NUM_INTRODUCERS)
{ {
std::set<std::shared_ptr<const i2p::data::RouterInfo> > requested; for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++)
for (auto i = m_Introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++)
{ {
auto introducer = i2p::data::netdb.GetRandomIntroducer (); auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded);
if (introducer && !requested.count (introducer)) // not requested already if (introducer)
{ {
auto address = introducer->GetSSUAddress (true); // v4 auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address ();
if (address && !address->host.is_unspecified ()) if (address && !address->host.is_unspecified () && address->port)
{ {
boost::asio::ip::udp::endpoint ep (address->host, address->port); boost::asio::ip::udp::endpoint ep (address->host, address->port);
if (std::find (m_Introducers.begin (), m_Introducers.end (), ep) == m_Introducers.end ()) // not connected yet if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet
{ {
CreateDirectSession (introducer, ep, false); CreateDirectSession (introducer, ep, false);
requested.insert (introducer); excluded.insert (introducer->GetIdentHash ());
} }
} }
} }
else
{
LogPrint (eLogDebug, "SSU: can't find more introducers");
break;
} }
} }
}
if (v4)
ScheduleIntroducersUpdateTimer (); ScheduleIntroducersUpdateTimer ();
else
ScheduleIntroducersUpdateTimerV6 ();
} }
} }

View File

@ -31,6 +31,7 @@ namespace transport
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes
const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
const size_t SSU_MAX_NUM_INTRODUCERS = 3; const size_t SSU_MAX_NUM_INTRODUCERS = 3;
const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K
@ -51,11 +52,10 @@ namespace transport
~SSUServer (); ~SSUServer ();
void Start (); void Start ();
void Stop (); void Stop ();
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false, bool v4only = false); bool CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false, bool v4only = false);
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest = false); std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest = false);
void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
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> FindSession (const boost::asio::ip::udp::endpoint& e) const;
std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded); std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded);
std::shared_ptr<SSUSession> GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded); std::shared_ptr<SSUSession> GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded);
@ -71,6 +71,7 @@ namespace transport
void RemoveRelay (uint32_t tag); void RemoveRelay (uint32_t tag);
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag); std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
void RescheduleIntroducersUpdateTimer (); void RescheduleIntroducersUpdateTimer ();
void RescheduleIntroducersUpdateTimerV6 ();
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr); void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
@ -99,9 +100,10 @@ namespace transport
template<typename Filter> template<typename Filter>
std::shared_ptr<SSUSession> GetRandomV6Session (Filter filter); std::shared_ptr<SSUSession> GetRandomV6Session (Filter filter);
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers); std::list<std::shared_ptr<SSUSession> > FindIntroducers (int maxNumIntroducers, bool v4, std::set<i2p::data::IdentHash>& excluded);
void ScheduleIntroducersUpdateTimer (); void ScheduleIntroducersUpdateTimer ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); void ScheduleIntroducersUpdateTimerV6 ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4);
void SchedulePeerTestsCleanupTimer (); void SchedulePeerTestsCleanupTimer ();
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
@ -127,9 +129,9 @@ namespace transport
boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6;
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6;
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6,
m_TerminationTimer, m_TerminationTimerV6; m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6;
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to std::list<boost::asio::ip::udp::endpoint> m_Introducers, m_IntroducersV6; // introducers we are connected to
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6; std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6;
std::map<uint32_t, std::shared_ptr<SSUSession> > m_Relays; // we are introducer std::map<uint32_t, std::shared_ptr<SSUSession> > m_Relays; // we are introducer
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2020, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -120,7 +120,8 @@ namespace transport
else else
{ {
// try own intro key // try own intro key
auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () :
i2p::context.GetRouterInfo ().GetSSUAddress (true);
if (!address) if (!address)
{ {
LogPrint (eLogInfo, "SSU is not supported"); LogPrint (eLogInfo, "SSU is not supported");
@ -212,7 +213,7 @@ namespace transport
{ {
uint8_t extendedOptionsLen = buf[headerSize]; uint8_t extendedOptionsLen = buf[headerSize];
headerSize++; headerSize++;
if (extendedOptionsLen >= 3) // options are presented if (extendedOptionsLen >= 2) // options are presented
{ {
uint16_t flags = bufbe16toh (buf + headerSize); uint16_t flags = bufbe16toh (buf + headerSize);
sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG;
@ -257,27 +258,14 @@ namespace transport
s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x
s.Insert (y, 256); // y s.Insert (y, 256); // y
payload += 256; payload += 256;
uint8_t addressSize = *payload;
payload += 1; // size
uint8_t * ourAddress = payload;
boost::asio::ip::address ourIP; boost::asio::ip::address ourIP;
if (addressSize == 4) // v4 uint16_t ourPort = 0;
{ auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort);
boost::asio::ip::address_v4::bytes_type bytes; if (!addressAndPortLen) return;
memcpy (bytes.data (), ourAddress, 4); uint8_t * ourAddressAndPort = payload + 1;
ourIP = boost::asio::ip::address_v4 (bytes); payload += addressAndPortLen;
} addressAndPortLen--; // -1 byte address size
else // v6 s.Insert (ourAddressAndPort, addressAndPortLen); // address + port
{
boost::asio::ip::address_v6::bytes_type bytes;
memcpy (bytes.data (), ourAddress, 16);
ourIP = boost::asio::ip::address_v6 (bytes);
}
s.Insert (ourAddress, addressSize); // our IP
payload += addressSize; // address
uint16_t ourPort = bufbe16toh (payload);
s.Insert (payload, 2); // our port
payload += 2; // port
if (m_RemoteEndpoint.address ().is_v4 ()) if (m_RemoteEndpoint.address ().is_v4 ())
s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4
else else
@ -286,7 +274,7 @@ namespace transport
s.Insert (payload, 8); // relayTag and signed on time s.Insert (payload, 8); // relayTag and signed on time
m_RelayTag = bufbe32toh (payload); m_RelayTag = bufbe32toh (payload);
payload += 4; // relayTag payload += 4; // relayTag
if (i2p::context.GetStatus () == eRouterStatusTesting) if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting)
{ {
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
uint32_t signedOnTime = bufbe32toh(payload); uint32_t signedOnTime = bufbe32toh(payload);
@ -308,8 +296,16 @@ namespace transport
if (s.Verify (m_RemoteIdentity, payload)) if (s.Verify (m_RemoteIdentity, payload))
{ {
LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort);
if (!i2p::util::net::IsInReservedRange (ourIP))
{
i2p::context.UpdateAddress (ourIP); i2p::context.UpdateAddress (ourIP);
SendSessionConfirmed (y, ourAddress, addressSize + 2); SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen);
}
else
{
LogPrint (eLogError, "SSU: Wrong external address ", ourIP.to_string ());
Failed ();
}
} }
else else
{ {
@ -325,12 +321,17 @@ namespace transport
auto headerSize = GetSSUHeaderSize (buf); auto headerSize = GetSSUHeaderSize (buf);
if (headerSize >= len) if (headerSize >= len)
{ {
LogPrint (eLogError, "SSU: Session confirmed header size ", len, " exceeds packet length ", len); LogPrint (eLogError, "SSU: Session confirmed header size ", headerSize, " exceeds packet length ", len);
return; return;
} }
const uint8_t * payload = buf + headerSize; const uint8_t * payload = buf + headerSize;
payload++; // identity fragment info payload++; // identity fragment info
uint16_t identitySize = bufbe16toh (payload); uint16_t identitySize = bufbe16toh (payload);
if (identitySize + headerSize + 7 > len) // 7 = fragment info + fragment size + signed on time
{
LogPrint (eLogError, "SSU: Session confirmed identity size ", identitySize, " exceeds packet length ", len);
return;
}
payload += 2; // size of identity fragment payload += 2; // size of identity fragment
auto identity = std::make_shared<i2p::data::IdentityEx> (payload, identitySize); auto identity = std::make_shared<i2p::data::IdentityEx> (payload, identitySize);
auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already
@ -348,10 +349,15 @@ namespace transport
if (m_SignedData) if (m_SignedData)
m_SignedData->Insert (payload, 4); // insert Alice's signed on time m_SignedData->Insert (payload, 4); // insert Alice's signed on time
payload += 4; // signed-on time payload += 4; // signed-on time
size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen ();
paddingSize &= 0x0F; // %16 size_t paddingSize = fullSize & 0x0F; // %16
if (paddingSize > 0) paddingSize = 16 - paddingSize; if (paddingSize > 0) paddingSize = 16 - paddingSize;
payload += paddingSize; payload += paddingSize;
if (fullSize + paddingSize > len)
{
LogPrint (eLogError, "SSU: Session confirmed message is too short ", len);
return;
}
// verify signature // verify signature
if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload))
{ {
@ -371,7 +377,9 @@ namespace transport
uint8_t * payload = buf + sizeof (SSUHeader); uint8_t * payload = buf + sizeof (SSUHeader);
uint8_t flag = 0; uint8_t flag = 0;
// fill extended options, 3 bytes extended options don't change message size // fill extended options, 3 bytes extended options don't change message size
if (i2p::context.GetStatus () == eRouterStatusOK) // we don't need relays bool isV4 = m_RemoteEndpoint.address ().is_v4 ();
if ((isV4 && i2p::context.GetStatus () == eRouterStatusOK) ||
(!isV4 && i2p::context.GetStatusV6 () == eRouterStatusOK)) // we don't need relays
{ {
// tell out peer to now assign relay tag // tell out peer to now assign relay tag
flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED;
@ -382,7 +390,6 @@ namespace transport
} }
// fill payload // fill payload
memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x
bool isV4 = m_RemoteEndpoint.address ().is_v4 ();
if (isV4) if (isV4)
{ {
payload[256] = 4; payload[256] = 4;
@ -402,7 +409,8 @@ namespace transport
void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce)
{ {
auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () :
i2p::context.GetRouterInfo ().GetSSUAddress (true);
if (!address) if (!address)
{ {
LogPrint (eLogInfo, "SSU is not supported"); LogPrint (eLogInfo, "SSU is not supported");
@ -430,6 +438,7 @@ namespace transport
else else
FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey);
m_Server.Send (buf, 96, m_RemoteEndpoint); m_Server.Send (buf, 96, m_RemoteEndpoint);
LogPrint (eLogDebug, "SSU: relay request sent");
} }
void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag)
@ -475,7 +484,7 @@ namespace transport
else else
s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6
s.Insert<uint16_t> (htobe16 (address->port)); // our port s.Insert<uint16_t> (htobe16 (address->port)); // our port
if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer (!IsV6 ()))
{ {
RAND_bytes((uint8_t *)&m_SentRelayTag, 4); RAND_bytes((uint8_t *)&m_SentRelayTag, 4);
if (!m_SentRelayTag) m_SentRelayTag = 1; if (!m_SentRelayTag) m_SentRelayTag = 1;
@ -579,22 +588,33 @@ namespace transport
void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to)
{ {
// Charlie's address always v4 bool isV4 = to.address ().is_v4 (); // Charle's
if (!to.address ().is_v4 ()) bool isV4A = from.address ().is_v4 (); // Alice's
if ((isV4 && !isV4A) || (!isV4 && isV4A))
{ {
LogPrint (eLogWarning, "SSU: Charlie's IP must be v4"); LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay response");
return; return;
} }
uint8_t buf[80 + 18] = {0}; // 64 Alice's ipv4 and 80 Alice's ipv6 uint8_t buf[80 + 18] = {0}; // 64 for ipv4 and 80 for ipv6
uint8_t * payload = buf + sizeof (SSUHeader); uint8_t * payload = buf + sizeof (SSUHeader);
// Charlie
if (isV4)
{
*payload = 4; *payload = 4;
payload++; // size payload++; // size
htobe32buf (payload, to.address ().to_v4 ().to_ulong ()); // Charlie's IP memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4
payload += 4; // address payload += 4; // address
}
else
{
*payload = 16;
payload++; // size
memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6
payload += 16; // address
}
htobe16buf (payload, to.port ()); // Charlie's port htobe16buf (payload, to.port ()); // Charlie's port
payload += 2; // port payload += 2; // port
// Alice // Alice
bool isV4 = from.address ().is_v4 (); // Alice's
if (isV4) if (isV4)
{ {
*payload = 4; *payload = 4;
@ -633,57 +653,59 @@ namespace transport
void SSUSession::SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from) void SSUSession::SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from)
{ {
if (!session) return; if (!session) return;
// Alice's address always v4 bool isV4 = from.address ().is_v4 (); // Alice's
if (!from.address ().is_v4 ()) bool isV4C = session->m_RemoteEndpoint.address ().is_v4 (); // Charlie's
if ((isV4 && !isV4C) || (!isV4 && isV4C))
{ {
LogPrint (eLogWarning, "SSU: Alice's IP must be v4"); LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay intro");
return; return;
} }
uint8_t buf[48 + 18] = {0}; uint8_t buf[64 + 18] = {0}; // 48 for ipv4 and 64 for ipv6
uint8_t * payload = buf + sizeof (SSUHeader); uint8_t * payload = buf + sizeof (SSUHeader);
if (isV4)
{
*payload = 4; *payload = 4;
payload++; // size payload++; // size
htobe32buf (payload, from.address ().to_v4 ().to_ulong ()); // Alice's IP memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4
payload += 4; // address payload += 4; // address
}
else
{
*payload = 16;
payload++; // size
memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6
payload += 16; // address
}
htobe16buf (payload, from.port ()); // Alice's port htobe16buf (payload, from.port ()); // Alice's port
payload += 2; // port payload += 2; // port
*payload = 0; // challenge size *payload = 0; // challenge size
uint8_t iv[16]; uint8_t iv[16];
RAND_bytes (iv, 16); // random iv RAND_bytes (iv, 16); // random iv
FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey);
m_Server.Send (buf, 48, session->m_RemoteEndpoint); m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint);
LogPrint (eLogDebug, "SSU: relay intro sent"); LogPrint (eLogDebug, "SSU: relay intro sent");
} }
void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len)
{ {
LogPrint (eLogDebug, "SSU message: Relay response received"); LogPrint (eLogDebug, "SSU message: Relay response received");
uint8_t remoteSize = *buf; boost::asio::ip::address remoteIP;
buf++; // remote size uint16_t remotePort = 0;
boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort);
buf += remoteSize; // remote address if (!remoteSize) return;
uint16_t remotePort = bufbe16toh (buf); buf += remoteSize; len -= remoteSize;
buf += 2; // remote port
uint8_t ourSize = *buf;
buf++; // our size
boost::asio::ip::address ourIP; boost::asio::ip::address ourIP;
if (ourSize == 4) uint16_t ourPort = 0;
{ auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort);
boost::asio::ip::address_v4::bytes_type bytes; if (!ourSize) return;
memcpy (bytes.data (), buf, 4); buf += ourSize; len -= ourSize;
ourIP = boost::asio::ip::address_v4 (bytes);
}
else
{
boost::asio::ip::address_v6::bytes_type bytes;
memcpy (bytes.data (), buf, 16);
ourIP = boost::asio::ip::address_v6 (bytes);
}
buf += ourSize; // our address
uint16_t ourPort = bufbe16toh (buf);
buf += 2; // our port
LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort);
if (!i2p::util::net::IsInReservedRange (ourIP))
i2p::context.UpdateAddress (ourIP); i2p::context.UpdateAddress (ourIP);
else
LogPrint (eLogWarning, "SSU: Wrong external address ", ourIP.to_string ());
if (ourIP.is_v4 ())
{
if (ourPort != m_Server.GetPort ()) if (ourPort != m_Server.GetPort ())
{ {
if (i2p::context.GetStatus () == eRouterStatusTesting) if (i2p::context.GetStatus () == eRouterStatusTesting)
@ -691,6 +713,7 @@ namespace transport
} }
else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT)
i2p::context.SetStatus (eRouterStatusTesting); i2p::context.SetStatus (eRouterStatusTesting);
}
uint32_t nonce = bufbe32toh (buf); uint32_t nonce = bufbe32toh (buf);
buf += 4; // nonce buf += 4; // nonce
auto it = m_RelayRequests.find (nonce); auto it = m_RelayRequests.find (nonce);
@ -703,12 +726,16 @@ namespace transport
// we didn't have correct endpoint when sent relay request // we didn't have correct endpoint when sent relay request
// now we do // now we do
LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint);
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable if ((remoteIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) ||
(remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled))
m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch
// we assume that HolePunch has been sent by this time and our SessionRequest will go through
m_Server.CreateDirectSession (it->second, remoteEndpoint, false); m_Server.CreateDirectSession (it->second, remoteEndpoint, false);
} }
// delete request // delete request
m_RelayRequests.erase (it); m_RelayRequests.erase (it);
// cancel connect timer
m_ConnectTimer.cancel ();
} }
else else
LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce);
@ -716,18 +743,12 @@ namespace transport
void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len)
{ {
uint8_t size = *buf; boost::asio::ip::address ip;
if (size == 4) uint16_t port = 0;
{ ExtractIPAddressAndPort (buf, len, ip, port);
buf++; // size if (!ip.is_unspecified () && port)
boost::asio::ip::address_v4 address (bufbe32toh (buf));
buf += 4; // address
uint16_t port = bufbe16toh (buf);
// send hole punch of 0 bytes // send hole punch of 0 bytes
m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port));
}
else
LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported");
} }
void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len,
@ -986,15 +1007,15 @@ namespace transport
void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint)
{ {
uint32_t nonce = bufbe32toh (buf); // 4 bytes uint32_t nonce = bufbe32toh (buf); // 4 bytes
uint8_t size = buf[4]; // 1 byte boost::asio::ip::address addr; // Alice's addresss
const uint8_t * address = buf + 5; // big endian, size bytes uint16_t port = 0; // and port
uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port);
const uint8_t * introKey = buf + size + 7; if (port && (size != 7) && (size != 19))
if (port && (size != 4) && (size != 16))
{ {
LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported");
return; return;
} }
const uint8_t * introKey = buf + 4 + size;
switch (m_Server.GetPeerTestParticipant (nonce)) switch (m_Server.GetPeerTestParticipant (nonce))
{ {
// existing test // existing test
@ -1003,7 +1024,15 @@ namespace transport
if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob
{ {
LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice");
if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK if (IsV6 ())
{
if (i2p::context.GetStatusV6 () == eRouterStatusTesting)
{
i2p::context.SetStatusV6 (eRouterStatusFirewalled);
m_Server.RescheduleIntroducersUpdateTimerV6 ();
}
}
else if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK
{ {
i2p::context.SetStatus (eRouterStatusFirewalled); i2p::context.SetStatus (eRouterStatusFirewalled);
m_Server.RescheduleIntroducersUpdateTimer (); m_Server.RescheduleIntroducersUpdateTimer ();
@ -1014,6 +1043,9 @@ namespace transport
LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice");
if (m_State == eSessionStateEstablished) if (m_State == eSessionStateEstablished)
LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice"); LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice");
if (IsV6 ())
i2p::context.SetStatusV6 (eRouterStatusOK);
else
i2p::context.SetStatus (eRouterStatusOK); i2p::context.SetStatus (eRouterStatusOK);
m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2);
SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie
@ -1028,6 +1060,9 @@ namespace transport
{ {
// peer test successive // peer test successive
LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice");
if (IsV6 ())
i2p::context.SetStatusV6 (eRouterStatusOK);
else
i2p::context.SetStatus (eRouterStatusOK); i2p::context.SetStatus (eRouterStatusOK);
m_Server.RemovePeerTest (nonce); m_Server.RemovePeerTest (nonce);
} }
@ -1060,20 +1095,7 @@ namespace transport
LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie");
m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie);
Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob
boost::asio::ip::address addr; // Alice's address SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob
if (size == 4) // v4
{
boost::asio::ip::address_v4::bytes_type bytes;
memcpy (bytes.data (), address, 4);
addr = boost::asio::ip::address_v4 (bytes);
}
else // v6
{
boost::asio::ip::address_v6::bytes_type bytes;
memcpy (bytes.data (), address, 16);
addr = boost::asio::ip::address_v6 (bytes);
}
SendPeerTest (nonce, addr, be16toh (port), introKey); // to Alice with her address received from Bob
} }
else else
{ {
@ -1130,7 +1152,8 @@ namespace transport
if (toAddress) if (toAddress)
{ {
// send our intro key to address instead of its own // send our intro key to address instead of its own
auto addr = i2p::context.GetRouterInfo ().GetSSUAddress (); auto addr = address.is_v4 () ? i2p::context.GetRouterInfo ().GetSSUAddress (true) : // ipv4
i2p::context.GetRouterInfo ().GetSSUV6Address ();
if (addr) if (addr)
memcpy (payload, addr->ssu->key, 32); // intro key memcpy (payload, addr->ssu->key, 32); // intro key
else else
@ -1160,7 +1183,7 @@ namespace transport
{ {
// we are Alice // we are Alice
LogPrint (eLogDebug, "SSU: sending peer test"); LogPrint (eLogDebug, "SSU: sending peer test");
auto address = i2p::context.GetRouterInfo ().GetSSUAddress (i2p::context.SupportsV4 ()); auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true);
if (!address) if (!address)
{ {
LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); LogPrint (eLogInfo, "SSU is not supported. Can't send peer test");
@ -1233,5 +1256,36 @@ namespace transport
i2p::transport::transports.UpdateSentBytes (size); i2p::transport::transports.UpdateSentBytes (size);
m_Server.Send (buf, size, m_RemoteEndpoint); m_Server.Send (buf, size, m_RemoteEndpoint);
} }
size_t SSUSession::ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port)
{
if (!len) return 0;
uint8_t size = *buf;
size_t s = 1 + size + 2; // size + address + port
if (len < s)
{
LogPrint (eLogWarning, "SSU: Address is too short ", len);
port = 0;
return len;
}
buf++; // size
if (size == 4)
{
boost::asio::ip::address_v4::bytes_type bytes;
memcpy (bytes.data (), buf, 4);
ip = boost::asio::ip::address_v4 (bytes);
}
else if (size == 16)
{
boost::asio::ip::address_v6::bytes_type bytes;
memcpy (bytes.data (), buf, 16);
ip = boost::asio::ip::address_v6 (bytes);
}
else
LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported");
buf += size;
port = bufbe16toh (buf);
return s;
}
} }
} }

View File

@ -102,6 +102,7 @@ namespace transport
uint32_t GetRelayTag () const { return m_RelayTag; }; uint32_t GetRelayTag () const { return m_RelayTag; };
const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; };
uint32_t GetCreationTime () const { return m_CreationTime; }; uint32_t GetCreationTime () const { return m_CreationTime; };
void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers
void FlushData (); void FlushData ();
@ -145,6 +146,8 @@ namespace transport
void Reset (); void Reset ();
static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size
private: private:
friend class SSUData; // TODO: change in later friend class SSUData; // TODO: change in later

View File

@ -942,15 +942,26 @@ namespace stream
{ {
if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ())
{ {
m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());
if (!m_RemoteLeaseSet) if (!remoteLeaseSet)
{ {
LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found");
if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ())
{
m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet (
std::make_shared<i2p::data::BlindedPublicKey>(m_RemoteIdentity));
return; // we keep m_RemoteLeaseSet for possible next request
}
else
{
m_RemoteLeaseSet = nullptr;
m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt
} }
}
else else
{ {
// LeaseSet updated // LeaseSet updated
m_RemoteLeaseSet = remoteLeaseSet;
m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity ();
m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier ();
} }

View File

@ -137,7 +137,7 @@ namespace transport
m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr),
m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
m_SSUServer (nullptr), m_NTCP2Server (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr),
m_X25519KeysPairSupplier (5), // 5 pre-generated keys m_X25519KeysPairSupplier (15), // 15 pre-generated keys
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0),
@ -505,7 +505,7 @@ namespace transport
} }
if (address && address->IsReachableSSU ()) if (address && address->IsReachableSSU ())
{ {
m_SSUServer->CreateSession (peer.router, address); if (m_SSUServer->CreateSession (peer.router, address))
return true; return true;
} }
} }
@ -576,67 +576,65 @@ namespace transport
return; return;
} }
if (m_SSUServer) if (m_SSUServer)
{ PeerTest ();
bool isv4 = i2p::context.SupportsV4 ();
if (m_IsNAT && isv4)
i2p::context.SetStatus (eRouterStatusTesting);
for (int i = 0; i < 5; i++)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4
if (router)
m_SSUServer->CreateSession (router, true, isv4); // peer test
else
{
// if not peer test capable routers found pick any
router = i2p::data::netdb.GetRandomRouter ();
if (router && router->IsSSU ())
m_SSUServer->CreateSession (router); // no peer test
}
}
if (i2p::context.SupportsV6 ())
{
// try to connect to few v6 addresses to get our address back
for (int i = 0; i < 3; i++)
{
auto router = i2p::data::netdb.GetRandomSSUV6Router ();
if (router)
{
auto addr = router->GetSSUV6Address ();
if (addr)
m_SSUServer->GetService ().post ([this, router, addr]
{
m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false);
});
}
}
}
}
else else
LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available");
} }
void Transports::PeerTest () void Transports::PeerTest (bool ipv4, bool ipv6)
{ {
if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return; if (RoutesRestricted() || !m_SSUServer) return;
if (m_SSUServer) if (ipv4 && i2p::context.SupportsV4 ())
{ {
LogPrint (eLogInfo, "Transports: Started peer test"); LogPrint (eLogInfo, "Transports: Started peer test ipv4");
std::set<i2p::data::IdentHash> excluded;
bool statusChanged = false; bool statusChanged = false;
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
auto router = i2p::data::netdb.GetRandomPeerTestRouter (true); // v4 only auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4
if (router) if (router)
{
auto addr = router->GetSSUAddress (true); // ipv4
if (addr && !i2p::util::net::IsInReservedRange(addr->host))
{ {
if (!statusChanged) if (!statusChanged)
{ {
statusChanged = true; statusChanged = true;
i2p::context.SetStatus (eRouterStatusTesting); // first time only i2p::context.SetStatus (eRouterStatusTesting); // first time only
} }
m_SSUServer->CreateSession (router, true, true); // peer test v4 m_SSUServer->CreateSession (router, addr, true); // peer test v4
}
excluded.insert (router->GetIdentHash ());
} }
} }
if (!statusChanged) if (!statusChanged)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test"); LogPrint (eLogWarning, "Transports: Can't find routers for peer test ipv4");
}
if (ipv6 && i2p::context.SupportsV6 ())
{
LogPrint (eLogInfo, "Transports: Started peer test ipv6");
std::set<i2p::data::IdentHash> excluded;
bool statusChanged = false;
for (int i = 0; i < 5; i++)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6
if (router)
{
auto addr = router->GetSSUV6Address ();
if (addr && !i2p::util::net::IsInReservedRange(addr->host))
{
if (!statusChanged)
{
statusChanged = true;
i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only
}
m_SSUServer->CreateSession (router, addr, true); // peer test v6
}
excluded.insert (router->GetIdentHash ());
}
}
if (!statusChanged)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test ipv6");
} }
} }
@ -752,9 +750,12 @@ namespace transport
++it; ++it;
} }
UpdateBandwidth (); // TODO: use separate timer(s) for it UpdateBandwidth (); // TODO: use separate timer(s) for it
if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting;
DetectExternalIP (); bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting;
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); // if still testing, repeat peer test
if (ipv4Testing || ipv6Testing)
PeerTest (ipv4Testing, ipv6Testing);
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
} }
} }
@ -772,10 +773,15 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
{ {
if (m_Peers.empty ()) return nullptr; if (m_Peers.empty ()) return nullptr;
i2p::data::IdentHash ident;
{
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.begin (); auto it = m_Peers.begin ();
std::advance (it, rand () % m_Peers.size ()); std::advance (it, rand () % m_Peers.size ());
return it != m_Peers.end () ? it->second.router : nullptr; if (it == m_Peers.end () || it->second.router) return nullptr; // not connected
ident = it->first;
}
return i2p::data::netdb.FindRouter (ident);
} }
void Transports::RestrictRoutesToFamilies(std::set<std::string> families) void Transports::RestrictRoutesToFamilies(std::set<std::string> families)
{ {

View File

@ -76,9 +76,9 @@ namespace transport
} }
}; };
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds const size_t SESSION_CREATION_TIMEOUT = 15; // in seconds
const int PEER_TEST_INTERVAL = 71; // in minutes const int PEER_TEST_INTERVAL = 71; // in minutes
const int MAX_NUM_DELAYED_MESSAGES = 50; const int MAX_NUM_DELAYED_MESSAGES = 150;
class Transports class Transports
{ {
public: public:
@ -131,7 +131,7 @@ namespace transport
bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
void PeerTest (); void PeerTest (bool ipv4 = true, bool ipv6 = true);
void SetCheckReserved (bool check) { m_CheckReserved = check; }; void SetCheckReserved (bool check) { m_CheckReserved = check; };
bool IsCheckReserved () { return m_CheckReserved; }; bool IsCheckReserved () { return m_CheckReserved; };

View File

@ -406,6 +406,7 @@ namespace tunnel
bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop)
{ {
int start = 0;
auto prevHop = i2p::context.GetSharedRouterInfo (); auto prevHop = i2p::context.GetSharedRouterInfo ();
if(i2p::transport::transports.RoutesRestricted()) if(i2p::transport::transports.RoutesRestricted())
{ {
@ -414,20 +415,22 @@ namespace tunnel
if(!hop) return false; if(!hop) return false;
peers.push_back(hop->GetRouterIdentity()); peers.push_back(hop->GetRouterIdentity());
prevHop = hop; prevHop = hop;
start++;
} }
else if (i2p::transport::transports.GetNumPeers () > 25) else if (i2p::transport::transports.GetNumPeers () > 100 ||
(inbound && i2p::transport::transports.GetNumPeers () > 25))
{ {
auto r = i2p::transport::transports.GetRandomPeer (); auto r = i2p::transport::transports.GetRandomPeer ();
if (r && !r->GetProfile ()->IsBad () && if (r && !r->GetProfile ()->IsBad () &&
(numHops > 1 || (!inbound && r->IsV4 ()) || r->IsReachable ())) // first inbound must be reachable (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable
{ {
prevHop = r; prevHop = r;
peers.push_back (r->GetRouterIdentity ()); peers.push_back (r->GetRouterIdentity ());
numHops--; start++;
} }
} }
for(int i = 0; i < numHops; i++ ) for(int i = start; i < numHops; i++ )
{ {
auto hop = nextHop (prevHop, inbound); auto hop = nextHop (prevHop, inbound);
if (!hop && !i) // if no suitable peer found for first hop, try already connected if (!hop && !i) // if no suitable peer found for first hop, try already connected
@ -440,9 +443,8 @@ namespace tunnel
LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ());
return false; return false;
} }
if ((i == numHops - 1) && if ((i == numHops - 1) && (!hop->IsV4 () || // doesn't support ipv4
((inbound && !hop->IsReachable ()) || // IBGW is not reachable (inbound && !hop->IsReachable ()))) // IBGW is not reachable
(!inbound && !hop->IsV4 ()))) // OBEP is not ipv4
{ {
auto hop1 = nextHop (prevHop, true); auto hop1 = nextHop (prevHop, true);
if (hop1) hop = hop1; if (hop1) hop = hop1;

View File

@ -524,6 +524,7 @@ namespace net
bool IsInReservedRange (const boost::asio::ip::address& host) bool IsInReservedRange (const boost::asio::ip::address& host)
{ {
// https://en.wikipedia.org/wiki/Reserved_IP_addresses // https://en.wikipedia.org/wiki/Reserved_IP_addresses
if (host.is_unspecified ()) return false;
if(host.is_v4()) if(host.is_v4())
{ {
static const std::vector< std::pair<uint32_t, uint32_t> > reservedIPv4Ranges { static const std::vector< std::pair<uint32_t, uint32_t> > reservedIPv4Ranges {

View File

@ -16,7 +16,7 @@
#define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c)
#define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MAJOR 2
#define I2PD_VERSION_MINOR 37 #define I2PD_VERSION_MINOR 38
#define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_MICRO 0
#define I2PD_VERSION_PATCH 0 #define I2PD_VERSION_PATCH 0
#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO)
@ -30,7 +30,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 49 #define I2P_VERSION_MICRO 50
#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)
#define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)

View File

@ -84,7 +84,12 @@ namespace client
m_Owner->SendMessagePayloadMessage (buf + 4, length); m_Owner->SendMessagePayloadMessage (buf + 4, length);
} }
void I2CPDestination::CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) void I2CPDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
{
GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels));
}
void I2CPDestination::PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
{ {
if (m_IsCreatingLeaseSet) if (m_IsCreatingLeaseSet)
{ {

View File

@ -93,7 +93,7 @@ namespace client
// I2CP // I2CP
void HandleDataMessage (const uint8_t * buf, size_t len); void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels); void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private: private:
@ -101,6 +101,8 @@ namespace client
{ return std::static_pointer_cast<I2CPDestination>(shared_from_this ()); } { return std::static_pointer_cast<I2CPDestination>(shared_from_this ()); }
bool SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote); bool SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
void PostCreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
private: private:
std::shared_ptr<I2CPSession> m_Owner; std::shared_ptr<I2CPSession> m_Owner;

View File

@ -84,7 +84,11 @@ namespace client
// bind to 127.x.x.x address // bind to 127.x.x.x address
// where x.x.x are first three bytes from ident // where x.x.x are first three bytes from ident
auto ourIP = GetLoopbackAddressFor(addr); auto ourIP = GetLoopbackAddressFor(addr);
sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); boost::system::error_code ec;
sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec);
if (ec)
LogPrint (eLogError, "I2PTunnel: can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ());
} }
#endif #endif

View File

@ -58,8 +58,8 @@ namespace client
{ {
if (Session) if (Session)
{ {
if (m_IsAccepting && Session->localDestination) if (m_IsAccepting && Session->GetLocalDestination ())
Session->localDestination->StopAcceptingStreams (); Session->GetLocalDestination ()->StopAcceptingStreams ();
} }
break; break;
} }
@ -270,6 +270,10 @@ namespace client
ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_SESSION_ADD))
ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE))
ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND))
{ {
size_t len = bytes_transferred - (separator - m_Buffer) - 1; size_t len = bytes_transferred - (separator - m_Buffer) - 1;
@ -352,6 +356,7 @@ namespace client
if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream;
else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram;
else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw;
else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster;
if (type == eSAMSessionTypeUnknown) if (type == eSAMSessionTypeUnknown)
{ {
// unknown style // unknown style
@ -409,7 +414,7 @@ namespace client
if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw)
{ {
session->UDPEndpoint = forward; session->UDPEndpoint = forward;
auto dest = session->localDestination->CreateDatagramDestination (); auto dest = session->GetLocalDestination ()->CreateDatagramDestination ();
if (type == eSAMSessionTypeDatagram) if (type == eSAMSessionTypeDatagram)
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
@ -418,7 +423,7 @@ namespace client
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
} }
if (session->localDestination->IsReady ()) if (session->GetLocalDestination ()->IsReady ())
SendSessionCreateReplyOk (); SendSessionCreateReplyOk ();
else else
{ {
@ -438,7 +443,7 @@ namespace client
auto session = m_Owner.FindSession(m_ID); auto session = m_Owner.FindSession(m_ID);
if(session) if(session)
{ {
if (session->localDestination->IsReady ()) if (session->GetLocalDestination ()->IsReady ())
SendSessionCreateReplyOk (); SendSessionCreateReplyOk ();
else else
{ {
@ -457,7 +462,7 @@ namespace client
{ {
uint8_t buf[1024]; uint8_t buf[1024];
char priv[1024]; char priv[1024];
size_t l = session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024); size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024);
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024); size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
priv[l1] = 0; priv[l1] = 0;
#ifdef _MSC_VER #ifdef _MSC_VER
@ -495,21 +500,39 @@ namespace client
else else
m_BufferOffset = 0; m_BufferOffset = 0;
std::shared_ptr<const Address> addr;
if (destination.find(".i2p") != std::string::npos)
addr = context.GetAddressBook().GetAddress (destination);
else
{
auto dest = std::make_shared<i2p::data::IdentityEx> (); auto dest = std::make_shared<i2p::data::IdentityEx> ();
size_t l = dest->FromBase64(destination); size_t l = dest->FromBase64(destination);
if (l > 0) if (l > 0)
{ {
context.GetAddressBook().InsertFullAddress(dest); context.GetAddressBook().InsertFullAddress(dest);
auto leaseSet = session->localDestination->FindLeaseSet(dest->GetIdentHash()); addr = std::make_shared<Address>(dest->GetIdentHash ());
}
}
if (addr && addr->IsValid ())
{
if (addr->IsIdentHash ())
{
auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash);
if (leaseSet) if (leaseSet)
Connect(leaseSet, session); Connect(leaseSet, session);
else else
{ {
session->localDestination->RequestDestination(dest->GetIdentHash(), session->GetLocalDestination ()->RequestDestination(addr->identHash,
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1)); shared_from_this(), std::placeholders::_1));
} }
} }
else // B33
session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey,
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1));
}
else else
SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true);
} }
@ -523,7 +546,7 @@ namespace client
if (session) if (session)
{ {
m_SocketType = eSAMSocketTypeStream; m_SocketType = eSAMSocketTypeStream;
m_Stream = session->localDestination->CreateStream (remote); m_Stream = session->GetLocalDestination ()->CreateStream (remote);
if (m_Stream) if (m_Stream)
{ {
m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send
@ -567,10 +590,10 @@ namespace client
if (session) if (session)
{ {
m_SocketType = eSAMSocketTypeAcceptor; m_SocketType = eSAMSocketTypeAcceptor;
if (!session->localDestination->IsAcceptingStreams ()) if (!session->GetLocalDestination ()->IsAcceptingStreams ())
{ {
m_IsAccepting = true; m_IsAccepting = true;
session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
} }
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
} }
@ -590,7 +613,7 @@ namespace client
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
return; return;
} }
if (session->localDestination->IsAcceptingStreams ()) if (session->GetLocalDestination ()->IsAcceptingStreams ())
{ {
SendI2PError ("Already accepting"); SendI2PError ("Already accepting");
return; return;
@ -620,7 +643,7 @@ namespace client
m_IsAccepting = true; m_IsAccepting = true;
std::string& silent = params[SAM_PARAM_SILENT]; std::string& silent = params[SAM_PARAM_SILENT];
if (silent == SAM_VALUE_TRUE) m_IsSilent = true; if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, session->GetLocalDestination ()->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward,
shared_from_this (), std::placeholders::_1, ep)); shared_from_this (), std::placeholders::_1, ep));
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
} }
@ -636,7 +659,7 @@ namespace client
auto session = m_Owner.FindSession(m_ID); auto session = m_Owner.FindSession(m_ID);
if (session) if (session)
{ {
auto d = session->localDestination->GetDatagramDestination (); auto d = session->GetLocalDestination ()->GetDatagramDestination ();
if (d) if (d)
{ {
i2p::data::IdentityEx dest; i2p::data::IdentityEx dest;
@ -706,7 +729,7 @@ namespace client
std::shared_ptr<const i2p::data::IdentityEx> identity; std::shared_ptr<const i2p::data::IdentityEx> identity;
std::shared_ptr<const Address> addr; std::shared_ptr<const Address> addr;
auto session = m_Owner.FindSession(m_ID); auto session = m_Owner.FindSession(m_ID);
auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->localDestination; auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->GetLocalDestination ();
if (name == "ME") if (name == "ME")
SendNamingLookupReply (name, dest->GetIdentity ()); SendNamingLookupReply (name, dest->GetIdentity ());
else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr)
@ -740,6 +763,73 @@ namespace client
} }
} }
void SAMSocket::ProcessSessionAdd (char * buf, size_t len)
{
auto session = m_Owner.FindSession(m_ID);
if (session && session->Type == eSAMSessionTypeMaster)
{
LogPrint (eLogDebug, "SAM: subsession add: ", buf);
auto masterSession = std::static_pointer_cast<SAMMasterSession>(session);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
if (masterSession->subsessions.count (id) > 1)
{
// session exists
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false);
return;
}
std::string& style = params[SAM_PARAM_STYLE];
SAMSessionType type = eSAMSessionTypeUnknown;
if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream;
// TODO: implement other styles
if (type == eSAMSessionTypeUnknown)
{
// unknown style
SendI2PError("Unsupported STYLE");
return;
}
auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]);
if (fromPort == -1)
{
SendI2PError("Invalid from port");
return;
}
auto subsession = std::make_shared<SAMSubSession>(masterSession, id, type, fromPort);
if (m_Owner.AddSession (subsession))
{
masterSession->subsessions.insert (id);
SendSessionCreateReplyOk ();
}
else
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false);
}
else
SendI2PError ("Wrong session type");
}
void SAMSocket::ProcessSessionRemove (char * buf, size_t len)
{
auto session = m_Owner.FindSession(m_ID);
if (session && session->Type == eSAMSessionTypeMaster)
{
LogPrint (eLogDebug, "SAM: subsession remove: ", buf);
auto masterSession = std::static_pointer_cast<SAMMasterSession>(session);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
if (!masterSession->subsessions.erase (id))
{
SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), false);
return;
}
m_Owner.CloseSession (id);
SendSessionCreateReplyOk ();
}
else
SendI2PError ("Wrong session type");
}
void SAMSocket::SendI2PError(const std::string & msg) void SAMSocket::SendI2PError(const std::string & msg)
{ {
LogPrint (eLogError, "SAM: i2p error ", msg); LogPrint (eLogError, "SAM: i2p error ", msg);
@ -952,7 +1042,7 @@ namespace client
if (it->m_SocketType == eSAMSocketTypeAcceptor) if (it->m_SocketType == eSAMSocketTypeAcceptor)
{ {
it->m_IsAccepting = true; it->m_IsAccepting = true;
session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1));
break; break;
} }
} }
@ -1090,19 +1180,11 @@ namespace client
m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this()));
} }
SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type, std::shared_ptr<ClientDestination> dest): SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type):
m_Bridge(parent), m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr)
localDestination (dest),
UDPEndpoint(nullptr),
Name(id), Type (type)
{ {
} }
SAMSession::~SAMSession ()
{
i2p::client::context.DeleteLocalDestination (localDestination);
}
void SAMSession::CloseStreams () void SAMSession::CloseStreams ()
{ {
for(const auto & itr : m_Bridge.ListSockets(Name)) for(const auto & itr : m_Bridge.ListSockets(Name))
@ -1111,6 +1193,58 @@ namespace client
} }
} }
SAMSingleSession::SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr<ClientDestination> dest):
SAMSession (parent, name, type),
localDestination (dest)
{
}
SAMSingleSession::~SAMSingleSession ()
{
i2p::client::context.DeleteLocalDestination (localDestination);
}
void SAMSingleSession::StopLocalDestination ()
{
localDestination->Release ();
localDestination->StopAcceptingStreams ();
}
void SAMMasterSession::Close ()
{
SAMSingleSession::Close ();
for (const auto& it: subsessions)
m_Bridge.CloseSession (it);
subsessions.clear ();
}
SAMSubSession::SAMSubSession (std::shared_ptr<SAMMasterSession> master, const std::string& name, SAMSessionType type, int port):
SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port)
{
if (Type == eSAMSessionTypeStream)
{
auto d = masterSession->GetLocalDestination ()->CreateStreamingDestination (inPort);
if (d) d->Start ();
}
// TODO: implement datagrams
}
std::shared_ptr<ClientDestination> SAMSubSession::GetLocalDestination ()
{
return masterSession ? masterSession->GetLocalDestination () : nullptr;
}
void SAMSubSession::StopLocalDestination ()
{
auto dest = GetLocalDestination ();
if (dest && Type == eSAMSessionTypeStream)
{
auto d = dest->RemoveStreamingDestination (inPort);
if (d) d->Stop ();
}
// TODO: implement datagrams
}
SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread):
RunnableService ("SAM"), m_IsSingleThread (singleThread), RunnableService ("SAM"), m_IsSingleThread (singleThread),
m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
@ -1156,7 +1290,7 @@ namespace client
{ {
std::unique_lock<std::mutex> l(m_SessionsMutex); std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto& it: m_Sessions) for (auto& it: m_Sessions)
it.second->CloseStreams (); it.second->Close ();
m_Sessions.clear (); m_Sessions.clear ();
} }
StopIOService (); StopIOService ();
@ -1248,7 +1382,8 @@ namespace client
if (localDestination) if (localDestination)
{ {
localDestination->Acquire (); localDestination->Acquire ();
auto session = std::make_shared<SAMSession>(*this, id, type, localDestination); auto session = (type == eSAMSessionTypeMaster) ? std::make_shared<SAMMasterSession>(*this, id, localDestination) :
std::make_shared<SAMSingleSession>(*this, id, type, localDestination);
std::unique_lock<std::mutex> l(m_SessionsMutex); std::unique_lock<std::mutex> l(m_SessionsMutex);
auto ret = m_Sessions.insert (std::make_pair(id, session)); auto ret = m_Sessions.insert (std::make_pair(id, session));
if (!ret.second) if (!ret.second)
@ -1258,6 +1393,13 @@ namespace client
return nullptr; return nullptr;
} }
bool SAMBridge::AddSession (std::shared_ptr<SAMSession> session)
{
if (!session) return false;
auto ret = m_Sessions.emplace (session->Name, session);
return ret.second;
}
void SAMBridge::CloseSession (const std::string& id) void SAMBridge::CloseSession (const std::string& id)
{ {
std::shared_ptr<SAMSession> session; std::shared_ptr<SAMSession> session;
@ -1272,9 +1414,8 @@ namespace client
} }
if (session) if (session)
{ {
session->localDestination->Release (); session->StopLocalDestination ();
session->localDestination->StopAcceptingStreams (); session->Close ();
session->CloseStreams ();
if (m_IsSingleThread) if (m_IsSingleThread)
{ {
auto timer = std::make_shared<boost::asio::deadline_timer>(GetService ()); auto timer = std::make_shared<boost::asio::deadline_timer>(GetService ());
@ -1349,10 +1490,10 @@ namespace client
i2p::data::IdentityEx dest; i2p::data::IdentityEx dest;
dest.FromBase64 (destination); dest.FromBase64 (destination);
if (session->Type == eSAMSessionTypeDatagram) if (session->Type == eSAMSessionTypeDatagram)
session->localDestination->GetDatagramDestination ()-> session->GetLocalDestination ()->GetDatagramDestination ()->
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
else // raw else // raw
session->localDestination->GetDatagramDestination ()-> session->GetLocalDestination ()->GetDatagramDestination ()->
SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
} }
else else

View File

@ -13,6 +13,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <list> #include <list>
#include <set>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
@ -41,6 +42,8 @@ namespace client
const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n"; const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n";
const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n";
const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n"; const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n";
const char SAM_SESSION_ADD[] = "SESSION ADD";
const char SAM_SESSION_REMOVE[] = "SESSION REMOVE";
const char SAM_STREAM_CONNECT[] = "STREAM CONNECT"; const char SAM_STREAM_CONNECT[] = "STREAM CONNECT";
const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
@ -72,10 +75,12 @@ namespace client
const char SAM_PARAM_SIZE[] = "SIZE"; const char SAM_PARAM_SIZE[] = "SIZE";
const char SAM_PARAM_HOST[] = "HOST"; const char SAM_PARAM_HOST[] = "HOST";
const char SAM_PARAM_PORT[] = "PORT"; const char SAM_PARAM_PORT[] = "PORT";
const char SAM_PARAM_FROM_PORT[] = "FROM_PORT";
const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT";
const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_STREAM[] = "STREAM";
const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM";
const char SAM_VALUE_RAW[] = "RAW"; const char SAM_VALUE_RAW[] = "RAW";
const char SAM_VALUE_MASTER[] = "MASTER";
const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_TRUE[] = "true";
const char SAM_VALUE_FALSE[] = "false"; const char SAM_VALUE_FALSE[] = "false";
@ -134,6 +139,8 @@ namespace client
void ProcessStreamForward (char * buf, size_t len); void ProcessStreamForward (char * buf, size_t len);
void ProcessDestGenerate (char * buf, size_t len); void ProcessDestGenerate (char * buf, size_t len);
void ProcessNamingLookup (char * buf, size_t len); void ProcessNamingLookup (char * buf, size_t len);
void ProcessSessionAdd (char * buf, size_t len);
void ProcessSessionRemove (char * buf, size_t len);
void SendI2PError(const std::string & msg); void SendI2PError(const std::string & msg);
size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
void ExtractParams (char * buf, std::map<std::string, std::string>& params); void ExtractParams (char * buf, std::map<std::string, std::string>& params);
@ -171,23 +178,57 @@ namespace client
eSAMSessionTypeUnknown, eSAMSessionTypeUnknown,
eSAMSessionTypeStream, eSAMSessionTypeStream,
eSAMSessionTypeDatagram, eSAMSessionTypeDatagram,
eSAMSessionTypeRaw eSAMSessionTypeRaw,
eSAMSessionTypeMaster
}; };
struct SAMSession struct SAMSession
{ {
SAMBridge & m_Bridge; SAMBridge & m_Bridge;
std::shared_ptr<ClientDestination> localDestination;
std::shared_ptr<boost::asio::ip::udp::endpoint> UDPEndpoint;
std::string Name; std::string Name;
SAMSessionType Type; SAMSessionType Type;
std::shared_ptr<boost::asio::ip::udp::endpoint> UDPEndpoint; // TODO: move
SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr<ClientDestination> dest); SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type);
~SAMSession (); virtual ~SAMSession () {};
virtual std::shared_ptr<ClientDestination> GetLocalDestination () = 0;
virtual void StopLocalDestination () = 0;
virtual void Close () { CloseStreams (); };
void CloseStreams (); void CloseStreams ();
}; };
struct SAMSingleSession: public SAMSession
{
std::shared_ptr<ClientDestination> localDestination;
SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr<ClientDestination> dest);
~SAMSingleSession ();
std::shared_ptr<ClientDestination> GetLocalDestination () { return localDestination; };
void StopLocalDestination ();
};
struct SAMMasterSession: public SAMSingleSession
{
std::set<std::string> subsessions;
SAMMasterSession (SAMBridge & parent, const std::string & name, std::shared_ptr<ClientDestination> dest):
SAMSingleSession (parent, name, eSAMSessionTypeMaster, dest) {};
void Close ();
};
struct SAMSubSession: public SAMSession
{
std::shared_ptr<SAMMasterSession> masterSession;
int inPort;
SAMSubSession (std::shared_ptr<SAMMasterSession> master, const std::string& name, SAMSessionType type, int port);
// implements SAMSession
std::shared_ptr<ClientDestination> GetLocalDestination ();
void StopLocalDestination ();
};
class SAMBridge: private i2p::util::RunnableService class SAMBridge: private i2p::util::RunnableService
{ {
public: public:
@ -201,6 +242,7 @@ namespace client
boost::asio::io_service& GetService () { return GetIOService (); }; boost::asio::io_service& GetService () { return GetIOService (); };
std::shared_ptr<SAMSession> CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient std::shared_ptr<SAMSession> CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient
const std::map<std::string, std::string> * params); const std::map<std::string, std::string> * params);
bool AddSession (std::shared_ptr<SAMSession> session);
void CloseSession (const std::string& id); void CloseSession (const std::string& id);
std::shared_ptr<SAMSession> FindSession (const std::string& id) const; std::shared_ptr<SAMSession> FindSession (const std::string& id) const;