diff --git a/ChangeLog b/ChangeLog index 3b2f40eb..9a79440e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,37 @@ # for this file format description, # 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 ### Added - Address registration line for reg.i2p and stats.i2p through the web console diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 15157355..db5a1f3d 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -142,25 +142,47 @@ namespace win32 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) { s << "\n"; s << "Status: "; - switch (i2p::context.GetStatus()) + ShowNetworkStatus (s, i2p::context.GetStatus ()); + if (i2p::context.SupportsV6 ()) { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - 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 << " / "; + ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; diff --git a/appveyor.yml b/appveyor.yml index cf760242..89d85494 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.37.0.{build} +version: 2.38.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 0c2ec5bc..e8c397f5 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -20,6 +20,7 @@ ## Logging configuration section ## 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) ## * stdout - print log entries to stdout @@ -34,14 +35,30 @@ ## Write full CLF-formatted date and time to log (default: write only time) # 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 ## Specify a family, router belongs to (default - none) # 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 +## 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 ## Port to listen for connections @@ -54,23 +71,9 @@ ipv4 = true ## Enable communication through ipv6 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) # ssu = true -## Should we assume we are behind NAT? (false only in MeshNet) -# nat = true - ## Bandwidth configuration ## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, ## X - unlimited @@ -84,6 +87,7 @@ ipv6 = false # notransit = true ## Router will be floodfill +## Note: that mode uses much more network connections and CPU! # floodfill = true [http] @@ -95,7 +99,7 @@ address = 127.0.0.1 port = 7070 ## Path to web console, default "/" # webroot = / -## Uncomment following lines to enable Web Console authentication +## Uncomment following lines to enable Web Console authentication # auth = true # user = i2pd # pass = changeme @@ -131,7 +135,7 @@ port = 4447 ## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. [sam] -## Uncomment and set to 'true' to enable SAM Bridge +## Comment or set to 'false' to disable SAM Bridge enabled = true ## Address and port service will listen on # address = 127.0.0.1 @@ -171,6 +175,13 @@ enabled = true ## Name i2pd appears in UPnP forwardings list (default = 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] ## Options for bootstrapping into I2P network, aka reseeding ## Enable or disable reseed data verification. @@ -178,6 +189,8 @@ verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # 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 # file = /path/to/i2pseeds.su3 ## or HTTPS URL to reseed from @@ -195,19 +208,15 @@ verify = true ## Default: reg.i2p at "mainline" I2P Network # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt ## 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] ## Maximum active transit sessions (default:2500) # transittunnels = 2500 -## Limit number of open file descriptors (0 - use system limit) +## Limit number of open file descriptors (0 - use system limit) # openfiles = 0 -## Maximum size of corefile in Kb (0 - use system limit) +## Maximum size of corefile in Kb (0 - use system limit) # 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] ## Enable explicit trust options. false by default @@ -215,13 +224,13 @@ verify = true ## Make direct I2P connections only to routers in specified Family. # family = MyFamily ## Make direct I2P connections only to routers specified here. Comma separated list of base64 identities. -# routers = +# routers = ## Should we hide our router from other routers? false by default # hidden = true [exploratory] ## Exploratory tunnels settings with default values -# inbound.length = 2 +# inbound.length = 2 # inbound.quantity = 3 # outbound.length = 2 # outbound.quantity = 3 @@ -229,6 +238,8 @@ verify = true [persist] ## Save peer profiles on disk (default: true) # profiles = true +## Save full addresses on disk (default: true) +# addressbook = true [cpuext] ## Use CPU AES-NI instructions set when work with cryptography when available (default: true) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index e0985eda..1061fc8f 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.37.0 +Version: 2.38.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -137,6 +137,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon May 17 2021 orignal - 2.38.0 +- update to 2.38.0 + * Mon Mar 15 2021 orignal - 2.37.0 - update to 2.37.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 8cb54bba..a2994f28 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.37.0 +Version: 2.38.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -135,6 +135,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon May 17 2021 orignal - 2.38.0 +- update to 2.38.0 + * Mon Mar 15 2021 orignal - 2.37.0 - update to 2.37.0 diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 51783068..77000652 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -142,23 +142,23 @@ namespace util i2p::context.SetNetID (netID); i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET // manual override for meshnet ipv4 = false; ipv6 = true; #endif // ifname -> address - std::string ifname; i2p::config::GetOption("ifname", ifname); + std::string ifname; i2p::config::GetOption("ifname", ifname); if (ipv4 && i2p::config::IsDefault ("address4")) { std::string ifname4; i2p::config::GetOption("ifname4", ifname4); if (!ifname4.empty ()) i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 else if (!ifname.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 - } + i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 + } if (ipv6 && i2p::config::IsDefault ("address6")) { std::string ifname6; i2p::config::GetOption("ifname6", ifname6); @@ -166,8 +166,8 @@ namespace util i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 else if (!ifname.empty ()) i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 - } - + } + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); boost::asio::ip::address_v6 yggaddr; if (ygg) @@ -210,15 +210,15 @@ namespace util { bool published; i2p::config::GetOption("ntcp2.published", published); if (published) - { + { std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); if (!ntcp2proxy.empty ()) published = false; - } + } if (published) { uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); if (!ntcp2port) ntcp2port = port; // use standard port - i2p::context.PublishNTCP2Address (ntcp2port, true); // publish + i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish if (ipv6) { std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); @@ -228,17 +228,16 @@ namespace util } } else - i2p::context.PublishNTCP2Address (port, false); // unpublish + i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish } if (ygg) { - if (!ntcp2) - i2p::context.PublishNTCP2Address (port, true); + i2p::context.PublishNTCP2Address (port, true, false, false, true); i2p::context.UpdateNTCP2V6Address (yggaddr); if (!ipv4 && !ipv6) - i2p::context.SetStatus (eRouterStatusMesh); - } - + i2p::context.SetStatus (eRouterStatusMesh); + } + bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); @@ -281,7 +280,7 @@ namespace util else if (isFloodfill) { 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 { diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 4741e5cf..d56c2894 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -242,13 +242,9 @@ namespace http { s << "ERROR: " << string << "
\r\n"; } - void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { - s << "Uptime: "; - ShowUptime(s, i2p::context.GetUptime ()); - s << "
\r\n"; - s << "Network status: "; - switch (i2p::context.GetStatus ()) + switch (status) { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Testing"; break; @@ -269,14 +265,29 @@ namespace http { break; case eRouterErrorSymmetricNAT: s << " - Symmetric NAT"; - break; + break; default: ; } break; } default: s << "Unknown"; } + } + + void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) + { + s << "Uptime: "; + ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; + s << "Network status: "; + ShowNetworkStatus (s, i2p::context.GetStatus ()); + s << "
\r\n"; + if (i2p::context.SupportsV6 ()) + { + s << "Network status 6: "; + ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); + s << "
\r\n"; + } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (auto remains = Daemon.gracefulShutdownInterval) { s << "Stopping in: "; @@ -314,6 +325,7 @@ namespace http { if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; + s << "Version: " VERSION "
\r\n"; s << "Our external address:" << "
\r\n\r\n"; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { @@ -831,7 +843,7 @@ namespace http { s << "SAM Sessions:
\r\n
\r\n"; for (auto& it: sam->GetSessions ()) { - auto& name = it.second->localDestination->GetNickname (); + auto& name = it.second->GetLocalDestination ()->GetNickname (); s << "\r\n" << std::endl; } @@ -857,7 +869,7 @@ namespace http { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "SAM Session:
\r\n
\r\n"; - auto& ident = session->localDestination->GetIdentHash(); + auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
\r\n"; @@ -1264,14 +1276,14 @@ namespace http { ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); - if (dest) + if (dest) { std::size_t pos; pos = name.find (".i2p"); - if (pos == (name.length () - 4)) + if (pos == (name.length () - 4)) { pos = name.find (".b32.i2p"); - if (pos == std::string::npos) + if (pos == std::string::npos) { auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); uint8_t * signature = new uint8_t[signatureLen]; @@ -1291,13 +1303,13 @@ namespace http { "\r\n
\r\n"; delete[] signature; delete[] sig; - } - else + } + else s << "ERROR: Domain can't end with .b32.i2p\r\n
\r\n
\r\n"; - } + } else s << "ERROR: Domain must end with .i2p\r\n
\r\n
\r\n"; - } + } else s << "ERROR: Such destination is not found\r\n
\r\n
\r\n"; diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 3f0033e5..12602c99 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -2,6 +2,10 @@ #include #include #include + +// Use global placeholders from boost introduced when local_time.hpp is loaded +#define BOOST_BIND_GLOBAL_PLACEHOLDERS + #include #include #include @@ -714,8 +718,8 @@ namespace client for (auto& it: sam->GetSessions ()) { boost::property_tree::ptree sam_session, sam_session_sockets; - auto& name = it.second->localDestination->GetNickname (); - auto& ident = it.second->localDestination->GetIdentHash(); + auto& name = it.second->GetLocalDestination ()->GetNickname (); + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); sam_session.put("name", name); sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); diff --git a/debian/changelog b/debian/changelog index fa7d1a92..777b6216 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.38.0-1) unstable; urgency=medium + + * updated to version 2.38.0/0.9.50 + + -- orignal Mon, 17 May 2021 16:00:00 +0000 + i2pd (2.37.0-1) unstable; urgency=medium * updated to version 2.37.0 diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 6770d223..dbab9b94 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -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 * @@ -272,11 +272,19 @@ namespace data case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: publicKeyLength = BlindECDSA (m_SigType, priv, seed, BlindEncodedPrivateKeyECDSA, blindedPriv, blindedPub); - break; + break; case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; 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: LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); } diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index cdbe1bff..b316ba83 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -62,11 +62,11 @@ namespace config { ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->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)") - ("ntcpproxy", value()->default_value(""), "Ignored") + ("ntcpproxy", value()->default_value(""), "Deprecated option") #ifdef _WIN32 - ("svcctl", value()->default_value(""), "Ignored") + ("svcctl", value()->default_value(""), "Deprecated option") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") #endif @@ -77,9 +77,9 @@ namespace config { ("limits.coresize", value()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") ("limits.openfiles", value()->default_value(0), "Maximum number of open files (0 - use system default)") ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") - ("limits.ntcpsoft", value()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)") - ("limits.ntcphard", value()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") - ("limits.ntcpthreads", value()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)") + ("limits.ntcpsoft", value()->default_value(0), "Deprecated option") + ("limits.ntcphard", value()->default_value(0), "Deprecated option") + ("limits.ntcpthreads", value()->default_value(1), "Deprecated option") ; options_description httpserver("HTTP Server options"); @@ -281,7 +281,7 @@ namespace config { options_description meshnets("Meshnet transports 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()->default_value(""), "Yggdrasil address to publish") ; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 52ede959..9962599b 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -300,7 +300,11 @@ namespace client { 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 - 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) @@ -386,7 +390,7 @@ namespace client if (leaseSet->IsNewer (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"); else { @@ -405,7 +409,7 @@ namespace client leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else leaseSet = std::make_shared (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 ()) { @@ -505,7 +509,7 @@ namespace client // schedule verification m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, - shared_from_this (), std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); @@ -588,8 +592,7 @@ namespace client // assume it successive and try to verify m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, - shared_from_this (), std::placeholders::_1)); - + shared_from_this (), std::placeholders::_1)); } } } @@ -1127,13 +1130,28 @@ namespace client return dest; } + std::shared_ptr 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) { if (m_DatagramDestination == nullptr) m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); return m_DatagramDestination; - } - + } + std::vector > ClientDestination::GetAllStreams () const { std::vector > ret; @@ -1176,7 +1194,7 @@ namespace client LogPrint(eLogError, "Destinations: Can't save keys to ", path); } - void ClientDestination::CreateNewLeaseSet (std::vector > tunnels) + void ClientDestination::CreateNewLeaseSet (const std::vector >& tunnels) { std::shared_ptr leaseSet; if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index ffe41212..6e149cf5 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -152,7 +152,7 @@ namespace client virtual void CleanupDestination () {}; // additional clean up in derived classes // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; - virtual void CreateNewLeaseSet (std::vector > tunnels) = 0; + virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; private: @@ -236,6 +236,7 @@ namespace client // streaming std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional std::shared_ptr GetStreamingDestination (int port = 0) const; + std::shared_ptr RemoveStreamingDestination (int port); // following methods operate with default streaming destination void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); @@ -262,7 +263,7 @@ namespace client void CleanupDestination (); // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (std::vector > tunnels); + void CreateNewLeaseSet (const std::vector >& tunnels); private: diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index f956fa67..f9d5672f 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -160,9 +160,10 @@ namespace garlic return true; } - ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): - GarlicRoutingSession (owner, attachLeaseSet) + ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): + GarlicRoutingSession (owner, true) { + if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; } @@ -195,7 +196,7 @@ namespace garlic i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); } // we still didn't find elligator eligible pair - for (int i = 0; i < 10; i++) + for (int i = 0; i < 25; i++) { // create new m_EphemeralKeys = std::make_shared(); @@ -511,6 +512,7 @@ namespace garlic { auto tagsetNsr = std::make_shared(shared_from_this (), true); InitNewSessionTagset (tagsetNsr); + tagsetNsr->Expire (); // let non-replied session expire GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS); } } @@ -813,7 +815,6 @@ namespace garlic case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: - receiveTagset->Expire (); // NSR tagset return HandleNewOutgoingSessionReply (buf, len); default: return false; @@ -1101,6 +1102,7 @@ namespace garlic RouterIncomingRatchetSession::RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState): ECIESX25519AEADRatchetSession (&i2p::context, false) { + SetLeaseSetUpdateStatus (eLeaseSetDoNotSend); SetNoiseState (initState); } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index bd6d55e3..bdbb7111 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -88,7 +88,8 @@ namespace garlic bool IsNS () const { return m_IsNS; }; std::shared_ptr GetSession () { return m_Session; }; void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; - + int GetTrimBehind () const { return m_TrimBehindIndex; }; + void Expire (); bool IsExpired (uint64_t ts) const; @@ -160,7 +161,7 @@ namespace garlic public: - ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); + ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS); ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 42aca5be..66a3b62f 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -553,9 +553,10 @@ namespace garlic if (!session->HandleNextMessage (buf, length, nullptr, 0)) { // 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); + LogPrint (eLogWarning, "Garlic: trying to generate more ECIES-X25519-AEAD-Ratchet tags"); for (int i = 0; i < maxTags; i++) { auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); @@ -879,8 +880,12 @@ namespace garlic { auto session = it->second.tagset->GetSession (); if (!session || session->IsTerminated()) - it->second.tagset->Expire (); - ++it; + { + it = m_ECIESx25519Tags.erase (it); + numExpiredTags++; + } + else + ++it; } } if (numExpiredTags > 0) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 6897148b..7f059d72 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -371,10 +371,7 @@ namespace i2p 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"); - BN_CTX * ctx = BN_CTX_new (); - bool success = i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); - BN_CTX_free (ctx); - if(!success) return false; + if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; uint8_t retCode = 0; bool isECIES = i2p::context.IsECIES (); // replace record to reply diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index b90d6d85..a0b14385 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -72,6 +72,12 @@ namespace data } size += 256; // encryption 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]; size++; // num LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); @@ -81,9 +87,14 @@ namespace data m_IsValid = false; return; } - + if (size + num*LEASE_SIZE > m_BufferLen) + { + LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen); + m_IsValid = false; + return; + } + UpdateLeasesBegin (); - // process leases m_ExpirationTime = 0; auto ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -106,14 +117,22 @@ namespace data return; } m_ExpirationTime += LEASE_ENDDATE_THRESHOLD; - UpdateLeasesEnd (); // verify - if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) - { - LogPrint (eLogWarning, "LeaseSet: verification failed"); - m_IsValid = false; + 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"); + m_IsValid = false; + } } } @@ -778,7 +797,7 @@ namespace data } LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, std::vector > tunnels, + const KeySections& encryptionKeys, const std::vector >& tunnels, bool isPublic, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { @@ -843,9 +862,18 @@ namespace data offset += 4; // end date } // update expiration - SetExpirationTime (expirationTime*1000LL); - auto expires = expirationTime - timestamp; - htobe16buf (expiresBuf, expires > 0 ? expires : 0); + if (expirationTime) + { + SetExpirationTime (expirationTime*1000LL); + auto expires = (int)expirationTime - timestamp; + htobe16buf (expiresBuf, expires > 0 ? expires : 0); + } + else + { + // no tunnels or withdraw + SetExpirationTime (timestamp*1000LL); + memset (expiresBuf, 0, 2); // expires immeditely + } // sign keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type } diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index cd31bf30..cd6535df 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -251,7 +251,7 @@ namespace data LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, const KeySections& encryptionKeys, - std::vector > tunnels, + const std::vector >& tunnels, bool isPublic, bool isPublishedEncrypted = false); LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index ffd9bb7d..d5a03d1c 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -342,7 +342,7 @@ namespace transport else LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); } - m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + + m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } @@ -717,7 +717,7 @@ namespace transport m_Establisher->m_SessionRequestBuffer = new uint8_t[287]; // 287 bytes max for now boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + std::placeholders::_1, std::placeholders::_2)); } void NTCP2Session::ReceiveLength () @@ -726,7 +726,7 @@ namespace transport #ifdef __linux__ const int one = 1; setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); -#endif +#endif boost::asio::async_read (m_Socket, boost::asio::buffer(&m_NextReceivedLen, 2), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceivedLength, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -780,8 +780,8 @@ namespace transport if (IsTerminated ()) return; #ifdef __linux__ const int one = 1; - setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); -#endif + setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); +#endif boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -1009,11 +1009,11 @@ namespace transport LogPrint (eLogDebug, "NTCP2: Next frame sent ", bytes_transferred); if (m_LastActivityTimestamp > m_NextRouterInfoResendTime) { - m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + + m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; - SendRouterInfo (); - } - else + SendRouterInfo (); + } + else SendQueue (); } } @@ -1113,7 +1113,7 @@ namespace transport SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { - LogPrint (eLogWarning, "NTCP2: outgoing messages queue size to ", + LogPrint (eLogWarning, "NTCP2: outgoing messages queue size to ", GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } @@ -1193,7 +1193,12 @@ namespace transport m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (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 (); LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); @@ -1274,7 +1279,7 @@ namespace transport { LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; - } + } LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint ()); GetService ().post([this, conn]() { @@ -1295,25 +1300,25 @@ namespace transport // bind to local address std::shared_ptr localAddress; if (conn->GetRemoteEndpoint ().address ().is_v6 ()) - { + { if (i2p::util::net::IsYggdrasilAddress (conn->GetRemoteEndpoint ().address ())) localAddress = m_YggdrasilAddress; - else + else localAddress = m_Address6; conn->GetSocket ().open (boost::asio::ip::tcp::v6 ()); - } + } else - { + { localAddress = m_Address4; conn->GetSocket ().open (boost::asio::ip::tcp::v4 ()); - } + } if (localAddress) { boost::system::error_code ec; conn->GetSocket ().bind (*localAddress, ec); if (ec) - LogPrint (eLogError, "NTCP2: can't bind to ", localAddress->address ().to_string (), ": ", ec.message ()); - } + LogPrint (eLogError, "NTCP2: can't bind to ", localAddress->address ().to_string (), ": ", ec.message ()); + } conn->GetSocket ().async_connect (conn->GetRemoteEndpoint (), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); } else @@ -1443,8 +1448,8 @@ namespace transport { LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; - } - GetService().post([this, conn]() + } + GetService().post([this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1541,10 +1546,10 @@ namespace transport if(ep.address ().is_v6 ()) req.uri = "[" + ep.address ().to_string() + "]:" + std::to_string(ep.port ()); else - req.uri = ep.address ().to_string() + ":" + std::to_string(ep.port ()); + req.uri = ep.address ().to_string() + ":" + std::to_string(ep.port ()); if (!m_ProxyAuthorization.empty ()) req.AddHeader("Proxy-Authorization", m_ProxyAuthorization); - + boost::asio::streambuf writebuff; std::ostream out(&writebuff); out << req.to_string(); @@ -1622,7 +1627,7 @@ namespace transport sz += 16; memcpy(buff->data () + 4, addrbytes.data(), 16); } - else + else { // We mustn't really fall here because all connections are made to IP addresses LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy"); @@ -1661,17 +1666,17 @@ namespace transport } void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) - { + { auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); if (localAddress.is_v6 ()) - { + { if (i2p::util::net::IsYggdrasilAddress (localAddress)) m_YggdrasilAddress = addr; - else + else m_Address6 = addr; - } + } else m_Address4 = addr; - } + } } } diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 076a8665..a1d97cba 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -336,7 +336,7 @@ namespace data if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) { - if (leaseSet->IsPublic ()) + if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) { // TODO: implement actual update LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); @@ -345,7 +345,7 @@ namespace data } else { - LogPrint (eLogWarning, "NetDb: Unpublished LeaseSet2 received: ", ident.ToBase32()); + LogPrint (eLogWarning, "NetDb: Unpublished or expired LeaseSet2 received: ", ident.ToBase32()); m_LeaseSets.erase (ident); } } @@ -454,7 +454,7 @@ namespace data { auto r = std::make_shared(path); 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->ClearProperties (); // properties are not used for regular routers @@ -584,7 +584,7 @@ namespace data if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) it.second->SetUnreachable (false); // 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) // RouterInfo expires after 1 hour if uses introducer @@ -1149,12 +1149,13 @@ namespace data }); } - std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4only) const + std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4, const std::set& excluded) const { return GetRandomRouter ( - [v4only](std::shared_ptr router)->bool + [v4, &excluded](std::shared_ptr 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 NetDb::GetRandomIntroducer () const + std::shared_ptr NetDb::GetRandomIntroducer (bool v4, const std::set& excluded) const { return GetRandomRouter ( - [](std::shared_ptr router)->bool + [v4, &excluded](std::shared_ptr 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 }); } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 5dbef743..364cae4b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -85,9 +85,9 @@ namespace data std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; - std::shared_ptr GetRandomPeerTestRouter (bool v4only = true) const; + std::shared_ptr GetRandomPeerTestRouter (bool v4, const std::set& excluded) const; std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later - std::shared_ptr GetRandomIntroducer () const; + std::shared_ptr GetRandomIntroducer (bool v4, const std::set& excluded) const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly = false) const; diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index a712975e..41111ecc 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -678,8 +678,31 @@ namespace data // direct connection auto it = boost::asio::ip::tcp::resolver(service).resolve ( boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); - if(!ecode) - s.lowest_layer().connect (*it, ecode); + if (!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) { diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 00d467db..ad6576ed 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -28,7 +28,7 @@ namespace i2p RouterContext::RouterContext (): 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) { } @@ -44,12 +44,12 @@ namespace i2p m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); if (IsECIES ()) - { + { auto initState = new i2p::crypto::NoiseSymmetricState (); i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); - m_InitialNoiseState.reset (initState); + m_InitialNoiseState.reset (initState); m_ECIESSession = std::make_shared(*initState); - } + } } void RouterContext::CreateNewRouter () @@ -74,21 +74,21 @@ namespace i2p bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ssu; i2p::config::GetOption("ssu", ssu); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); bool nat; i2p::config::GetOption("nat", nat); - + if ((ntcp2 || ygg) && !m_NTCP2Keys) - NewNTCP2Keys (); - bool ntcp2Published = false; + NewNTCP2Keys (); + bool ntcp2Published = false; if (ntcp2) - { + { i2p::config::GetOption("ntcp2.published", ntcp2Published); if (ntcp2Published) { std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); if (!ntcp2proxy.empty ()) ntcp2Published = false; - } - } + } + } uint8_t caps = 0, addressCaps = 0; if (ipv4) { @@ -100,8 +100,8 @@ namespace i2p // we have no NAT so set external address from local address std::string address4; i2p::config::GetOption("address4", address4); if (!address4.empty ()) host = address4; - } - + } + if (ntcp2) { if (ntcp2Published) @@ -109,26 +109,26 @@ namespace i2p else // add non-published NTCP2 address { addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); - } - } + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + } + } if (ssu) - { + { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); caps |= i2p::data::RouterInfo::eReachable; // R - } + } } if (ipv6) { std::string host = "::1"; if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only i2p::config::GetOption("host", host); - else + else { std::string address6; i2p::config::GetOption("address6", address6); if (!address6.empty ()) host = address6; } - + if (ntcp2) { if (ntcp2Published) @@ -140,29 +140,29 @@ namespace i2p ntcp2Host = host; routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port); } - else - { + else + { if (!ipv4) // no other ntcp2 addresses yet routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - } - } + } + } if (ssu) - { + { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); caps |= i2p::data::RouterInfo::eReachable; // R - } + } } if (ygg) { auto yggaddr = i2p::util::net::GetYggdrasilAddress (); if (!yggaddr.is_unspecified ()) routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); - } + } if (addressCaps) routerInfo.SetUnreachableAddressesTransportCaps (addressCaps); - routerInfo.SetCaps (caps); // caps + L + routerInfo.SetCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); @@ -199,10 +199,29 @@ namespace i2p switch (m_Status) { case eRouterStatusOK: - SetReachable (); + SetReachable (true, false); // ipv4 break; 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; default: ; @@ -225,25 +244,35 @@ namespace i2p 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; bool updated = false; 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)) { - if (!port && !address->port) + bool isAddr = v4 && address->IsV4 (); + if (!isAddr && (v6 || ygg)) { - // select random port only if address's port is not set - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser + if (i2p::util::net::IsYggdrasilAddress (address->host)) + isAddr = ygg; + else + isAddr = v6 && address->IsV6 (); + } + if (isAddr) + { + if (!port && !address->port) + { + // select random port only if address's port is not set + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser + } + if (port) address->port = port; + address->published = publish; + address->ntcp2->iv = m_NTCP2Keys->iv; + updated = true; } - if (port) address->port = port; - address->cost = publish ? i2p::data::COST_NTCP2_PUBLISHED : i2p::data::COST_NTCP2_NON_PUBLISHED; - address->ntcp2->isPublished = publish; - address->ntcp2->iv = m_NTCP2Keys->iv; - updated = true; } } if (updated) @@ -281,7 +310,7 @@ namespace i2p bool updated = false; for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address->host != host && address->IsCompatible (host) && + if (address->host != host && address->IsCompatible (host) && !i2p::util::net::IsYggdrasilAddress (address->host)) { address->host = host; @@ -373,7 +402,7 @@ namespace i2p case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break; case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s default: - limit = 48; type = low; + limit = 48; type = low; } /* update caps & flags in RI */ auto caps = m_RouterInfo.GetCaps (); @@ -387,8 +416,8 @@ namespace i2p #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif - // no break here, extra + high means 'X' - case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; + // no break here, extra + high means 'X' + case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } m_RouterInfo.SetCaps (caps); UpdateRouterInfo (); @@ -435,54 +464,60 @@ namespace i2p } } - void RouterContext::SetUnreachable () + void RouterContext::SetUnreachable (bool v4, bool v6) { - // set caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eReachable; - caps |= i2p::data::RouterInfo::eUnreachable; - caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill - m_RouterInfo.SetCaps (caps); + if (v4 || (v6 && !SupportsV4 ())) + { + // set caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eReachable; + caps |= i2p::data::RouterInfo::eUnreachable; + caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill + m_RouterInfo.SetCaps (caps); + } uint16_t port = 0; // delete previous introducers auto& addresses = m_RouterInfo.GetAddresses (); 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->ssu->introducers.clear (); port = addr->port; } - // remove NTCP2 v4 address + // unpiblish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) - PublishNTCP2Address (port, false, true); + PublishNTCP2Address (port, false, v4, v6, false); // update UpdateRouterInfo (); } - void RouterContext::SetReachable () + void RouterContext::SetReachable (bool v4, bool v6) { - // update caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eUnreachable; - caps |= i2p::data::RouterInfo::eReachable; - if (m_IsFloodfill) - caps |= i2p::data::RouterInfo::eFloodfill; - m_RouterInfo.SetCaps (caps); + if (v4 || (v6 && !SupportsV4 ())) + { + // update caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eUnreachable; + caps |= i2p::data::RouterInfo::eReachable; + if (m_IsFloodfill) + caps |= i2p::data::RouterInfo::eFloodfill; + m_RouterInfo.SetCaps (caps); + } uint16_t port = 0; // delete previous introducers auto& addresses = m_RouterInfo.GetAddresses (); 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->ssu->introducers.clear (); port = addr->port; } - // insert NTCP2 back + // publish NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { @@ -491,7 +526,7 @@ namespace i2p { uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); if (!ntcp2Port) ntcp2Port = port; - PublishNTCP2Address (ntcp2Port, true, true); + PublishNTCP2Address (ntcp2Port, true, v4, v6, false); } } // update @@ -512,7 +547,7 @@ namespace i2p { if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU) foundSSU = true; - else if (addr->IsPublishedNTCP2 ()) + else if (addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP) foundNTCP2 = true; } port = addr->port; @@ -561,7 +596,7 @@ namespace i2p if (supportsV4) { bool foundSSU = false, foundNTCP2 = false; - std::string host = "127.0.0.1"; + std::string host = "127.0.0.1"; uint16_t port = 0; auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr: addresses) @@ -591,26 +626,26 @@ namespace i2p { bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); if (ntcp2Published) - { + { uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); if (!ntcp2Port) ntcp2Port = port; m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); - } + } else m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); - } - } + } + } m_RouterInfo.EnableV4 (); - } + } else m_RouterInfo.DisableV4 (); UpdateRouterInfo (); } void RouterContext::SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host) - { + { if (supportsmesh) - { + { m_RouterInfo.EnableMesh (); uint16_t port = 0; i2p::config::GetOption ("ntcp2.port", port); @@ -624,16 +659,16 @@ namespace i2p { foundMesh = true; break; - } + } } if (!foundMesh) m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); - } + } else m_RouterInfo.DisableMesh (); UpdateRouterInfo (); } - + void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { bool isYgg = i2p::util::net::IsYggdrasilAddress (host); @@ -705,9 +740,9 @@ namespace i2p if (!rekey && m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { // rekey routers with bandwidth = L (or default) this time - std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (bandwidth.empty () || bandwidth[0] == 'L') rekey = true; - } + bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); + if (!isFloodfill) rekey = true; + } if (rekey) { // update keys @@ -716,7 +751,7 @@ namespace i2p m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); - } + } // read NTCP2 keys if available std::ifstream n2k (i2p::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary); if (n2k) @@ -749,11 +784,11 @@ namespace i2p } 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 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); if (ntcp2 || ygg) { if (!m_NTCP2Keys) NewNTCP2Keys (); @@ -786,15 +821,15 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } - bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) - { + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + { auto msg = CreateI2NPMessage (typeID, payload, len); if (!msg) return false; i2p::HandleI2NPMessage (msg); - return true; - } + return true; + } + - void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); @@ -812,8 +847,8 @@ namespace i2p m_ECIESSession->HandleNextMessage (buf, len); else LogPrint (eLogError, "Router: Session is not set for ECIES router"); - } - else + } + else i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); } @@ -822,10 +857,10 @@ namespace i2p if (i2p::data::netdb.GetPublishReplyToken () == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) i2p::data::netdb.PostI2NPMsg (msg); else - { + { std::unique_lock l(m_GarlicMutex); i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); - } + } } void RouterContext::CleanupDestination () @@ -844,36 +879,41 @@ namespace i2p 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 (IsECIES ()) { if (!m_InitialNoiseState) return false; // m_InitialNoiseState is h = SHA256(h || hepk) - 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) 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"); return false; - } - m_CurrentNoiseState->MixKey (sharedSecret); + } + m_CurrentNoiseState->MixKey (sharedSecret); encrypted += 32; uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, false)) // decrypt { LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; - } + } m_CurrentNoiseState->MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) return true; - } - else - return m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); + } + else + { + 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 () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index fe6c9ec9..a4d18f82 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -91,20 +91,22 @@ namespace garlic void SetStatus (RouterStatus status); RouterError GetError () const { return m_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; }; 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 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 RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool IsUnreachable () const; - void SetUnreachable (); - void SetReachable (); + void SetUnreachable (bool v4, bool v6); + void SetReachable (bool v4, bool v6); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); @@ -168,7 +170,7 @@ namespace garlic std::chrono::time_point m_StartupTime; uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; - RouterStatus m_Status; + RouterStatus m_Status, m_StatusV6; RouterError m_Error; int m_NetID; std::mutex m_GarlicMutex; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 2157dd37..87a987fe 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -197,7 +197,8 @@ namespace data { uint8_t supportedTransports = 0; auto address = std::make_shared
(); - 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)); bool isHost = false, isIntroKey = false, isStaticKey = false; char transportStyle[6]; @@ -260,7 +261,7 @@ namespace data else if (!strcmp (key, "i")) // ntcp2 iv { 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') { @@ -308,7 +309,7 @@ namespace data else supportedTransports |= eNTCP2V4; } - else if (!address->ntcp2->isPublished) + else if (!address->published) { if (address->caps) { @@ -333,6 +334,23 @@ namespace data } else 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) @@ -513,7 +531,13 @@ namespace data for (const auto& addr_ptr : *m_Addresses) { 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)); std::stringstream properties; bool isPublished = false; @@ -529,8 +553,8 @@ namespace data WriteString ("caps", properties); properties << '='; std::string caps; - if (address.caps & AddressCaps::eV4) caps += CAPS_FLAG_V4; - if (address.caps & AddressCaps::eV6) caps += CAPS_FLAG_V6; + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; if (caps.empty ()) caps += CAPS_FLAG_V4; WriteString (caps, properties); properties << ';'; @@ -549,7 +573,7 @@ namespace data if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; if (address.host.is_v4 ()) { - if (IsReachable ()) + if (address.published) { isPublished = true; if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; @@ -558,11 +582,19 @@ namespace data caps += CAPS_FLAG_V4; } else if (address.host.is_v6 ()) - isPublished = true; + { + if (address.published) + { + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + } + else + caps += CAPS_FLAG_V6; + } else { - if (address.caps & AddressCaps::eV4) caps += CAPS_FLAG_V4; - if (address.caps & AddressCaps::eV6) caps += CAPS_FLAG_V6; + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; if (caps.empty ()) caps += CAPS_FLAG_V4; } WriteString (caps, properties); @@ -585,6 +617,18 @@ namespace data { int i = 0; for (const auto& introducer: address.ssu->introducers) + { + if (introducer.iExp) // expiration is specified + { + WriteString ("iexp" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iExp), properties); + properties << ';'; + } + i++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) { WriteString ("ihost" + boost::lexical_cast(i), properties); properties << '='; @@ -622,18 +666,6 @@ namespace data properties << ';'; i++; } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - if (introducer.iExp) // expiration is specified - { - WriteString ("iexp" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iExp), properties); - properties << ';'; - } - i++; - } } // write intro key WriteString ("key", properties); @@ -788,7 +820,7 @@ namespace data addr->host = boost::asio::ip::address::from_string (host); addr->port = port; 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->date = 0; addr->ssu.reset (new SSUExt ()); @@ -808,12 +840,11 @@ namespace data auto addr = std::make_shared
(); addr->host = host; addr->port = port; - addr->transportStyle = eTransportNTCP; - addr->cost = port ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; // override from RouterContext::PublishNTCP2Address + addr->transportStyle = eTransportNTCP; addr->caps = 0; addr->date = 0; 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->iv, iv, 16); m_Addresses->push_back(std::move(addr)); @@ -823,7 +854,8 @@ namespace data { 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) if (intro.iTag == introducer.iTag) return false; // already presented @@ -838,10 +870,11 @@ namespace data { 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) - if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) + if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { addr->ssu->introducers.erase (it); return true; @@ -1002,17 +1035,12 @@ namespace data } } - bool RouterInfo::UsesIntroducer () const - { - return m_Caps & Caps::eUnreachable; // non-reachable - } - std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const { return GetAddress ( [v4only](std::shared_ptr 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 ( [](std::shared_ptr 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; } - bool RouterInfo::IsPeerTesting (bool v4only) const + bool RouterInfo::IsPeerTesting (bool v4) const { - auto supportedTransports = m_SupportedTransports & (eSSUV4 | eSSUV6); - if (!supportedTransports) return false; // no SSU - if (v4only && !(supportedTransports & eSSUV4)) return false; // no SSU v4 + if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; return (bool)GetAddress ( - [](std::shared_ptr address)->bool + [v4](std::shared_ptr 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 & eSSUV4)) return false; + if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; return (bool)GetAddress ( - [](std::shared_ptr address)->bool + [v4](std::shared_ptr address)->bool { - return (address->transportStyle == eTransportSSU) && address->IsIntroducer (); + return (address->transportStyle == eTransportSSU) && address->IsIntroducer () && + ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); }); } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 93746e95..dd3e7e13 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -96,7 +96,7 @@ namespace data typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { - Introducer (): iExp (0) {}; + Introducer (): iPort (0), iExp (0) {}; boost::asio::ip::address iHost; int iPort; IntroKey iKey; @@ -115,7 +115,6 @@ namespace data { Tag<32> staticKey; Tag<16> iv; - bool isPublished = false; }; struct Address @@ -124,7 +123,8 @@ namespace data boost::asio::ip::address host; int port; uint64_t date; - uint8_t cost, caps; + uint8_t caps; + bool published = false; std::unique_ptr ssu; // not null for SSU std::unique_ptr ntcp2; // not null for NTCP2 @@ -146,14 +146,15 @@ namespace data } 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 UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); }; bool IsIntroducer () const { return caps & eSSUIntroducer; }; bool IsPeerTesting () const { return caps & eSSUTesting; }; - bool IsV4 () const { return (caps & AddressCaps::eV4) || host.is_v4 (); }; - bool IsV6 () const { return (caps & AddressCaps::eV6) || host.is_v6 (); }; + bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); }; + bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); }; }; typedef std::list > Addresses; @@ -204,13 +205,12 @@ namespace data bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool IsReachableFrom (const RouterInfo& other) const; bool HasValidAddresses () const { return m_SupportedTransports; }; - bool UsesIntroducer () const; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsEligibleFloodfill () const; - bool IsPeerTesting (bool v4only) const; - bool IsIntroducer () const; + bool IsPeerTesting (bool v4) const; + bool IsIntroducer (bool v4) const; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 3b2dc122..f7801bb0 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -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 * @@ -28,8 +28,8 @@ namespace transport 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_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), - m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), - m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) + m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service), + m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) { } @@ -79,9 +79,10 @@ namespace transport if (context.SupportsV4 ()) { OpenSocket (); - 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)); ScheduleTermination (); + ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } if (context.SupportsV6 ()) { @@ -89,9 +90,9 @@ namespace transport m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); + ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers } SchedulePeerTestsCleanupTimer (); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } void SSUServer::Stop () @@ -100,6 +101,8 @@ namespace transport m_IsRunning = false; m_TerminationTimer.cancel (); m_TerminationTimerV6.cancel (); + m_IntroducersUpdateTimer.cancel (); + m_IntroducersUpdateTimerV6.cancel (); m_Service.stop (); m_Socket.close (); m_SocketV6.close (); @@ -122,7 +125,7 @@ namespace transport m_Thread->join (); delete m_Thread; m_Thread = nullptr; - } + } } void SSUServer::Run () @@ -195,8 +198,8 @@ namespace transport m_EndpointV6.address (localAddress); else if (localAddress.is_v4 ()) m_Endpoint.address (localAddress); - } - + } + void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { m_Relays[tag] = relay; @@ -385,7 +388,7 @@ namespace transport auto it = sessions->find (packet->from); if (it != sessions->end ()) session = it->second; - if (!session) + if (!session && packet->len > 0) { session = std::make_shared (*this, packet->from); session->WaitForConnect (); @@ -393,7 +396,8 @@ namespace transport LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } - session->ProcessNextMessage (packet->buf, packet->len, packet->from); + if (session) + session->ProcessNextMessage (packet->buf, packet->len, packet->from); } catch (std::exception& ex) { @@ -406,23 +410,9 @@ namespace transport if (session) session->FlushData (); } - std::shared_ptr SSUServer::FindSession (std::shared_ptr 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 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; auto it = sessions.find (e); if (it != sessions.end ()) return it->second; @@ -430,28 +420,33 @@ namespace transport return nullptr; } - void SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) + bool SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) { auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); if (address) - CreateSession (router, address, peerTest); + return CreateSession (router, address, peerTest); else LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + return false; } - void SSUServer::CreateSession (std::shared_ptr router, + bool SSUServer::CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest) { if (router && address) { - if (router->UsesIntroducer ()) + if (address->UsesIntroducer ()) m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread else { + if (address->host.is_unspecified () || !address->port) return false; boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } } + else + return false; + return true; } void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) @@ -477,24 +472,28 @@ namespace transport } } - void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, + void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, std::shared_ptr 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) - { + { + // we rarely come here + auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions; 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 - if (it != m_Sessions.end ()) + if (it != sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) session->SendPeerTest (); return; } - } + } // create new session int numIntroducers = address->ssu->introducers.size (); if (numIntroducers > 0) @@ -503,14 +502,16 @@ namespace transport std::shared_ptr introducerSession; const i2p::data::RouterInfo::Introducer * introducer = nullptr; // we might have a session to introducer already + auto offset = rand (); 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 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); if (it != m_Sessions.end ()) { @@ -518,10 +519,20 @@ namespace transport 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) { - 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; } @@ -532,20 +543,27 @@ namespace transport LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); - m_Sessions[introducerEndpoint] = introducerSession; + if (introducerEndpoint.address ().is_v4 ()) + m_Sessions[introducerEndpoint] = introducerSession; + else if (introducerEndpoint.address ().is_v6 ()) + m_SessionsV6[introducerEndpoint] = introducerSession; } if (!address->host.is_unspecified () && address->port) { // create session boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - m_Sessions[remoteEndpoint] = session; + if (address->host.is_v4 ()) + m_Sessions[remoteEndpoint] = session; + else if (address->host.is_v6 ()) + m_SessionsV6[remoteEndpoint] = session; // introduce LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); 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]; Send (buf, 0, remoteEndpoint); // send HolePunch @@ -630,24 +648,30 @@ namespace transport ); } - std::set SSUServer::FindIntroducers (int maxNumIntroducers) + std::list > SSUServer::FindIntroducers (int maxNumIntroducers, + bool v4, std::set& excluded) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::set ret; - for (int i = 0; i < maxNumIntroducers; i++) + std::list > ret; + const auto& sessions = v4 ? m_Sessions : m_SessionsV6; + for (const auto& s : sessions) { - auto session = GetRandomV4Session ( - [&ret, ts](std::shared_ptr session)->bool - { - return session->GetRelayTag () && !ret.count (session.get ()) && - session->GetState () == eSessionStateEstablished && - ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION; - } - ); - if (session) + if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished && + ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) + ret.push_back (s.second); + else if (s.second->GetRemoteIdentity ()) + excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ()); + } + if ((int)ret.size () > maxNumIntroducers) + { + // shink ret randomly + int sz = ret.size () - maxNumIntroducers; + for (int i = 0; i < sz; i++) { - ret.insert (session.get ()); - break; + auto ind = rand () % ret.size (); + auto it = ret.begin (); + std::advance (it, ind); + ret.erase (it); } } return ret; @@ -658,56 +682,117 @@ namespace transport m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1)); - } - + this, std::placeholders::_1, true)); + } + void SSUServer::ScheduleIntroducersUpdateTimer () { m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1)); + 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) { // timeout expired - if (i2p::context.GetStatus () == eRouterStatusTesting) + if (v4) { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimer (); - return; + if (i2p::context.GetStatus () == eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimer (); + return; + } + if (i2p::context.GetStatus () != eRouterStatusFirewalled) + { + // we don't need introducers + m_Introducers.clear (); + return; + } + // we are firewalled + if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4 } - if (i2p::context.GetStatus () != eRouterStatusFirewalled) - { - // we don't need introducers - m_Introducers.clear (); - return; - } - // we are firewalled - if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); + 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 newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (const auto& it : m_Introducers) + std::set excluded; + auto& introducers = v4 ? m_Introducers : m_IntroducersV6; + for (const auto& it : introducers) { auto session = FindSession (it); - if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) + if (session) { - session->SendKeepAlive (); - newList.push_back (it); - numIntroducers++; + if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) + session->SendKeepAlive (); + if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) + { + newList.push_back (it); + numIntroducers++; + if (session->GetRemoteIdentity ()) + excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ()); + } + else + session = nullptr; } - else + if (!session) i2p::context.RemoveIntroducer (it); } - if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) { // create new - auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); - for (const auto& it1: introducers) + auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates + 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 (); i2p::data::RouterInfo::Introducer introducer; @@ -715,36 +800,46 @@ namespace transport introducer.iPort = ep.port (); introducer.iTag = it1->GetRelayTag (); introducer.iKey = it1->GetIntroKey (); + introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION; if (i2p::context.AddIntroducer (introducer)) { newList.push_back (ep); if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; } + if (it1->GetRemoteIdentity ()) + excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ()); } } - m_Introducers = newList; - if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) + introducers = newList; + if (introducers.size () < SSU_MAX_NUM_INTRODUCERS) { - std::set > requested; - for (auto i = m_Introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) - { - auto introducer = i2p::data::netdb.GetRandomIntroducer (); - if (introducer && !requested.count (introducer)) // not requested already - { - auto address = introducer->GetSSUAddress (true); // v4 - if (address && !address->host.is_unspecified ()) + for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) + { + auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded); + if (introducer) + { + auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address (); + if (address && !address->host.is_unspecified () && 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 - { - CreateDirectSession (introducer, ep, false); - requested.insert (introducer); - } - } - } - } + if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet + { + CreateDirectSession (introducer, ep, false); + excluded.insert (introducer->GetIdentHash ()); + } + } + } + else + { + LogPrint (eLogDebug, "SSU: can't find more introducers"); + break; + } + } } - ScheduleIntroducersUpdateTimer (); + if (v4) + ScheduleIntroducersUpdateTimer (); + else + ScheduleIntroducersUpdateTimerV6 (); } } diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index daaa83f3..aad3a384 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -31,6 +31,7 @@ namespace transport const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour + const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const size_t SSU_MAX_NUM_INTRODUCERS = 3; const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K @@ -51,11 +52,10 @@ namespace transport ~SSUServer (); void Start (); void Stop (); - void CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); - void CreateSession (std::shared_ptr router, + bool CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); + bool CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); - std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); @@ -71,6 +71,7 @@ namespace transport void RemoveRelay (uint32_t tag); std::shared_ptr FindRelaySession (uint32_t tag); void RescheduleIntroducersUpdateTimer (); + void RescheduleIntroducersUpdateTimerV6 (); void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); @@ -99,9 +100,10 @@ namespace transport template std::shared_ptr GetRandomV6Session (Filter filter); - std::set FindIntroducers (int maxNumIntroducers); + std::list > FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded); 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 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::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, - m_TerminationTimer, m_TerminationTimerV6; - std::list m_Introducers; // introducers we are connected to + boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6, + m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; + std::list m_Introducers, m_IntroducersV6; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map > m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index f9dbee5e..339ac8df 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -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 * @@ -120,7 +120,8 @@ namespace transport else { // 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) { LogPrint (eLogInfo, "SSU is not supported"); @@ -212,7 +213,7 @@ namespace transport { uint8_t extendedOptionsLen = buf[headerSize]; headerSize++; - if (extendedOptionsLen >= 3) // options are presented + if (extendedOptionsLen >= 2) // options are presented { uint16_t flags = bufbe16toh (buf + headerSize); sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; @@ -257,27 +258,14 @@ namespace transport s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y payload += 256; - uint8_t addressSize = *payload; - payload += 1; // size - uint8_t * ourAddress = payload; boost::asio::ip::address ourIP; - if (addressSize == 4) // v4 - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), ourAddress, 4); - ourIP = boost::asio::ip::address_v4 (bytes); - } - else // v6 - { - 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 + uint16_t ourPort = 0; + auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort); + if (!addressAndPortLen) return; + uint8_t * ourAddressAndPort = payload + 1; + payload += addressAndPortLen; + addressAndPortLen--; // -1 byte address size + s.Insert (ourAddressAndPort, addressAndPortLen); // address + port if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else @@ -286,7 +274,7 @@ namespace transport s.Insert (payload, 8); // relayTag and signed on time m_RelayTag = bufbe32toh (payload); payload += 4; // relayTag - if (i2p::context.GetStatus () == eRouterStatusTesting) + if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) { auto ts = i2p::util::GetSecondsSinceEpoch (); uint32_t signedOnTime = bufbe32toh(payload); @@ -308,8 +296,16 @@ namespace transport if (s.Verify (m_RemoteIdentity, payload)) { LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); - SendSessionConfirmed (y, ourAddress, addressSize + 2); + if (!i2p::util::net::IsInReservedRange (ourIP)) + { + i2p::context.UpdateAddress (ourIP); + SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen); + } + else + { + LogPrint (eLogError, "SSU: Wrong external address ", ourIP.to_string ()); + Failed (); + } } else { @@ -325,12 +321,17 @@ namespace transport auto headerSize = GetSSUHeaderSize (buf); 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; } const uint8_t * payload = buf + headerSize; payload++; // identity fragment info 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 auto identity = std::make_shared (payload, identitySize); auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already @@ -348,10 +349,15 @@ namespace transport if (m_SignedData) m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time - size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); - paddingSize &= 0x0F; // %16 + size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); + size_t paddingSize = fullSize & 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; + if (fullSize + paddingSize > len) + { + LogPrint (eLogError, "SSU: Session confirmed message is too short ", len); + return; + } // verify signature if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) { @@ -371,18 +377,19 @@ namespace transport uint8_t * payload = buf + sizeof (SSUHeader); uint8_t flag = 0; // 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 flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length + *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; } // fill payload memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x - bool isV4 = m_RemoteEndpoint.address ().is_v4 (); if (isV4) { payload[256] = 4; @@ -402,7 +409,8 @@ namespace transport 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) { LogPrint (eLogInfo, "SSU is not supported"); @@ -430,6 +438,7 @@ namespace transport else FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); m_Server.Send (buf, 96, m_RemoteEndpoint); + LogPrint (eLogDebug, "SSU: relay request sent"); } void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) @@ -475,7 +484,7 @@ namespace transport else s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (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); 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, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) { - // Charlie's address always v4 - if (!to.address ().is_v4 ()) + bool isV4 = to.address ().is_v4 (); // Charle's + 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; } - 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); - *payload = 4; - payload++; // size - htobe32buf (payload, to.address ().to_v4 ().to_ulong ()); // Charlie's IP - payload += 4; // address + // Charlie + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4 + 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 payload += 2; // port // Alice - bool isV4 = from.address ().is_v4 (); // Alice's if (isV4) { *payload = 4; @@ -633,64 +653,67 @@ namespace transport void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) { if (!session) return; - // Alice's address always v4 - if (!from.address ().is_v4 ()) + bool isV4 = from.address ().is_v4 (); // Alice's + 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; } - 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); - *payload = 4; - payload++; // size - htobe32buf (payload, from.address ().to_v4 ().to_ulong ()); // Alice's IP - payload += 4; // address + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 + 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 payload += 2; // port *payload = 0; // challenge size uint8_t iv[16]; RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); - m_Server.Send (buf, 48, session->m_RemoteEndpoint); + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey); + m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint); LogPrint (eLogDebug, "SSU: relay intro sent"); } void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU message: Relay response received"); - uint8_t remoteSize = *buf; - buf++; // remote size - boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); - buf += remoteSize; // remote address - uint16_t remotePort = bufbe16toh (buf); - buf += 2; // remote port - uint8_t ourSize = *buf; - buf++; // our size + boost::asio::ip::address remoteIP; + uint16_t remotePort = 0; + auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort); + if (!remoteSize) return; + buf += remoteSize; len -= remoteSize; boost::asio::ip::address ourIP; - if (ourSize == 4) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf, 4); - 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 + uint16_t ourPort = 0; + auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort); + if (!ourSize) return; + buf += ourSize; len -= ourSize; LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); - if (ourPort != m_Server.GetPort ()) + if (!i2p::util::net::IsInReservedRange (ourIP)) + i2p::context.UpdateAddress (ourIP); + else + LogPrint (eLogWarning, "SSU: Wrong external address ", ourIP.to_string ()); + if (ourIP.is_v4 ()) { - if (i2p::context.GetStatus () == eRouterStatusTesting) - i2p::context.SetError (eRouterErrorSymmetricNAT); - } - else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) - i2p::context.SetStatus (eRouterStatusTesting); + if (ourPort != m_Server.GetPort ()) + { + if (i2p::context.GetStatus () == eRouterStatusTesting) + i2p::context.SetError (eRouterErrorSymmetricNAT); + } + else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) + i2p::context.SetStatus (eRouterStatusTesting); + } uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce auto it = m_RelayRequests.find (nonce); @@ -703,12 +726,16 @@ namespace transport // we didn't have correct endpoint when sent relay request // now we do 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 + // we assume that HolePunch has been sent by this time and our SessionRequest will go through m_Server.CreateDirectSession (it->second, remoteEndpoint, false); } // delete request m_RelayRequests.erase (it); + // cancel connect timer + m_ConnectTimer.cancel (); } else LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); @@ -716,18 +743,12 @@ namespace transport void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) { - uint8_t size = *buf; - if (size == 4) - { - buf++; // size - boost::asio::ip::address_v4 address (bufbe32toh (buf)); - buf += 4; // address - uint16_t port = bufbe16toh (buf); + boost::asio::ip::address ip; + uint16_t port = 0; + ExtractIPAddressAndPort (buf, len, ip, port); + if (!ip.is_unspecified () && port) // send hole punch of 0 bytes - m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); - } - else - LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); + m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port)); } 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) { uint32_t nonce = bufbe32toh (buf); // 4 bytes - uint8_t size = buf[4]; // 1 byte - const uint8_t * address = buf + 5; // big endian, size bytes - uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes - const uint8_t * introKey = buf + size + 7; - if (port && (size != 4) && (size != 16)) + boost::asio::ip::address addr; // Alice's addresss + uint16_t port = 0; // and port + auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port); + if (port && (size != 7) && (size != 19)) { - LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); + LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported"); return; } + const uint8_t * introKey = buf + 4 + size; switch (m_Server.GetPeerTestParticipant (nonce)) { // existing test @@ -1003,7 +1024,15 @@ namespace transport if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob { 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); m_Server.RescheduleIntroducersUpdateTimer (); @@ -1014,7 +1043,10 @@ namespace transport LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); if (m_State == eSessionStateEstablished) LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice"); - i2p::context.SetStatus (eRouterStatusOK); + if (IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); + else + i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie } @@ -1028,7 +1060,10 @@ namespace transport { // peer test successive LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); - i2p::context.SetStatus (eRouterStatusOK); + if (IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); + else + i2p::context.SetStatus (eRouterStatusOK); m_Server.RemovePeerTest (nonce); } break; @@ -1060,20 +1095,7 @@ namespace transport LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - boost::asio::ip::address addr; // Alice's address - 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 + SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob } else { @@ -1130,7 +1152,8 @@ namespace transport if (toAddress) { // 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) memcpy (payload, addr->ssu->key, 32); // intro key else @@ -1160,7 +1183,7 @@ namespace transport { // we are Alice 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) { LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); @@ -1233,5 +1256,36 @@ namespace transport i2p::transport::transports.UpdateSentBytes (size); 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; + } } } diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 3aa04638..43d0d595 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -102,7 +102,8 @@ namespace transport uint32_t GetRelayTag () const { return m_RelayTag; }; const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; uint32_t GetCreationTime () const { return m_CreationTime; }; - + void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers + void FlushData (); private: @@ -145,6 +146,8 @@ namespace transport 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: friend class SSUData; // TODO: change in later diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 0d482303..612b1058 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -942,15 +942,26 @@ namespace stream { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { - m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); - if (!m_RemoteLeaseSet) + auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (!remoteLeaseSet) { - LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + 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(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 + } } else { // LeaseSet updated + m_RemoteLeaseSet = remoteLeaseSet; m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); } diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 9265f289..1e40f88c 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -137,7 +137,7 @@ namespace transport 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_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_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), @@ -505,8 +505,8 @@ namespace transport } if (address && address->IsReachableSSU ()) { - m_SSUServer->CreateSession (peer.router, address); - return true; + if (m_SSUServer->CreateSession (peer.router, address)) + return true; } } else @@ -576,68 +576,66 @@ namespace transport return; } if (m_SSUServer) - { - 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); - }); - } - } - } - } + PeerTest (); else 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 (m_SSUServer) + if (RoutesRestricted() || !m_SSUServer) return; + if (ipv4 && i2p::context.SupportsV4 ()) { - LogPrint (eLogInfo, "Transports: Started peer test"); + LogPrint (eLogInfo, "Transports: Started peer test ipv4"); + std::set excluded; bool statusChanged = false; 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 (!statusChanged) + auto addr = router->GetSSUAddress (true); // ipv4 + if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { - statusChanged = true; - i2p::context.SetStatus (eRouterStatusTesting); // first time only - } - m_SSUServer->CreateSession (router, true, true); // peer test v4 + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatus (eRouterStatusTesting); // first time only + } + m_SSUServer->CreateSession (router, addr, true); // peer test v4 + } + excluded.insert (router->GetIdentHash ()); } } 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 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"); + } } std::shared_ptr Transports::GetNextX25519KeysPair () @@ -752,9 +750,12 @@ namespace transport ++it; } UpdateBandwidth (); // TODO: use separate timer(s) for it - if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test - DetectExternalIP (); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting; + bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting; + // 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)); } } @@ -772,10 +773,15 @@ namespace transport std::shared_ptr Transports::GetRandomPeer () const { if (m_Peers.empty ()) return nullptr; - std::unique_lock l(m_PeersMutex); - auto it = m_Peers.begin (); - std::advance (it, rand () % m_Peers.size ()); - return it != m_Peers.end () ? it->second.router : nullptr; + i2p::data::IdentHash ident; + { + std::unique_lock l(m_PeersMutex); + auto it = m_Peers.begin (); + std::advance (it, rand () % m_Peers.size ()); + 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 families) { diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index d480840a..9377286c 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -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 MAX_NUM_DELAYED_MESSAGES = 50; + const int MAX_NUM_DELAYED_MESSAGES = 150; class Transports { public: @@ -131,7 +131,7 @@ namespace transport 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; }; bool IsCheckReserved () { return m_CheckReserved; }; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 417f5f15..ed9a1a82 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -406,6 +406,7 @@ namespace tunnel bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) { + int start = 0; auto prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { @@ -414,20 +415,22 @@ namespace tunnel if(!hop) return false; peers.push_back(hop->GetRouterIdentity()); 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 (); 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; 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); 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 ()); return false; } - if ((i == numHops - 1) && - ((inbound && !hop->IsReachable ()) || // IBGW is not reachable - (!inbound && !hop->IsV4 ()))) // OBEP is not ipv4 + if ((i == numHops - 1) && (!hop->IsV4 () || // doesn't support ipv4 + (inbound && !hop->IsReachable ()))) // IBGW is not reachable { auto hop1 = nextHop (prevHop, true); if (hop1) hop = hop1; diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index e75b7c19..69fc366a 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -524,6 +524,7 @@ namespace net bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses + if (host.is_unspecified ()) return false; if(host.is_v4()) { static const std::vector< std::pair > reservedIPv4Ranges { diff --git a/libi2pd/version.h b/libi2pd/version.h index dd6511f1..028d1692 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,7 +16,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 37 +#define I2PD_VERSION_MINOR 38 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #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_MINOR 9 -#define I2P_VERSION_MICRO 49 +#define I2P_VERSION_MICRO 50 #define I2P_VERSION_PATCH 0 #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) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index c9db4a82..d07c6248 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -84,7 +84,12 @@ namespace client m_Owner->SendMessagePayloadMessage (buf + 4, length); } - void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) + void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) + { + GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); + } + + void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) { if (m_IsCreatingLeaseSet) { diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index da7d8ffa..085bf30a 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -93,7 +93,7 @@ namespace client // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (std::vector > tunnels); + void CreateNewLeaseSet (const std::vector >& tunnels); private: @@ -101,6 +101,8 @@ namespace client { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); + void PostCreateNewLeaseSet (std::vector > tunnels); + private: std::shared_ptr m_Owner; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index b15b47b0..5476bfe2 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -84,7 +84,11 @@ namespace client // bind to 127.x.x.x address // where x.x.x are first three bytes from ident 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 diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 9f7e771e..af9ba6a8 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -58,8 +58,8 @@ namespace client { if (Session) { - if (m_IsAccepting && Session->localDestination) - Session->localDestination->StopAcceptingStreams (); + if (m_IsAccepting && Session->GetLocalDestination ()) + Session->GetLocalDestination ()->StopAcceptingStreams (); } break; } @@ -270,6 +270,10 @@ namespace client ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) 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)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; @@ -352,6 +356,7 @@ namespace client if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; + else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster; if (type == eSAMSessionTypeUnknown) { // unknown style @@ -409,7 +414,7 @@ namespace client if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) { session->UDPEndpoint = forward; - auto dest = session->localDestination->CreateDatagramDestination (); + auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); if (type == eSAMSessionTypeDatagram) dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), 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)); } - if (session->localDestination->IsReady ()) + if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { @@ -438,7 +443,7 @@ namespace client auto session = m_Owner.FindSession(m_ID); if(session) { - if (session->localDestination->IsReady ()) + if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { @@ -457,7 +462,7 @@ namespace client { uint8_t buf[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); priv[l1] = 0; #ifdef _MSC_VER @@ -495,20 +500,38 @@ namespace client else m_BufferOffset = 0; - auto dest = std::make_shared (); - size_t l = dest->FromBase64(destination); - if (l > 0) + std::shared_ptr addr; + if (destination.find(".i2p") != std::string::npos) + addr = context.GetAddressBook().GetAddress (destination); + else { - context.GetAddressBook().InsertFullAddress(dest); - auto leaseSet = session->localDestination->FindLeaseSet(dest->GetIdentHash()); - if (leaseSet) - Connect(leaseSet, session); - else + auto dest = std::make_shared (); + size_t l = dest->FromBase64(destination); + if (l > 0) { - session->localDestination->RequestDestination(dest->GetIdentHash(), + context.GetAddressBook().InsertFullAddress(dest); + addr = std::make_shared
(dest->GetIdentHash ()); + } + } + + if (addr && addr->IsValid ()) + { + if (addr->IsIdentHash ()) + { + auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash); + if (leaseSet) + Connect(leaseSet, session); + else + { + session->GetLocalDestination ()->RequestDestination(addr->identHash, + std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, + shared_from_this(), std::placeholders::_1)); + } + } + else // B33 + session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); - } } else SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); @@ -523,7 +546,7 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeStream; - m_Stream = session->localDestination->CreateStream (remote); + m_Stream = session->GetLocalDestination ()->CreateStream (remote); if (m_Stream) { m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send @@ -567,10 +590,10 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeAcceptor; - if (!session->localDestination->IsAcceptingStreams ()) + if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { 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); } @@ -590,7 +613,7 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); return; } - if (session->localDestination->IsAcceptingStreams ()) + if (session->GetLocalDestination ()->IsAcceptingStreams ()) { SendI2PError ("Already accepting"); return; @@ -620,7 +643,7 @@ namespace client m_IsAccepting = true; std::string& silent = params[SAM_PARAM_SILENT]; 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)); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } @@ -636,7 +659,7 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - auto d = session->localDestination->GetDatagramDestination (); + auto d = session->GetLocalDestination ()->GetDatagramDestination (); if (d) { i2p::data::IdentityEx dest; @@ -706,7 +729,7 @@ namespace client std::shared_ptr identity; std::shared_ptr addr; 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") SendNamingLookupReply (name, dest->GetIdentity ()); 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(session); + std::map 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(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(session); + std::map 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) { LogPrint (eLogError, "SAM: i2p error ", msg); @@ -952,7 +1042,7 @@ namespace client if (it->m_SocketType == eSAMSocketTypeAcceptor) { 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; } } @@ -1090,19 +1180,11 @@ namespace client 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 dest): - m_Bridge(parent), - localDestination (dest), - UDPEndpoint(nullptr), - Name(id), Type (type) + SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): + m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) { } - SAMSession::~SAMSession () - { - i2p::client::context.DeleteLocalDestination (localDestination); - } - void SAMSession::CloseStreams () { 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 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 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 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): RunnableService ("SAM"), m_IsSingleThread (singleThread), 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 l(m_SessionsMutex); for (auto& it: m_Sessions) - it.second->CloseStreams (); + it.second->Close (); m_Sessions.clear (); } StopIOService (); @@ -1248,7 +1382,8 @@ namespace client if (localDestination) { localDestination->Acquire (); - auto session = std::make_shared(*this, id, type, localDestination); + auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : + std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) @@ -1258,6 +1393,13 @@ namespace client return nullptr; } + bool SAMBridge::AddSession (std::shared_ptr session) + { + if (!session) return false; + auto ret = m_Sessions.emplace (session->Name, session); + return ret.second; + } + void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; @@ -1272,9 +1414,8 @@ namespace client } if (session) { - session->localDestination->Release (); - session->localDestination->StopAcceptingStreams (); - session->CloseStreams (); + session->StopLocalDestination (); + session->Close (); if (m_IsSingleThread) { auto timer = std::make_shared(GetService ()); @@ -1349,10 +1490,10 @@ namespace client i2p::data::IdentityEx dest; dest.FromBase64 (destination); if (session->Type == eSAMSessionTypeDatagram) - session->localDestination->GetDatagramDestination ()-> + session->GetLocalDestination ()->GetDatagramDestination ()-> SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); else // raw - session->localDestination->GetDatagramDestination ()-> + session->GetLocalDestination ()->GetDatagramDestination ()-> SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 9495bf6f..d92cf009 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,8 @@ namespace client 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_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_STATUS_OK[] = "STREAM STATUS RESULT=OK\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_HOST[] = "HOST"; const char SAM_PARAM_PORT[] = "PORT"; + const char SAM_PARAM_FROM_PORT[] = "FROM_PORT"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; const char SAM_VALUE_RAW[] = "RAW"; + const char SAM_VALUE_MASTER[] = "MASTER"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; @@ -134,6 +139,8 @@ namespace client void ProcessStreamForward (char * buf, size_t len); void ProcessDestGenerate (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); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); @@ -171,23 +178,57 @@ namespace client eSAMSessionTypeUnknown, eSAMSessionTypeStream, eSAMSessionTypeDatagram, - eSAMSessionTypeRaw + eSAMSessionTypeRaw, + eSAMSessionTypeMaster }; struct SAMSession { SAMBridge & m_Bridge; - std::shared_ptr localDestination; - std::shared_ptr UDPEndpoint; std::string Name; SAMSessionType Type; - - SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); - ~SAMSession (); - + std::shared_ptr UDPEndpoint; // TODO: move + + SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); + virtual ~SAMSession () {}; + + virtual std::shared_ptr GetLocalDestination () = 0; + virtual void StopLocalDestination () = 0; + virtual void Close () { CloseStreams (); }; + void CloseStreams (); }; + struct SAMSingleSession: public SAMSession + { + std::shared_ptr localDestination; + + SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); + ~SAMSingleSession (); + + std::shared_ptr GetLocalDestination () { return localDestination; }; + void StopLocalDestination (); + }; + + struct SAMMasterSession: public SAMSingleSession + { + std::set subsessions; + SAMMasterSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest): + SAMSingleSession (parent, name, eSAMSessionTypeMaster, dest) {}; + void Close (); + }; + + struct SAMSubSession: public SAMSession + { + std::shared_ptr masterSession; + int inPort; + + SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port); + // implements SAMSession + std::shared_ptr GetLocalDestination (); + void StopLocalDestination (); + }; + class SAMBridge: private i2p::util::RunnableService { public: @@ -201,6 +242,7 @@ namespace client boost::asio::io_service& GetService () { return GetIOService (); }; std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); + bool AddSession (std::shared_ptr session); void CloseSession (const std::string& id); std::shared_ptr FindSession (const std::string& id) const;