diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index ed7147ff..6f10e62b 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -133,6 +133,8 @@ jobs: git clone https://github.com/msys2/MINGW-packages cd MINGW-packages git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost + cd mingw-w64-boost + sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD # headers - name: Get headers package version @@ -230,8 +232,10 @@ jobs: run: | cd MINGW-packages/mingw-w64-boost MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Remove boost packages + run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs - name: Install boost package - run: pacman --noconfirm -U --overwrite MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst + run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst # Building i2pd - name: Build application diff --git a/ChangeLog b/ChangeLog index b3559fb7..23864c0e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,70 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.56.0] - 2025-02-11 +### Added +- Config params for shared local destination +- AddressBook full addresses cache +- Decline transit tunnel to duplicated router +- Recreate tunnels in random order +### Changed +- Exclude disk operations from SSU2 and NTCP2 threads +- Set minimal version for peer test to 0.9.62 +- Send ack requested flag after second SSU2 resend attempt +- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions +- Don't change datagram routing path too often if unidirectional data stream +- Reduce LeaseSet and local RouterInfo publishing confirmation intervals +- Don't delete buffer of connected routers or if an update received +- Smaller RouterInfo request timeout if sent directly +- Persist local RouterInfo in separate thread +- Don't recalculate and process ranges for every SSU2 Ack block +- Reseeds list +### Fixed +- Termination deadlock if SAM session is active +- Race condition at tunnel endpoint +- Inbound tunnel build encryption + +## [2.55.0] - 2024-12-30 +### Added +- Support boost 1.87 +- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams +- Separate thread for tunnel build requests +- Show next peer and connectivity on "Transit tunnels" page +- Tunnel name for local destination thread +- Throttle incoming ECIESx25519 sessions +- Send tunnel data to transport session directly if possible +- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy +- Random tunnel rejection when medium congestion +- Save unreachable router's endpoint to use it next time without introducers +- Recognize symmetric NAT from peer test message 7 +- Resend HolePunch and RelayResponse messages +### Changed +- Removed own implementation of AESNI and always use one from openssl +- Renamed main thread to i2pd-daemon +- Set i2p.streaming.profile=2 for shared local destination +- Reduced LeaseSet and RouterInfo lookup timeouts +- Cleanup ECIES sessions and tags more often +- Check LeaseSet expiration time +- Handle NTCP2 session handshakes in separate thread +- Limit last decline time by 1.5 hours in router's profile +- Don't handle RelayRequest and RelayIntro with same nonce twice +- Increased hole punch expiration interval +- Send peer test message 6 with delay if message 4 was received before message 5 +- Pre-calculate more x25519 keys for transports in runtime +- Don't request LeaseSet for incoming stream +- Terminate incoming stream right away if no remote LeaseSet +- Handle choked, new RTO and window size calculation and resetting algorithm for streams +### Fixed +- Empty string in addressbook subscriptions +- ECIESx25519 sessions without destination +- Missing RouterInfo buffer in NetDb +- Invalid I2PControl certificate +- Routers disappear from NetDb when offline +- Peer test message 6 sent to unknown endpoint +- Race condition with LeaseSet update +- Excessive CPU usage by streams +- Crash on shutdown + ## [2.54.0] - 2024-10-06 ### Added - Maintain recently connected routers list to avoid false-positive peer test @@ -21,12 +85,12 @@ - Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP - Publish LeaseSet with new timestamp update if tunnel was replaced in the same second - Increase max number of generated tags to 800 per tagset -- Routing path expiration by time instead num attempts +- Routing path expiration by time instead num attempts - Save timestamp from epoch instead local time to profiles - Update introducer's iTag if session to introducer was replaced to new one - RTT, window size and number of NACKs calculation for streaming - Don't select same peer for tunnel too often -- Use WinApi for data path UTF-8 conversion for Windows +- Use WinApi for data path UTF-8 conversion for Windows ### Fixed - Jump link crash if address book is disabled - Race condition if connect through an introducer diff --git a/Makefile b/Makefile index 3998beb0..0d4ca48c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,6 @@ DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk -USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) USE_UPNP := $(or $(USE_UPNP),no) DEBUG := $(or $(DEBUG),yes) @@ -70,6 +69,9 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) else ifneq (, $(findstring haiku, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.haiku +else ifneq (, $(findstring solaris, $(SYS))) + DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp + include Makefile.solaris else # not supported $(error Not supported platform) endif diff --git a/Makefile.bsd b/Makefile.bsd index 4cf8f80a..1c911802 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -3,7 +3,7 @@ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misl DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDLIBS = -lcrypto -lssl -lz -lpthread -lboost_system -lboost_program_options +LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. diff --git a/Makefile.haiku b/Makefile.haiku index d0824d73..bc3094f6 100644 --- a/Makefile.haiku +++ b/Makefile.haiku @@ -2,7 +2,7 @@ CXX = g++ CXXFLAGS := -Wall -std=c++17 INCFLAGS = -I/system/develop/headers DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE -LDLIBS = -lbe -lbsd -lnetwork -lz -lcrypto -lssl -lboost_system -lboost_program_options -lpthread +LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_system -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP diff --git a/Makefile.homebrew b/Makefile.homebrew index e14ea955..706f9811 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -18,7 +18,7 @@ endif LDLIBS += -lpthread -ldl else LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib - LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_filesystem -lboost_program_options -lpthread + LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) LDFLAGS += -L${UPNPROOT}/lib LDLIBS += -lminiupnpc @@ -30,13 +30,6 @@ ifeq ($(USE_UPNP),yes) INCFLAGS += -I${UPNPROOT}/include endif -ifeq ($(USE_AESNI),yes) -ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that - NEEDED_CXXFLAGS += -maes - DEFINES += -D__AES__ -endif -endif - install: all install -d ${PREFIX}/bin install -m 755 ${I2PD} ${PREFIX}/bin diff --git a/Makefile.linux b/Makefile.linux index 9c3e6895..4ea39e22 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -40,7 +40,7 @@ ifeq ($(USE_UPNP),yes) endif LDLIBS += -lpthread -ldl else - LDLIBS += -lcrypto -lssl -lz -lboost_program_options -lpthread -latomic + LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif @@ -51,13 +51,6 @@ ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP endif -ifeq ($(USE_AESNI),yes) -ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that - NEEDED_CXXFLAGS += -maes - DEFINES += -D__AES__ -endif -endif - install: all install -d ${PREFIX}/bin install -m 755 ${I2PD} ${PREFIX}/bin diff --git a/Makefile.mingw b/Makefile.mingw index fc92e9b0..32d60764 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -42,12 +42,6 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_AESNI),yes) - NEEDED_CXXFLAGS += -maes - LDFLAGS += -maes - DEFINES += -D__AES__ -endif - ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif diff --git a/Makefile.osx b/Makefile.osx index 48eb1a51..52282307 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -7,9 +7,9 @@ LDFLAGS += -Wl,-dead_strip LDFLAGS += -Wl,-dead_strip_dylibs ifeq ($(USE_STATIC),yes) - LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread + LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread else - LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_filesystem -lboost_program_options -lpthread + LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) @@ -25,9 +25,5 @@ endif OSARCH = $(shell uname -p) ifneq ($(OSARCH),powerpc) - ifeq ($(USE_AESNI),yes) - CXXFLAGS += -D__AES__ -maes - else - CXXFLAGS += -msse - endif + CXXFLAGS += -msse endif diff --git a/Makefile.solaris b/Makefile.solaris new file mode 100644 index 00000000..77d34114 --- /dev/null +++ b/Makefile.solaris @@ -0,0 +1,9 @@ +CXX = g++ +INCFLAGS = -I/usr/openssl/3/include +CXXFLAGS := -Wall -std=c++20 +LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket + +ifeq ($(USE_UPNP),yes) + DEFINES += -DUSE_UPNP + LDLIBS += -lminiupnpc +endif \ No newline at end of file diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 954f5de9..bc936e18 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -29,7 +29,6 @@ project( ) # configurable options -option(WITH_AESNI "Use AES-NI instructions set" ON) option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) @@ -185,16 +184,6 @@ if(UNIX) endif() endif() -# Note: AES-NI and AVX is available on x86-based CPU's. -# Here also ARM64 implementation, but currently we don't support it. -# MSVC is not supported due to different ASM processing, so we hope OpenSSL has its own checks to run optimized code. -if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386")) - if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") - endif() - add_definitions(-D__AES__) -endif() - if(WITH_ADDRSANITIZER) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") @@ -335,7 +324,6 @@ message(STATUS "Architecture : ${ARCHITECTURE}") message(STATUS "Compiler flags : ${CMAKE_CXX_FLAGS}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") -message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake b/build/cmake_modules/GetGitRevisionDescription.cmake index 4fbd90db..a08895c6 100644 --- a/build/cmake_modules/GetGitRevisionDescription.cmake +++ b/build/cmake_modules/GetGitRevisionDescription.cmake @@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) # function returns an empty string via _git_dir_var. # # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and -# neither foo nor bar contain a file/directory .git. This wil return +# neither foo nor bar contain a file/directory .git. This will return # C:/bla/.git # function(_git_find_closest_git_dir _start_dir _git_dir_var) diff --git a/contrib/debian/trusty/patches/01-upnp.patch b/contrib/debian/trusty/patches/01-upnp.patch index bec8f2b0..74d36c06 100644 --- a/contrib/debian/trusty/patches/01-upnp.patch +++ b/contrib/debian/trusty/patches/01-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2022-03-23 +Last-Update: 2024-12-30 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ include filelist.mk +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) diff --git a/contrib/debian/xenial/patches/01-upnp.patch b/contrib/debian/xenial/patches/01-upnp.patch index bec8f2b0..74d36c06 100644 --- a/contrib/debian/xenial/patches/01-upnp.patch +++ b/contrib/debian/xenial/patches/01-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2022-03-23 +Last-Update: 2024-12-30 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ include filelist.mk +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index be4a6719..c26f8af0 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -243,7 +243,7 @@ verify = true ## Default: reg.i2p at "mainline" I2P Network # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt ## Optional subscriptions URLs, separated by comma -# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/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 [limits] ## Maximum active transit sessions (default: 5000) @@ -277,9 +277,3 @@ verify = 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) -# aesni = true -## Force usage of CPU instructions set, even if they not found (default: false) -## DO NOT TOUCH that option if you really don't know what are you doing! -# force = false diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 05158bc9..2083ba18 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.54.0 +Version: 2.56.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -148,6 +148,12 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 11 2025 orignal - 2.56.0 +- update to 2.56.0 + +* Mon Dec 30 2024 orignal - 2.55.0 +- update to 2.55.0 + * Sun Oct 6 2024 orignal - 2.54.0 - update to 2.54.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 4b1e573b..4eb558ba 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.54.0 +Version: 2.56.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -146,6 +146,12 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 11 2025 orignal - 2.56.0 +- update to 2.56.0 + +* Mon Dec 30 2024 orignal - 2.55.0 +- update to 2.55.0 + * Sun Oct 6 2024 orignal - 2.54.0 - update to 2.54.0 diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index 55723c43..fc455e79 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -5,6 +5,7 @@ port = 6668 destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat +i2p.streaming.profile=2 #[IRC-IRC2P] #type = client diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index b572944f..e2fdf2d4 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -149,12 +149,10 @@ namespace util LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); - bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); bool ssu; i2p::config::GetOption("ssu", ssu); if (!ssu && i2p::config::IsDefault ("precomputation.elgamal")) precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly - i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt); + i2p::crypto::InitCrypto (precomputation); i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index e32bd459..f10bd1e2 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -132,7 +132,8 @@ namespace http { static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { - std::string state, stateText; + std::string state; + std::string_view stateText; switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : @@ -146,7 +147,7 @@ namespace http { } if (stateText.empty ()) stateText = tr(state); - s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; + s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: ShowTraffic(s, bytes); s << "\r\n"; } @@ -213,7 +214,7 @@ namespace http { "\r\n"; } - static void ShowError(std::stringstream& s, const std::string& string) + static void ShowError(std::stringstream& s, std::string_view string) { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } @@ -826,7 +827,7 @@ namespace http { if (i2p::tunnel::tunnels.CountTransitTunnels()) { s << "" << tr("Transit Tunnels") << ":
\r\n"; - s << ""; + s << "
ID" << tr("Amount") << "
"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (std::dynamic_pointer_cast(it)) @@ -836,7 +837,7 @@ namespace http { else s << "\r\n"; + s << "\r\n"; } s << "
ID" << tr("Amount") << "" << tr("Next") << "
" << it->GetTunnelID () << ""; ShowTraffic(s, it->GetNumTransmittedBytes ()); - s << "
" << it->GetNextPeerName () << "
\r\n"; } @@ -1262,7 +1263,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, tr("Unknown page") + ": " + page); + ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO return; } } @@ -1462,7 +1463,7 @@ namespace http { else { res.code = 400; - ShowError(s, tr("Unknown command") + ": " + cmd); + ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO return; } diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 1ea75bbb..6261a14c 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,6 @@ // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include -#include #include "FS.h" #include "Log.h" @@ -30,11 +29,24 @@ namespace i2p namespace client { I2PControlService::I2PControlService (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)), + m_IsRunning (false), m_SSLContext (boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { + if (port) + m_Acceptor = std::make_unique(m_Service, + boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)); + else +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + { + std::remove (address.c_str ()); // just in case + m_LocalAcceptor = std::make_unique(m_Service, + boost::asio::local::stream_protocol::endpoint(address)); + } +#else + LogPrint(eLogError, "I2PControl: Local sockets are not supported"); +#endif + i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate / keys @@ -98,7 +110,7 @@ namespace client { Accept (); m_IsRunning = true; - m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); + m_Thread = std::make_unique(std::bind (&I2PControlService::Run, this)); } } @@ -107,12 +119,19 @@ namespace client if (m_IsRunning) { m_IsRunning = false; - m_Acceptor.cancel (); + if (m_Acceptor) m_Acceptor->cancel (); +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + if (m_LocalAcceptor) + { + auto path = m_LocalAcceptor->local_endpoint().path(); + m_LocalAcceptor->cancel (); + std::remove (path.c_str ()); + } +#endif m_Service.stop (); if (m_Thread) { m_Thread->join (); - delete m_Thread; m_Thread = nullptr; } } @@ -134,40 +153,60 @@ namespace client void I2PControlService::Accept () { - auto newSocket = std::make_shared (m_Service, m_SSLContext); - m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, - std::placeholders::_1, newSocket)); + if (m_Acceptor) + { + auto newSocket = std::make_shared > (m_Service, m_SSLContext); + m_Acceptor->async_accept (newSocket->lowest_layer(), + [this, newSocket](const boost::system::error_code& ecode) + { + HandleAccepted (ecode, newSocket); + }); + } +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + else if (m_LocalAcceptor) + { + auto newSocket = std::make_shared > (m_Service, m_SSLContext); + m_LocalAcceptor->async_accept (newSocket->lowest_layer(), + [this, newSocket](const boost::system::error_code& ecode) + { + HandleAccepted (ecode, newSocket); + }); + } +#endif } - void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + template + void I2PControlService::HandleAccepted (const boost::system::error_code& ecode, + std::shared_ptr newSocket) { if (ecode != boost::asio::error::operation_aborted) Accept (); - if (ecode) { + if (ecode) + { LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); return; } - LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ()); - Handshake (socket); - } - + LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ()); + Handshake (newSocket); + } + + template void I2PControlService::Handshake (std::shared_ptr socket) { socket->async_handshake(boost::asio::ssl::stream_base::server, - std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket)); - } - - void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) - { - if (ecode) { - LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); - return; - } - //std::this_thread::sleep_for (std::chrono::milliseconds(5)); - ReadRequest (socket); + [this, socket](const boost::system::error_code& ecode) + { + if (ecode) + { + LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); + return; + } + ReadRequest (socket); + }); } + template void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); @@ -177,10 +216,13 @@ namespace client #else boost::asio::buffer (request->data (), request->size ()), #endif - std::bind(&I2PControlService::HandleRequestReceived, this, - std::placeholders::_1, std::placeholders::_2, socket, request)); + [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) + { + HandleRequestReceived (ecode, bytes_transferred, socket, request); + }); } + template void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) @@ -258,6 +300,7 @@ namespace client } } + template void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -267,7 +310,7 @@ namespace client std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; - header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; + header << "Content-Length: " << std::to_string(len) << "\r\n"; header << "Content-Type: application/json\r\n"; header << "Date: "; std::time_t t = std::time (nullptr); @@ -280,16 +323,11 @@ namespace client memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), - std::bind(&I2PControlService::HandleResponseSent, this, - std::placeholders::_1, std::placeholders::_2, socket, buf)); - } - - void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf) - { - if (ecode) { - LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); - } + [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); + }); } // handlers diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index ff32c131..83dd6549 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -35,8 +35,6 @@ namespace client class I2PControlService: public I2PControlHandlers { - typedef boost::asio::ssl::stream ssl_socket; - public: I2PControlService (const std::string& address, int port); @@ -49,16 +47,18 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + template + void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr newSocket); + template void Handshake (std::shared_ptr socket); - void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); + template void ReadRequest (std::shared_ptr socket); + template void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + template void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); - void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); @@ -86,10 +86,13 @@ namespace client std::string m_Password; bool m_IsRunning; - std::thread * m_Thread; + std::unique_ptr m_Thread; boost::asio::io_context m_Service; - boost::asio::ip::tcp::acceptor m_Acceptor; + std::unique_ptr m_Acceptor; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + std::unique_ptr m_LocalAcceptor; +#endif boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 7885578e..8e6dbcf6 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -52,7 +52,7 @@ namespace transport { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: Starting"); - m_Service.post (std::bind (&UPnP::Discover, this)); + boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum @@ -150,7 +150,7 @@ namespace transport // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); + i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress)); // port mapping PortMapping (); } diff --git a/debian/changelog b/debian/changelog index fd7b8d57..d170f534 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +i2pd (2.56.0-1) unstable; urgency=medium + + * updated to version 2.56.0/0.9.65 + + -- orignal Tue, 11 Feb 2025 16:00:00 +0000 + +i2pd (2.55.0-1) unstable; urgency=medium + + * updated to version 2.55.0 + + -- orignal Mon, 30 Dec 2024 16:00:00 +0000 + i2pd (2.54.0-1) unstable; urgency=medium * updated to version 2.54.0/0.9.64 diff --git a/debian/patches/01-upnp.patch b/debian/patches/01-upnp.patch index bec8f2b0..74d36c06 100644 --- a/debian/patches/01-upnp.patch +++ b/debian/patches/01-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2022-03-23 +Last-Update: 2024-12-30 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ include filelist.mk +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index b582a06a..b69c42ef 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace afrikaans // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"failed", "Het misluk"}, {"unknown", "onbekend"}, diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp index b99e5032..67955d8a 100644 --- a/i18n/Armenian.cpp +++ b/i18n/Armenian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace armenian // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f ԿիԲ"}, {"%.2f MiB", "%.2f ՄիԲ"}, diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp index ad46178c..e3b63ebd 100644 --- a/i18n/Chinese.cpp +++ b/i18n/Chinese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace chinese // language namespace return 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Czech.cpp b/i18n/Czech.cpp index 3b865474..94803354 100644 --- a/i18n/Czech.cpp +++ b/i18n/Czech.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace czech // language namespace return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/English.cpp b/i18n/English.cpp index 2670e984..fb774527 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,7 +30,7 @@ namespace english // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"", ""}, }; diff --git a/i18n/French.cpp b/i18n/French.cpp index 999f82b9..985296a3 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace french // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f Kio"}, {"%.2f MiB", "%.2f Mio"}, diff --git a/i18n/German.cpp b/i18n/German.cpp index 02662e8e..90ce82f4 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace german // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/I18N.cpp b/i18n/I18N.cpp index cf4873eb..48a02357 100644 --- a/i18n/I18N.cpp +++ b/i18n/I18N.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,12 +30,12 @@ namespace i18n } } - std::string translate (const std::string& arg) + std::string_view translate (std::string_view arg) { return i2p::client::context.GetLanguage ()->GetString (arg); } - std::string translate (const std::string& arg, const std::string& arg2, const int& n) + std::string translate (const std::string& arg, const std::string& arg2, const int n) { return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); } diff --git a/i18n/I18N.h b/i18n/I18N.h index 6ec5b16e..8ed77a6b 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #define __I18N_H__ #include +#include #include #include #include @@ -18,12 +19,13 @@ namespace i2p { namespace i18n { + typedef std::map LocaleStrings; class Locale { public: Locale ( const std::string& language, - const std::map& strings, + const LocaleStrings& strings, const std::map>& plurals, std::function formula ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; @@ -34,7 +36,7 @@ namespace i18n return m_Language; } - std::string GetString (const std::string& arg) const + std::string_view GetString (std::string_view arg) const { const auto it = m_Strings.find(arg); if (it == m_Strings.end()) @@ -47,7 +49,7 @@ namespace i18n } } - std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const + std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const { const auto it = m_Plurals.find(arg2); if (it == m_Plurals.end()) // not found, fallback to english @@ -63,14 +65,14 @@ namespace i18n private: const std::string m_Language; - const std::map m_Strings; + const LocaleStrings m_Strings; const std::map> m_Plurals; std::function m_Formula; }; void SetLanguage(const std::string &lang); - std::string translate (const std::string& arg); - std::string translate (const std::string& arg, const std::string& arg2, const int& n); + std::string_view translate (std::string_view arg); + std::string translate (const std::string& arg, const std::string& arg2, int n); } // i18n } // i2p @@ -79,7 +81,7 @@ namespace i18n * @param arg String with message */ template -std::string tr (TValue&& arg) +std::string_view tr (TValue&& arg) { return i2p::i18n::translate(std::forward(arg)); } @@ -92,7 +94,7 @@ std::string tr (TValue&& arg) template std::string tr (TValue&& arg, TArgs&&... args) { - std::string tr_str = i2p::i18n::translate(std::forward(arg)); + std::string tr_str = std::string (i2p::i18n::translate(std::forward(arg))); // TODO: size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); std::string str(size, 0); @@ -108,7 +110,7 @@ std::string tr (TValue&& arg, TArgs&&... args) * @param n Integer, used for selection of form */ template -std::string ntr (TValue&& arg, TValue2&& arg2, int& n) +std::string ntr (TValue&& arg, TValue2&& arg2, int n) { return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); } @@ -121,7 +123,7 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int& n) * @param args Array of arguments for string formatting */ template -std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args) +std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) { std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp index 2dcaab5e..0ae26f21 100644 --- a/i18n/Italian.cpp +++ b/i18n/Italian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace italian // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp index b2abda11..0e8df096 100644 --- a/i18n/Polish.cpp +++ b/i18n/Polish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023-2024, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace polish // language namespace return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp index 0c490ba3..26204dc3 100644 --- a/i18n/Portuguese.cpp +++ b/i18n/Portuguese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023-2024, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace portuguese // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 15952710..235cc0ae 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace russian // language namespace return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f КиБ"}, {"%.2f MiB", "%.2f МиБ"}, diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp index a5ecc30a..0e657fb4 100644 --- a/i18n/Spanish.cpp +++ b/i18n/Spanish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace spanish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp index 05ed1e18..df13d22f 100644 --- a/i18n/Swedish.cpp +++ b/i18n/Swedish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace swedish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp index d4398ebe..9946b336 100644 --- a/i18n/Turkish.cpp +++ b/i18n/Turkish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace turkish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 35ee0f89..7efb8891 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace turkmen // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index d089c142..c1b6c772 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace ukrainian // language namespace return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f КіБ"}, {"%.2f MiB", "%.2f МіБ"}, diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp index cf94a489..8e870772 100644 --- a/i18n/Uzbek.cpp +++ b/i18n/Uzbek.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace uzbek // language namespace return n > 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index b8de571b..dc331e3e 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,11 +27,6 @@ namespace data { return T32; } - - bool IsBase32 (char ch) - { - return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7'); - } static void iT64Build(void); @@ -59,11 +54,6 @@ namespace data { return T64; } - - bool IsBase64 (char ch) - { - return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~'; - } /* * Reverse Substitution Table (built in run time) @@ -234,21 +224,12 @@ namespace data return outCount; } - - size_t Base64EncodingBufferSize (const size_t input_size) - { - auto d = div (input_size, 3); - if (d.rem) - d.quot++; - - return 4 * d.quot; - } - - std::string ToBase64Standard (const std::string& in) + + std::string ToBase64Standard (std::string_view in) { auto len = Base64EncodingBufferSize (in.length ()); char * str = new char[len + 1]; - auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len); + auto l = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length (), str, len); str[l] = 0; // replace '-' by '+' and '~' by '/' for (size_t i = 0; i < l; i++) @@ -280,13 +261,12 @@ namespace data iT64[(int)P64] = 0; } - size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen) + size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen) { unsigned int tmp = 0, bits = 0; size_t ret = 0; - for (size_t i = 0; i < len; i++) + for (auto ch: base32Str) { - char ch = inBuf[i]; if (ch >= '2' && ch <= '7') // digit ch = (ch - '2') + 26; // 26 means a-z else if (ch >= 'a' && ch <= 'z') @@ -306,13 +286,15 @@ namespace data tmp <<= 5; } return ret; - } - - size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen) + } + + std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len) { - size_t ret = 0, pos = 1; + std::string out; + out.reserve ((len * 8 + 4) / 5); + size_t pos = 1; unsigned int bits = 8, tmp = inBuf[0]; - while (ret < outLen && (bits > 0 || pos < len)) + while (bits > 0 || pos < len) { if (bits < 5) { @@ -332,10 +314,9 @@ namespace data bits -= 5; int ind = (tmp >> bits) & 0x1F; - outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2'); - ret++; + out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2')); } - return ret; - } + return out; + } } } diff --git a/libi2pd/Base.h b/libi2pd/Base.h index a163435c..3bdbe211 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,27 +11,42 @@ #include #include -#include +#include +#include -namespace i2p { -namespace data { +namespace i2p +{ +namespace data +{ size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); const char * GetBase32SubstitutionTable (); const char * GetBase64SubstitutionTable (); - bool IsBase64 (char ch); + constexpr bool IsBase64 (char ch) + { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~'; + } - size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); - size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); - bool IsBase32 (char ch); + size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen); + std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len); + + constexpr bool IsBase32 (char ch) + { + return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7'); + } /** * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes */ - size_t Base64EncodingBufferSize(const size_t input_size); - - std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization + constexpr size_t Base64EncodingBufferSize(size_t input_size) + { + auto d = std::div (input_size, 3); + if (d.rem) d.quot++; + return 4 * d.quot; + } + std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization + } // data } // i2p diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index ced086e1..a661b428 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -152,11 +152,11 @@ namespace data m_BlindedSigType = m_SigType; } - BlindedPublicKey::BlindedPublicKey (const std::string& b33): + BlindedPublicKey::BlindedPublicKey (std::string_view b33): m_SigType (0) // 0 means invalid, we can't blind DSA, set it later { uint8_t addr[40]; // TODO: define length from b33 - size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); + size_t l = i2p::data::Base32ToByteStream (b33, addr, 40); if (l < 32) { LogPrint (eLogError, "Blinding: Malformed b33 ", b33); @@ -198,7 +198,7 @@ namespace data std::string BlindedPublicKey::ToB33 () const { if (m_PublicKey.size () > 32) return ""; // assume 25519 - uint8_t addr[35]; char str[60]; // TODO: define actual length + uint8_t addr[35]; uint8_t flags = 0; if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; addr[0] = flags; // flags @@ -208,8 +208,7 @@ namespace data uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); // checksum is Little Endian addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); - auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); - return std::string (str, str + l); + return ByteStreamToBase32 (addr, m_PublicKey.size () + 3); } void BlindedPublicKey::GetCredential (uint8_t * credential) const diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index c78db003..fc11f613 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include "Identity.h" @@ -23,7 +24,7 @@ namespace data public: BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p std::string ToB33 () const; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp deleted file mode 100644 index 77820c88..00000000 --- a/libi2pd/CPU.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Copyright (c) 2013-2023, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include "CPU.h" -#include "Log.h" - -#ifndef bit_AES - #define bit_AES (1 << 25) -#endif - -#if defined(__GNUC__) && __GNUC__ < 6 && IS_X86 - #include -#endif - -#ifdef _MSC_VER - #include -#endif - -namespace i2p -{ -namespace cpu -{ - bool aesni = false; - - inline bool cpu_support_aes() - { -#if IS_X86 -#if defined(__clang__) -# if (__clang_major__ >= 6) - __builtin_cpu_init(); -# endif - return __builtin_cpu_supports("aes"); -#elif (defined(__GNUC__) && __GNUC__ >= 6) - __builtin_cpu_init(); - return __builtin_cpu_supports("aes"); -#elif (defined(__GNUC__) && __GNUC__ < 6) - int cpu_info[4]; - bool flag = false; - __cpuid(0, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]); - if (cpu_info[0] >= 0x00000001) { - __cpuid(0x00000001, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]); - flag = ((cpu_info[2] & bit_AES) != 0); - } - return flag; -#elif defined(_MSC_VER) - int cpu_info[4]; - __cpuid(cpu_info, 1); - return ((cpu_info[2] & bit_AES) != 0); -#endif -#endif - return false; - } - - void Detect(bool AesSwitch, bool force) - { - if ((cpu_support_aes() && AesSwitch) || (AesSwitch && force)) { - aesni = true; - } - - LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled")); - } -} -} diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index 1c30db48..3fc38d47 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,20 +21,4 @@ # define IS_X86_64 0 #endif -#if defined(__AES__) && !defined(_MSC_VER) && IS_X86 -# define SUPPORTS_AES 1 -#else -# define SUPPORTS_AES 0 -#endif - -namespace i2p -{ -namespace cpu -{ - extern bool aesni; - - void Detect(bool AesSwitch, bool force); -} -} - #endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 5add5f1e..3da74da0 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -117,7 +117,7 @@ namespace config { ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") - ("httpproxy.senduseragent", value()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by deafult") + ("httpproxy.senduseragent", value()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by default") ("httpproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("httpproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") @@ -154,6 +154,17 @@ namespace config { ("socksproxy.i2p.streaming.profile", value()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") ; + options_description shareddest("Shared local destination options"); + shareddest.add_options() + ("shareddest.inbound.length", value()->default_value("3"), "Shared local destination inbound tunnel length") + ("shareddest.outbound.length", value()->default_value("3"), "Shared local destination outbound tunnel length") + ("shareddest.inbound.quantity", value()->default_value("3"), "Shared local destination inbound tunnels quantity") + ("shareddest.outbound.quantity", value()->default_value("3"), "Shared local destination outbound tunnels quantity") + ("shareddest.i2cp.leaseSetType", value()->default_value("3"), "Shared local destination's LeaseSet type") + ("shareddest.i2cp.leaseSetEncType", value()->default_value("0,4"), "Shared local destination's LeaseSet encryption type") + ("shareddest.i2p.streaming.profile", value()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") + ; + options_description sam("SAM bridge options"); sam.add_options() ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") @@ -227,7 +238,7 @@ namespace config { "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," "https://reseed.i2pgit.org/," - "https://banana.incognet.io/," + "https://coconut.incognet.io/," "https://reseed-pl.i2pd.xyz/," "https://www2.mk16.de/," "https://i2p.ghativega.in/," @@ -315,11 +326,11 @@ namespace config { ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; - options_description cpuext("CPU encryption extensions options"); + options_description cpuext("CPU encryption extensions options. Deprecated"); cpuext.add_options() - ("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used") + ("cpuext.aesni", bool_switch()->default_value(true), "Deprecated option") ("cpuext.avx", bool_switch()->default_value(false), "Deprecated option") - ("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines") + ("cpuext.force", bool_switch()->default_value(false), "Deprecated option") ; options_description meshnets("Meshnet transports options"); @@ -341,6 +352,7 @@ namespace config { .add(httpserver) .add(httpproxy) .add(socksproxy) + .add(shareddest) .add(sam) .add(bob) .add(i2cp) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 2371b529..095f25d6 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,7 @@ #if OPENSSL_HKDF #include #endif +#include "CPU.h" #include "Crypto.h" #include "Ed25519.h" #include "I2PEndian.h" @@ -441,9 +442,8 @@ namespace crypto // encrypt CBCEncryption encryption; encryption.SetKey (shared); - encryption.SetIV (iv); encrypted[257] = 0; - encryption.Encrypt (m, 256, encrypted + 258); + encryption.Encrypt (m, 256, iv, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); BN_CTX_free (ctx); @@ -476,8 +476,7 @@ namespace crypto uint8_t m[256]; CBCDecryption decryption; decryption.SetKey (shared); - decryption.SetIV (iv); - decryption.Decrypt (encrypted + 258, 256, m); + decryption.Decrypt (encrypted + 258, 256, iv, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -515,440 +514,114 @@ namespace crypto } // AES -#if SUPPORTS_AES - #define KeyExpansion256(round0,round1) \ - "pshufd $0xff, %%xmm2, %%xmm2 \n" \ - "movaps %%xmm1, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pxor %%xmm2, %%xmm1 \n" \ - "movaps %%xmm1, "#round0"(%[sched]) \n" \ - "aeskeygenassist $0, %%xmm1, %%xmm4 \n" \ - "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ - "movaps %%xmm3, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pxor %%xmm2, %%xmm3 \n" \ - "movaps %%xmm3, "#round1"(%[sched]) \n" -#endif - -#if SUPPORTS_AES - void ECBCryptoAESNI::ExpandKey (const AESKey& key) + ECBEncryption::ECBEncryption () { - __asm__ - ( - "movups (%[key]), %%xmm1 \n" - "movups 16(%[key]), %%xmm3 \n" - "movaps %%xmm1, (%[sched]) \n" - "movaps %%xmm3, 16(%[sched]) \n" - "aeskeygenassist $1, %%xmm3, %%xmm2 \n" - KeyExpansion256(32,48) - "aeskeygenassist $2, %%xmm3, %%xmm2 \n" - KeyExpansion256(64,80) - "aeskeygenassist $4, %%xmm3, %%xmm2 \n" - KeyExpansion256(96,112) - "aeskeygenassist $8, %%xmm3, %%xmm2 \n" - KeyExpansion256(128,144) - "aeskeygenassist $16, %%xmm3, %%xmm2 \n" - KeyExpansion256(160,176) - "aeskeygenassist $32, %%xmm3, %%xmm2 \n" - KeyExpansion256(192,208) - "aeskeygenassist $64, %%xmm3, %%xmm2 \n" - // key expansion final - "pshufd $0xff, %%xmm2, %%xmm2 \n" - "movaps %%xmm1, %%xmm4 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pxor %%xmm2, %%xmm1 \n" - "movups %%xmm1, 224(%[sched]) \n" - : // output - : [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input - : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged - ); + m_Ctx = EVP_CIPHER_CTX_new (); } -#endif - - -#if SUPPORTS_AES - #define EncryptAES256(sched) \ - "pxor (%["#sched"]), %%xmm0 \n" \ - "aesenc 16(%["#sched"]), %%xmm0 \n" \ - "aesenc 32(%["#sched"]), %%xmm0 \n" \ - "aesenc 48(%["#sched"]), %%xmm0 \n" \ - "aesenc 64(%["#sched"]), %%xmm0 \n" \ - "aesenc 80(%["#sched"]), %%xmm0 \n" \ - "aesenc 96(%["#sched"]), %%xmm0 \n" \ - "aesenc 112(%["#sched"]), %%xmm0 \n" \ - "aesenc 128(%["#sched"]), %%xmm0 \n" \ - "aesenc 144(%["#sched"]), %%xmm0 \n" \ - "aesenc 160(%["#sched"]), %%xmm0 \n" \ - "aesenc 176(%["#sched"]), %%xmm0 \n" \ - "aesenc 192(%["#sched"]), %%xmm0 \n" \ - "aesenc 208(%["#sched"]), %%xmm0 \n" \ - "aesenclast 224(%["#sched"]), %%xmm0 \n" -#endif - - void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out) + + ECBEncryption::~ECBEncryption () { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : - : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) - : "%xmm0", "memory" - ); - } - else -#endif - { - AES_encrypt (in->buf, out->buf, &m_Key); - } + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ECBEncryption::Encrypt (const uint8_t * in, uint8_t * out) + { + EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int len; + EVP_EncryptUpdate (m_Ctx, out, &len, in, 16); + EVP_EncryptFinal_ex (m_Ctx, out + len, &len); } -#if SUPPORTS_AES - #define DecryptAES256(sched) \ - "pxor 224(%["#sched"]), %%xmm0 \n" \ - "aesdec 208(%["#sched"]), %%xmm0 \n" \ - "aesdec 192(%["#sched"]), %%xmm0 \n" \ - "aesdec 176(%["#sched"]), %%xmm0 \n" \ - "aesdec 160(%["#sched"]), %%xmm0 \n" \ - "aesdec 144(%["#sched"]), %%xmm0 \n" \ - "aesdec 128(%["#sched"]), %%xmm0 \n" \ - "aesdec 112(%["#sched"]), %%xmm0 \n" \ - "aesdec 96(%["#sched"]), %%xmm0 \n" \ - "aesdec 80(%["#sched"]), %%xmm0 \n" \ - "aesdec 64(%["#sched"]), %%xmm0 \n" \ - "aesdec 48(%["#sched"]), %%xmm0 \n" \ - "aesdec 32(%["#sched"]), %%xmm0 \n" \ - "aesdec 16(%["#sched"]), %%xmm0 \n" \ - "aesdeclast (%["#sched"]), %%xmm0 \n" -#endif - - void ECBDecryption::Decrypt (const ChipherBlock * in, ChipherBlock * out) + ECBDecryption::ECBDecryption () { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : - : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) - : "%xmm0", "memory" - ); - } - else -#endif - { - AES_decrypt (in->buf, out->buf, &m_Key); - } + m_Ctx = EVP_CIPHER_CTX_new (); + } + + ECBDecryption::~ECBDecryption () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out) + { + EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int len; + EVP_DecryptUpdate (m_Ctx, out, &len, in, 16); + EVP_DecryptFinal_ex (m_Ctx, out + len, &len); } -#if SUPPORTS_AES - #define CallAESIMC(offset) \ - "movaps "#offset"(%[shed]), %%xmm0 \n" \ - "aesimc %%xmm0, %%xmm0 \n" \ - "movaps %%xmm0, "#offset"(%[shed]) \n" -#endif - void ECBEncryption::SetKey (const AESKey& key) - { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - ExpandKey (key); - } - else -#endif - { - AES_set_encrypt_key (key, 256, &m_Key); - } + CBCEncryption::CBCEncryption () + { + m_Ctx = EVP_CIPHER_CTX_new (); } - - void ECBDecryption::SetKey (const AESKey& key) + + CBCEncryption::~CBCEncryption () { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - ExpandKey (key); // expand encryption key first - // then invert it using aesimc - __asm__ - ( - CallAESIMC(16) - CallAESIMC(32) - CallAESIMC(48) - CallAESIMC(64) - CallAESIMC(80) - CallAESIMC(96) - CallAESIMC(112) - CallAESIMC(128) - CallAESIMC(144) - CallAESIMC(160) - CallAESIMC(176) - CallAESIMC(192) - CallAESIMC(208) - : - : [shed]"r"(GetKeySchedule ()) - : "%xmm0", "memory" - ); - } - else -#endif - { - AES_set_decrypt_key (key, 256, &m_Key); - } - } - - void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) - { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "cc", "memory" - ); - } - else -#endif - { - for (int i = 0; i < numBlocks; i++) - { - *m_LastBlock.GetChipherBlock () ^= in[i]; - m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ()); - out[i] = *m_LastBlock.GetChipherBlock (); - } - } - } - - void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out) + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void CBCEncryption::Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out) { // len/16 - int numBlocks = len >> 4; - if (numBlocks > 0) - Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); + EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int l; + EVP_EncryptUpdate (m_Ctx, out, &l, in, len); + EVP_EncryptFinal_ex (m_Ctx, out + l, &l); } - void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) - { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - "movups %%xmm0, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); - } - else -#endif - Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + CBCDecryption::CBCDecryption () + { + m_Ctx = EVP_CIPHER_CTX_new (); } - - void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) + + CBCDecryption::~CBCDecryption () { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); - } - else -#endif - { - for (int i = 0; i < numBlocks; i++) - { - ChipherBlock tmp = in[i]; - m_ECBDecryption.Decrypt (in + i, out + i); - out[i] ^= *m_IV.GetChipherBlock (); - *m_IV.GetChipherBlock () = tmp; - } - } - } - - void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out) + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void CBCDecryption::Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out) { - int numBlocks = len >> 4; - if (numBlocks > 0) - Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); - } - - void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) - { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "movups %%xmm0, (%[iv]) \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); - } - else -#endif - Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + // len/16 + EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int l; + EVP_DecryptUpdate (m_Ctx, out, &l, in, len); + EVP_DecryptFinal_ex (m_Ctx, out + l, &l); } void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - // encrypt IV - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - EncryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // encrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched_l) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "cc", "memory" - ); - } - else -#endif - { - m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv - m_LayerEncryption.SetIV (out); - m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data - m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv - } + uint8_t iv[16]; + m_IVEncryption.Encrypt (in, iv); // iv + m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data + m_IVEncryption.Encrypt (iv, out); // double iv } void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) { -#if SUPPORTS_AES - if(i2p::cpu::aesni) - { - __asm__ - ( - // decrypt IV - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - DecryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // decrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched_l) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); - } - else -#endif - { - m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv - m_LayerDecryption.SetIV (out); - m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data - m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv - } + uint8_t iv[16]; + m_IVDecryption.Decrypt (in, iv); // iv + m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data + m_IVDecryption.Decrypt (iv, out); // double iv } // AEAD/ChaCha20/Poly1305 - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) + static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) { - if (len < msgLen) return false; + if (!ctx || len < msgLen) return false; if (encrypt && len < msgLen + 16) return false; bool ret = true; int outlen = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); if (encrypt) { EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); @@ -978,38 +651,100 @@ namespace crypto EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen); ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0; } + return ret; + } + bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) + { + EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new (); + auto ret = AEADChaCha20Poly1305 (ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, encrypt); EVP_CIPHER_CTX_free (ctx); return ret; } - void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac) + AEADChaCha20Poly1305Encryptor::AEADChaCha20Poly1305Encryptor () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + AEADChaCha20Poly1305Encryptor::~AEADChaCha20Poly1305Encryptor () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + bool AEADChaCha20Poly1305Encryptor::Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, true); + } + + void AEADChaCha20Poly1305Encryptor::Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac) { if (bufs.empty ()) return; int outlen = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); - EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); - EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); + EVP_EncryptInit_ex(m_Ctx, EVP_chacha20_poly1305(), 0, 0, 0); + EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); + EVP_EncryptInit_ex(m_Ctx, NULL, NULL, key, nonce); for (const auto& it: bufs) - EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second); - EVP_EncryptFinal_ex(ctx, NULL, &outlen); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); - EVP_CIPHER_CTX_free (ctx); + EVP_EncryptUpdate(m_Ctx, it.first, &outlen, it.first, it.second); + EVP_EncryptFinal_ex(m_Ctx, NULL, &outlen); + EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); + } + + AEADChaCha20Poly1305Decryptor::AEADChaCha20Poly1305Decryptor () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + AEADChaCha20Poly1305Decryptor::~AEADChaCha20Poly1305Decryptor () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + bool AEADChaCha20Poly1305Decryptor::Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false); } - void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) { - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); uint32_t iv[4]; iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv); int outlen = 0; EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen); EVP_EncryptFinal_ex(ctx, NULL, &outlen); + } + + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); + ChaCha20 (ctx, msg, msgLen, key, nonce, out); EVP_CIPHER_CTX_free (ctx); } + + ChaCha20Context::ChaCha20Context () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + ChaCha20Context::~ChaCha20Context () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out); + } + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen) { @@ -1157,9 +892,8 @@ namespace crypto } }*/ - void InitCrypto (bool precomputation, bool aesni, bool force) + void InitCrypto (bool precomputation) { - i2p::cpu::Detect (aesni, force); /* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 75bd7bb2..b2fa0292 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -25,7 +25,6 @@ #include "Base.h" #include "Tag.h" -#include "CPU.h" // recognize openssl version and features #if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 @@ -34,6 +33,9 @@ # if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL # define OPENSSL_SIPHASH 1 # endif +# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0 +# define OPENSSL_PQ 1 +# endif #endif namespace i2p @@ -85,142 +87,70 @@ namespace crypto void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); // AES - struct ChipherBlock - { - uint8_t buf[16]; - - void operator^=(const ChipherBlock& other) // XOR - { - if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ? - { - for (int i = 0; i < 4; i++) - reinterpret_cast(buf)[i] ^= reinterpret_cast(other.buf)[i]; - } - else - { - for (int i = 0; i < 16; i++) - buf[i] ^= other.buf[i]; - } - } - }; - typedef i2p::data::Tag<32> AESKey; - - template - class AESAlignedBuffer // 16 bytes alignment - { - public: - - AESAlignedBuffer () - { - m_Buf = m_UnalignedBuffer; - uint8_t rem = ((size_t)m_Buf) & 0x0f; - if (rem) - m_Buf += (16 - rem); - } - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; }; - const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; }; - - private: - - uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment - uint8_t * m_Buf; - }; - - -#if SUPPORTS_AES - class ECBCryptoAESNI - { - public: - - uint8_t * GetKeySchedule () { return m_KeySchedule; }; - - protected: - - void ExpandKey (const AESKey& key); - - private: - - AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes - }; -#endif - -#if SUPPORTS_AES - class ECBEncryption: public ECBCryptoAESNI -#else + class ECBEncryption -#endif { public: - void SetKey (const AESKey& key); + ECBEncryption (); + ~ECBEncryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; + void Encrypt(const uint8_t * in, uint8_t * out); - void Encrypt(const ChipherBlock * in, ChipherBlock * out); + private: - private: - AES_KEY m_Key; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; -#if SUPPORTS_AES - class ECBDecryption: public ECBCryptoAESNI -#else class ECBDecryption -#endif { public: - void SetKey (const AESKey& key); - void Decrypt (const ChipherBlock * in, ChipherBlock * out); + ECBDecryption (); + ~ECBDecryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; + void Decrypt (const uint8_t * in, uint8_t * out); + private: - AES_KEY m_Key; + + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class CBCEncryption { public: - CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); }; - - void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes - void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes - void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); }; - - void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out); - void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out); - void Encrypt (const uint8_t * in, uint8_t * out); // one block - - ECBEncryption & ECB() { return m_ECBEncryption; } + CBCEncryption (); + ~CBCEncryption (); + void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes + void Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out); + private: - AESAlignedBuffer<16> m_LastBlock; - - ECBEncryption m_ECBEncryption; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class CBCDecryption { public: - CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); }; - - void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes - void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes - void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); }; - - void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out); - void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out); - void Decrypt (const uint8_t * in, uint8_t * out); // one block - - ECBDecryption & ECB() { return m_ECBDecryption; } + CBCDecryption (); + ~CBCDecryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes + void Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out); private: - AESAlignedBuffer<16> m_IV; - ECBDecryption m_ECBDecryption; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class TunnelEncryption // with double IV encryption @@ -260,13 +190,58 @@ namespace crypto }; // AEAD/ChaCha20/Poly1305 - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag - void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad + class AEADChaCha20Poly1305Encryptor + { + public: + AEADChaCha20Poly1305Encryptor (); + ~AEADChaCha20Poly1305Encryptor (); + + bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag + + void Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + + class AEADChaCha20Poly1305Decryptor + { + public: + + AEADChaCha20Poly1305Decryptor (); + ~AEADChaCha20Poly1305Decryptor (); + + bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + + bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag + // ChaCha20 void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + class ChaCha20Context + { + public: + + ChaCha20Context (); + ~ChaCha20Context (); + void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + // HKDF void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 @@ -288,7 +263,7 @@ namespace crypto void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) // init and terminate - void InitCrypto (bool precomputation, bool aesni, bool force); + void InitCrypto (bool precomputation); void TerminateCrypto (); } } diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 1e0c06cc..732efca7 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -104,8 +104,7 @@ namespace datagram if (verified) { - auto h = identity.GetIdentHash(); - auto session = ObtainSession(h); + auto session = ObtainSession (identity.GetIdentHash()); session->Ack(); auto r = FindReceiver(toPort); if(r) @@ -381,15 +380,19 @@ namespace datagram if (!found) { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) - m_PendingRoutingSessions.push_back (m_RoutingSession); + if (m_RoutingSession) + { + m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL); + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) + m_PendingRoutingSessions.push_back (m_RoutingSession); + } } } auto path = m_RoutingSession->GetSharedRoutingPath(); - if (path && m_RoutingSession->IsRatchets () && (m_RoutingSession->CleanupUnconfirmedTags () || - m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT)) + if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) { + LogPrint (eLogDebug, "Datagram: path reset"); m_RoutingSession->SetSharedRoutingPath (nullptr); path = nullptr; } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 5a0bfc93..dd358434 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,8 +31,6 @@ namespace datagram { // milliseconds for max session idle time const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; - // milliseconds for how long we try sticking to a dead routing path before trying to switch - const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000; // milliseconds interval a routing path is used before switching const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; // milliseconds before lease expire should we try switching leases @@ -44,6 +42,7 @@ namespace datagram // max 64 messages buffered in send queue for each datagram session const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds + const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds class DatagramSession : public std::enable_shared_from_this { diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 2d1aa2c8..be079ee3 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -195,7 +195,7 @@ namespace client m_IsPublic = itr->second != "true"; } - int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency; + int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0; std::map intOpts = { {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, @@ -597,7 +597,8 @@ namespace client m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; // schedule verification - m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + + (m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0))); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); } @@ -676,8 +677,8 @@ namespace client m_ExcludedFloodfills.clear (); m_PublishReplyToken = 1; // dummy non-zero value // try again after a while - LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds"); - m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); + LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds"); + m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); return; @@ -696,7 +697,7 @@ namespace client s->HandlePublishConfirmationTimer (boost::system::error_code()); }); }; - m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); + m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg); @@ -712,15 +713,15 @@ namespace client m_PublishReplyToken = 0; if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds or failed. will try again"); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); Publish (); } else { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); // Java floodfill never sends confirmation back for unknown crypto type // assume it successive and try to verify - m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); @@ -833,7 +834,7 @@ namespace client request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2 if (requestComplete) request->requestComplete.push_back (requestComplete); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ret = m_LeaseSetRequests.insert (std::pair >(dest,request)); if (ret.second) // inserted { @@ -916,7 +917,7 @@ namespace client nextFloodfill->GetIdentHash (), 0, msg } }); - request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); + request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT)); request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer, shared_from_this (), std::placeholders::_1, dest)); } @@ -933,7 +934,7 @@ namespace client if (it != m_LeaseSetRequests.end ()) { bool done = false; - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT) { auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded); diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 7516c90f..4278f1fd 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -36,12 +36,13 @@ namespace client const uint8_t PROTOCOL_TYPE_STREAMING = 6; const uint8_t PROTOCOL_TYPE_DATAGRAM = 17; const uint8_t PROTOCOL_TYPE_RAW = 18; - const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds - const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish + const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds + const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish + const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds const int PUBLISH_MIN_INTERVAL = 20; // in seconds const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically - const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds - const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds + const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds + const int MAX_LEASESET_REQUEST_TIMEOUT = 12000; // in milliseconds const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index ffd47a31..bdf5f7f7 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -725,6 +725,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { + auto owner = GetOwner (); + if (!owner) return false; uint8_t nonce[12]; auto index = m_SendTagset->GetNextIndex (); CreateNonce (index, nonce); // tag's index @@ -732,8 +734,7 @@ namespace garlic if (!tag) { LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); - if (GetOwner ()) - GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + owner->RemoveECIESx25519Session (m_RemoteStaticKey); return false; } memcpy (out, &tag, 8); @@ -741,7 +742,7 @@ namespace garlic // ciphertext = ENCRYPT(k, n, payload, ad) uint8_t key[32]; m_SendTagset->GetSymmKey (index, key); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt + if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -760,34 +761,35 @@ namespace garlic uint8_t * payload = buf + 8; uint8_t key[32]; receiveTagset->GetSymmKey (index, key); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt + auto owner = GetOwner (); + if (!owner) return true; // drop message + + if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } HandlePayload (payload, len - 16, receiveTagset, index); - if (GetOwner ()) + + int moreTags = 0; + if (owner->GetNumRatchetInboundTags () > 0) // override in settings? { - int moreTags = 0; - if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings? - { - if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) - moreTags = GetOwner ()->GetNumRatchetInboundTags (); - index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind - } - else - { - moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset - (ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2 - if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; - moreTags -= (receiveTagset->GetNextIndex () - index); - index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind - } - if (moreTags > 0) - GenerateMoreReceiveTags (receiveTagset, moreTags); - if (index > 0) - receiveTagset->SetTrimBehind (index); + if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2) + moreTags = owner->GetNumRatchetInboundTags (); + index -= owner->GetNumRatchetInboundTags (); // trim behind } + else + { + moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset + (ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2 + if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; + moreTags -= (receiveTagset->GetNextIndex () - index); + index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind + } + if (moreTags > 0) + GenerateMoreReceiveTags (receiveTagset, moreTags); + if (index > 0) + receiveTagset->SetTrimBehind (index); return true; } @@ -911,7 +913,7 @@ namespace garlic } } if (!sendAckRequest && !first && - ((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + ECIESX25519_ACK_REQUEST_INTERVAL) || // regular request + ((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request (m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again { // not LeaseSet diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 960dedb8..d17565a8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,7 +33,7 @@ namespace garlic const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds - const int ECIESX25519_ACK_REQUEST_INTERVAL = 33000; // in milliseconds + const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3; const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; @@ -164,7 +164,7 @@ namespace garlic ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg) override; std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } @@ -180,11 +180,12 @@ namespace garlic bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } - bool IsRatchets () const { return true; }; - bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; }; - bool IsTerminated () const { return m_IsTerminated; } - uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; - bool CleanupUnconfirmedTags (); // return true if unaswered Ack requests, called from I2CP + bool IsRatchets () const override { return true; }; + bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; + bool IsTerminated () const override { return m_IsTerminated; } + uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; }; + void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; }; + bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP protected: @@ -192,7 +193,7 @@ namespace garlic void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; }; void CreateNonce (uint64_t seqn, uint8_t * nonce); void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); - bool MessageConfirmed (uint32_t msgID); + bool MessageConfirmed (uint32_t msgID) override; private: @@ -235,6 +236,7 @@ namespace garlic uint64_t m_LastAckRequestSendTime = 0; // milliseconds uint32_t m_AckRequestMsgID = 0; int m_AckRequestNumAttempts = 0; + int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds public: diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index 89e825f1..f8cf9c0a 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -90,10 +90,10 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key) const + std::string_view signature, const char * key) const { uint8_t buf[100], signatureBuf[64]; - size_t len = family.length (), signatureLen = strlen (signature); + size_t len = family.length (); if (len + 32 > 100) { LogPrint (eLogError, "Family: ", family, " is too long"); @@ -105,7 +105,7 @@ namespace data memcpy (buf, family.c_str (), len); memcpy (buf + len, (const uint8_t *)ident, 32); len += 32; - auto signatureBufLen = Base64ToByteStream (signature, signatureLen, signatureBuf, 64); + auto signatureBufLen = Base64ToByteStream (signature.data (), signature.length (), signatureBuf, 64); if (signatureBufLen) { EVP_MD_CTX * ctx = EVP_MD_CTX_create (); diff --git a/libi2pd/Family.h b/libi2pd/Family.h index a82e3042..fcf61082 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include #include "Identity.h" @@ -28,7 +29,7 @@ namespace data ~Families (); void LoadCertificates (); bool VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key = nullptr) const; + std::string_view signature, const char * key = nullptr) const; FamilyID GetFamilyID (const std::string& family) const; private: diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index b6f6d131..89737db0 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -160,7 +160,7 @@ namespace garlic uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); m_Destination->Encrypt ((uint8_t *)&elGamal, buf); - m_Encryption.SetIV (iv); + m_IV = iv; buf += 514; len += 514; } @@ -170,7 +170,7 @@ namespace garlic memcpy (buf, tag, 32); uint8_t iv[32]; // IV is first 16 bytes SHA256(tag, 32, iv); - m_Encryption.SetIV (iv); + m_IV = iv; buf += 32; len += 32; } @@ -210,7 +210,7 @@ namespace garlic size_t rem = blockSize % 16; if (rem) blockSize += (16-rem); //padding - m_Encryption.Encrypt(buf, blockSize, buf); + m_Encryption.Encrypt(buf, blockSize, m_IV, buf); return blockSize; } @@ -514,8 +514,7 @@ namespace garlic { uint8_t iv[32]; // IV is first 16 bytes SHA256(buf, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt (buf + 32, length - 32, buf + 32); + decryption->Decrypt (buf + 32, length - 32, iv, buf + 32); HandleAESBlock (buf + 32, length - 32, decryption, msg->from); found = true; } @@ -533,8 +532,7 @@ namespace garlic auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt(buf + 514, length - 514, buf + 514); + decryption->Decrypt(buf + 514, length - 514, iv, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) @@ -550,7 +548,7 @@ namespace garlic LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); } else - LogPrint (eLogWarning, "Garlic: Incoming sessions come too ofter"); + LogPrint (eLogWarning, "Garlic: Incoming sessions come too often"); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -1105,5 +1103,17 @@ namespace garlic m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; return m_PayloadBuffer; } + + bool GarlicDestination::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + bool GarlicDestination::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 57c3fddc..6283d740 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -116,7 +116,8 @@ namespace garlic virtual bool IsReadyToSend () const { return true; }; virtual bool IsTerminated () const { return !GetOwner (); }; virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only - + virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -205,6 +206,7 @@ namespace garlic std::map > m_UnconfirmedTagsMsgs; // msgID->tags i2p::crypto::CBCEncryption m_Encryption; + i2p::data::Tag<16> m_IV; public: @@ -241,6 +243,11 @@ namespace garlic void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessageForRouter (std::shared_ptr router, std::shared_ptr msg); + + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag @@ -286,7 +293,7 @@ namespace garlic std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet - uint64_t m_LastIncomingSessionTimestamp; // in millseconds + uint64_t m_LastIncomingSessionTimestamp; // in milliseconds // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; @@ -294,7 +301,10 @@ namespace garlic // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session - + // encryption + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + public: // for HTTP only diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 258d3ada..3cd5c193 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #include #include #include +#include #include "util.h" #include "Base.h" #include "HTTP.h" @@ -18,54 +19,51 @@ namespace i2p { namespace http { - const std::vector HTTP_METHODS = { + // list of valid HTTP methods + static constexpr std::array HTTP_METHODS = + { "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 }; - const std::vector HTTP_VERSIONS = { + + // list of valid HTTP versions + static constexpr std::array HTTP_VERSIONS = + { "HTTP/1.0", "HTTP/1.1" }; - const std::vector weekdays = { + + static constexpr std::array weekdays = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - const std::vector months = { + + static constexpr std::array months = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - inline bool is_http_version(const std::string & str) { + static inline bool is_http_version(std::string_view str) + { return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); } - inline bool is_http_method(const std::string & str) { + static inline bool is_http_method(std::string_view str) + { return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); } - static void strsplit(std::stringstream& ss, std::vector &tokens, char delim, std::size_t limit = 0) - { - std::size_t count = 0; - std::string token; - while (1) + static void strsplit(std::string_view line, std::vector &tokens, char delim, std::size_t limit = 0) + { + size_t count = 0, pos; + while ((pos = line.find (delim)) != line.npos) { count++; - if (limit > 0 && count >= limit) - delim = '\n'; /* reset delimiter */ - if (!std::getline(ss, token, delim)) - break; - tokens.push_back(token); + if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter + tokens.push_back (line.substr (0, pos)); + line = line.substr (pos + 1); } - } - - static void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) - { - std::stringstream ss{line}; - strsplit (ss, tokens, delim, limit); - } - - static void strsplit(std::string_view line, std::vector &tokens, char delim, std::size_t limit = 0) - { - std::stringstream ss{std::string(line)}; - strsplit (ss, tokens, delim, limit); + if (!line.empty ()) tokens.push_back (line); } static std::pair parse_header_line(std::string_view line) @@ -211,8 +209,9 @@ namespace http return true; } - bool URL::parse_query(std::map & params) { - std::vector tokens; + bool URL::parse_query(std::map & params) + { + std::vector tokens; strsplit(query, tokens, '&'); params.clear(); @@ -308,8 +307,9 @@ namespace http if (expect == REQ_LINE) { std::string_view line = str.substr(pos, eol - pos); - std::vector tokens; + std::vector tokens; strsplit(line, tokens, ' '); + if (tokens.size() != 3) return -1; if (!is_http_method(tokens[0])) @@ -333,11 +333,11 @@ namespace http else return -1; } - pos = eol + strlen(CRLF); + pos = eol + CRLF.length(); if (pos >= eoh) break; } - return eoh + strlen(HTTP_EOH); + return eoh + HTTP_EOH.length(); } void HTTPReq::write(std::ostream & o) @@ -381,7 +381,7 @@ namespace http } } - std::string HTTPReq::GetHeader (const std::string& name) const + std::string HTTPReq::GetHeader (std::string_view name) const { for (auto& it : headers) if (it.first == name) @@ -389,7 +389,7 @@ namespace http return ""; } - size_t HTTPReq::GetNumHeaders (const std::string& name) const + size_t HTTPReq::GetNumHeaders (std::string_view name) const { size_t num = 0; for (auto& it : headers) @@ -451,13 +451,15 @@ namespace http if (expect == RES_LINE) { std::string_view line = str.substr(pos, eol - pos); - std::vector tokens; + std::vector tokens; strsplit(line, tokens, ' ', 3); if (tokens.size() != 3) return -1; if (!is_http_version(tokens[0])) return -1; - code = atoi(tokens[1].c_str()); + auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); + if (res.ec != std::errc()) + return -1; if (code < 100 || code >= 600) return -1; /* all ok */ @@ -474,11 +476,11 @@ namespace http else return -1; } - pos = eol + strlen(CRLF); + pos = eol + CRLF.length(); if (pos >= eoh) break; } - return eoh + strlen(HTTP_EOH); + return eoh + HTTP_EOH.length(); } std::string HTTPRes::to_string() { @@ -503,9 +505,11 @@ namespace http return ss.str(); } - const char * HTTPCodeToStatus(int code) { - const char *ptr; - switch (code) { + std::string_view HTTPCodeToStatus(int code) + { + std::string_view ptr; + switch (code) + { case 105: ptr = "Name Not Resolved"; break; /* success */ case 200: ptr = "OK"; break; diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 438ef953..c65c1ce4 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,10 +21,8 @@ namespace i2p { namespace http { - const char CRLF[] = "\r\n"; /**< HTTP line terminator */ - const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ - extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ + constexpr std::string_view CRLF = "\r\n"; /**< HTTP line terminator */ + constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */ struct URL { @@ -103,8 +101,8 @@ namespace http void UpdateHeader (const std::string& name, const std::string& value); void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; - std::string GetHeader (const std::string& name) const; - size_t GetNumHeaders (const std::string& name) const; + std::string GetHeader (std::string_view name) const; + size_t GetNumHeaders (std::string_view name) const; size_t GetNumHeaders () const { return headers.size (); }; }; @@ -154,7 +152,7 @@ namespace http * @param code HTTP code [100, 599] * @return Immutable string with status */ - const char * HTTPCodeToStatus(int code); + std::string_view HTTPCodeToStatus(int code); /** * @brief Replaces %-encoded characters in string with their values diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 2b8b454e..93d7ec08 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,18 +27,15 @@ namespace data size_t Identity::FromBuffer (const uint8_t * buf, size_t len) { - if ( len < DEFAULT_IDENTITY_SIZE ) { - // buffer too small, don't overflow - return 0; - } - memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE); + if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow + memcpy (this, buf, DEFAULT_IDENTITY_SIZE); return DEFAULT_IDENTITY_SIZE; } IdentHash Identity::Hash () const { IdentHash hash; - SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash); + SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash); return hash; } @@ -122,6 +119,16 @@ namespace data memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH); break; } +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + { + memcpy (m_StandardIdentity, signingKey, 384); + excessLen = i2p::crypto::MLDSA44_PUBLIC_KEY_LENGTH - 384; + excessBuf = new uint8_t[excessLen]; + memcpy (excessBuf, signingKey + 384, excessLen); + break; + } +#endif default: LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported"); } @@ -262,11 +269,11 @@ namespace data return fullLen; } - size_t IdentityEx::FromBase64(const std::string& s) + size_t IdentityEx::FromBase64(std::string_view s) { const size_t slen = s.length(); std::vector buf(slen); // binary data can't exceed base64 - const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen); + const size_t len = Base64ToByteStream (s.data(), slen, buf.data(), slen); return FromBuffer (buf.data(), len); } @@ -355,6 +362,10 @@ namespace data return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512); case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: return new i2p::crypto::RedDSA25519Verifier (); +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + return new i2p::crypto::MLDSA44Verifier (); +#endif case SIGNING_KEY_TYPE_RSA_SHA256_2048: case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA512_4096: @@ -376,6 +387,18 @@ namespace data auto keyLen = verifier->GetPublicKeyLen (); if (keyLen <= 128) verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); +#if OPENSSL_PQ + else if (keyLen > 384) + { + // for post-quantum + uint8_t * signingKey = new uint8_t[keyLen]; + memcpy (signingKey, m_StandardIdentity.signingKey, 384); + size_t excessLen = keyLen - 384; + memcpy (signingKey + 384, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + verifier->SetPublicKey (signingKey); + delete[] signingKey; + } +#endif else { // for P521 @@ -402,12 +425,8 @@ namespace data return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); }; @@ -633,6 +652,11 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: return new i2p::crypto::RedDSA25519Signer (priv); break; +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + return new i2p::crypto::MLDSA44Signer (priv); + break; +#endif default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } @@ -676,12 +700,8 @@ namespace data return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); }; @@ -728,9 +748,7 @@ namespace data case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA512_4096: LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA"); -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); @@ -744,6 +762,11 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub); break; +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + i2p::crypto::CreateMLDSA44RandomKeys (priv, pub); + break; +#endif default: LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1 @@ -758,12 +781,8 @@ namespace data i2p::crypto::GenerateElGamalKeyPair(priv, pub); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: i2p::crypto::CreateECIESP256RandomKeys (priv, pub); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub); - break; case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); break; diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 5edd4545..cf1dbfcd 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "Base.h" @@ -54,6 +55,8 @@ namespace data Identity& operator=(const Keys& keys); size_t FromBuffer (const uint8_t * buf, size_t len); IdentHash Hash () const; + operator uint8_t * () { return reinterpret_cast(this); } + operator const uint8_t * () const { return reinterpret_cast(this); } }; Keys CreateRandomKeys (); @@ -63,9 +66,7 @@ namespace data const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; - const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later - const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES - + const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2; @@ -74,11 +75,12 @@ namespace data const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7; - const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented + const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // since openssl 3.0.0 const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9; const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only - + const uint16_t SIGNING_KEY_TYPE_MLDSA44 = 15; + typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; @@ -99,7 +101,7 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; - size_t FromBase64(const std::string& s); + size_t FromBase64(std::string_view s); std::string ToBase64 () const; const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; @@ -133,7 +135,7 @@ namespace data IdentHash m_IdentHash; std::unique_ptr m_Verifier; size_t m_ExtendedLen; - uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; // TODO: support PQ keys }; size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index cbfbc84e..870fcc19 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -120,9 +120,8 @@ namespace transport // encrypt X i2p::crypto::CBCEncryption encryption; encryption.SetKey (m_RemoteIdentHash); - encryption.SetIV (m_IV); - encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X - encryption.GetIV (m_IV); // save IV for SessionCreated + encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X + memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated // encryption key for next block if (!KDF1Alice ()) return false; // fill options @@ -161,8 +160,7 @@ namespace transport // encrypt Y i2p::crypto::CBCEncryption encryption; encryption.SetKey (i2p::context.GetIdentHash ()); - encryption.SetIV (m_IV); - encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y + encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y // encryption key for next block (m_K) if (!KDF2Bob ()) return false; uint8_t options[16]; @@ -208,9 +206,8 @@ namespace transport // decrypt X i2p::crypto::CBCDecryption decryption; decryption.SetKey (i2p::context.GetIdentHash ()); - decryption.SetIV (i2p::context.GetNTCP2IV ()); - decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ()); - decryption.GetIV (m_IV); // save IV for SessionCreated + decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ()); + memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated // decryption key for next block if (!KDF1Bob ()) { @@ -268,8 +265,7 @@ namespace transport // decrypt Y i2p::crypto::CBCDecryption decryption; decryption.SetKey (m_RemoteIdentHash); - decryption.SetIV (m_IV); - decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ()); + decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ()); // decryption key for next block (m_K) if (!KDF2Alice ()) { @@ -823,15 +819,20 @@ namespace transport Terminate (); return; } - std::shared_ptr profile; // not null if older + + bool isOlder = false; if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) { // received RouterInfo is older than one in netdb - profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile - if (profile && profile->IsDuplicated ()) + isOlder = true; + if (ri1->HasProfile ()) { - SendTerminationAndTerminate (eNTCP2Banned); - return; + auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile + if (profile && profile->IsDuplicated ()) + { + SendTerminationAndTerminate (eNTCP2Banned); + return; + } } } @@ -848,8 +849,12 @@ namespace transport memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address { - if (profile) // older router? - profile->Duplicated (); // mark router as duplicated in profile + if (isOlder) // older router? + i2p::data::UpdateRouterProfile (ri1->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->Duplicated (); // mark router as duplicated in profile + }); else LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); SendTerminationAndTerminate (eNTCP2Banned); @@ -995,7 +1000,7 @@ namespace transport i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2); uint8_t nonce[12]; CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++; - if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false)) + if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen)) { LogPrint (eLogDebug, "NTCP2: Received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); @@ -1184,7 +1189,7 @@ namespace transport } uint8_t nonce[12]; CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers + m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block // send buffers @@ -1215,7 +1220,7 @@ namespace transport // encrypt uint8_t nonce[12]; CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); + m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer); // send m_IsSending = true; @@ -1439,6 +1444,12 @@ namespace transport boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); } + i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const + { + if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4; + return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6; + } + NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), m_ProxyType(eNoProxy), m_Resolver(GetService ()), @@ -1548,6 +1559,7 @@ namespace transport void NTCP2Server::Stop () { + m_EstablisherService.Stop (); { // we have to copy it because Terminate changes m_NTCP2Sessions auto ntcpSessions = m_NTCP2Sessions; @@ -1563,7 +1575,6 @@ namespace transport m_TerminationTimer.cancel (); m_ProxyEndpoint = nullptr; } - m_EstablisherService.Stop (); StopIOService (); } @@ -1984,5 +1995,17 @@ namespace transport else m_Address4 = addr; } + + void NTCP2Server::AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac) + { + return m_Encryptor.Encrypt (bufs, key, nonce, mac); + } + + bool NTCP2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 108c58f6..47354f32 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -147,6 +147,7 @@ namespace transport void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; bool IsEstablished () const override { return m_IsEstablished; }; + i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; bool IsTerminated () const { return m_IsTerminated; }; void ClientLogin (); // Alice @@ -272,6 +273,11 @@ namespace transport auto& GetService () { return GetIOService (); }; auto& GetEstablisherService () { return m_EstablisherService.GetService (); }; std::mt19937& GetRng () { return m_Rng; }; + void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AddNTCP2Session (std::shared_ptr session, bool incoming = false); void RemoveNTCP2Session (std::shared_ptr session); @@ -309,9 +315,12 @@ namespace transport uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; + std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; std::mt19937 m_Rng; EstablisherService m_EstablisherService; + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; public: diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 18d6308b..7f6882c4 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -40,7 +39,7 @@ namespace data NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), - m_LastExploratorySelectionUpdateTime (0) + m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) { } @@ -119,8 +118,9 @@ namespace data i2p::util::SetThreadName("NetDB"); uint64_t lastManage = 0; - uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), lastObsoleteProfilesCleanup = lastProfilesCleanup; - int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0; + uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), + lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup; + int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0; std::list > msgs; while (m_IsRunning) @@ -181,7 +181,7 @@ namespace data LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk"); } lastProfilesCleanup = mts; - profilesCleanupVariance = rand () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE; + profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE; } if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) @@ -197,7 +197,20 @@ namespace data else LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk"); lastObsoleteProfilesCleanup = mts; - obsoleteProfilesCleanVariance = rand () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; + obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; + } + if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance) + { + bool isApplying = m_ApplyingProfileUpdates.valid (); + if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + { + m_ApplyingProfileUpdates.get (); + isApplying = false; + } + if (!isApplying) + m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates (); + lastApplyingProfileUpdates = mts; + applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE; } } catch (std::exception& ex) @@ -281,6 +294,7 @@ namespace data } else { + r->CancelBufferToDelete (); // since an update received if (CheckLogLevel (eLogDebug)) LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); updated = false; @@ -502,7 +516,7 @@ namespace data } // send them off - i2p::transport::transports.SendMessages(ih, requests); + i2p::transport::transports.SendMessages(ih, std::move (requests)); } bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts) @@ -557,7 +571,7 @@ namespace data while(n > 0) { std::lock_guard lock(m_RouterInfosMutex); - uint32_t idx = rand () % m_RouterInfos.size (); + uint32_t idx = m_Rng () % m_RouterInfos.size (); uint32_t i = 0; for (const auto & it : m_RouterInfos) { if(i >= idx) // are we at the random start point? @@ -637,7 +651,7 @@ namespace data if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; - bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no tranports + bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no transports std::list > > saveToDisk; std::list removeFromDisk; @@ -660,16 +674,21 @@ namespace data { std::lock_guard l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update buffer = r->CopyBuffer (); - r->ScheduleBufferToDelete (); } + if (!i2p::transport::transports.IsConnected (ident)) + r->ScheduleBufferToDelete (); if (buffer) - saveToDisk.push_back(std::make_pair(ident.ToBase64 (), buffer)); + saveToDisk.emplace_back(ident.ToBase64 (), buffer); } r->SetUpdated (false); updatedCount++; continue; } - if (r->GetProfile ()->IsUnreachable ()) + else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) + // since update was long time ago we assume that router is not connected anymore + r->ScheduleBufferToDelete (); + + if (r->HasProfile () && r->GetProfile ()->IsUnreachable ()) r->SetUnreachable (true); // make router reachable back if too few routers or floodfills if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline || @@ -696,15 +715,16 @@ namespace data r->SetUnreachable (true); } } - // make router reachable back if connected now - if (r->IsUnreachable () && i2p::transport::transports.IsConnected (ident)) + // make router reachable back if connected now or trusted router + if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) || + i2p::transport::transports.IsTrustedRouter (ident))) r->SetUnreachable (false); if (r->IsUnreachable ()) { if (r->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file - removeFromDisk.push_back (ident.ToBase64()); + removeFromDisk.emplace_back (ident.ToBase64()); deletedCount++; if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; } @@ -1331,7 +1351,7 @@ namespace data if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE) { std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection), - NETDB_MAX_EXPLORATORY_SELECTION_SIZE, std::mt19937(ts)); + NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng); } else std::swap (m_ExploratorySelection, eligible); diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 9d8b875a..f2a7019b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include #include #include +#include #include "Base.h" #include "Gzip.h" @@ -52,6 +53,7 @@ namespace data const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58 const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59 const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62 const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16; const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500; const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill @@ -185,10 +187,11 @@ namespace data std::shared_ptr m_Requests; bool m_PersistProfiles; - std::future m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters; + std::future m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters; std::vector > m_ExploratorySelection; uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds + std::mt19937 m_Rng; i2p::util::MemoryPoolMt m_RouterInfoBuffersPool; i2p::util::MemoryPoolMt m_RouterInfoAddressesPool; diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index 63d161cd..f8c5037c 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,8 +20,10 @@ namespace i2p namespace data { RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct): - m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), m_IsActive (true), - m_CreationTime (i2p::util::GetSecondsSinceEpoch ()), m_LastRequestTime (0), m_NumAttempts (0) + m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), + m_IsActive (true), m_IsSentDirectly (false), + m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()), + m_LastRequestTime (0), m_NumAttempts (0) { if (i2p::context.IsFloodfill ()) m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill @@ -44,8 +46,9 @@ namespace data msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); if(router) m_ExcludedPeers.insert (router->GetIdentHash ()); - m_LastRequestTime = i2p::util::GetSecondsSinceEpoch (); + m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); m_NumAttempts++; + m_IsSentDirectly = false; return msg; } @@ -55,7 +58,8 @@ namespace data i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); m_NumAttempts++; - m_LastRequestTime = i2p::util::GetSecondsSinceEpoch (); + m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); + m_IsSentDirectly = true; return msg; } @@ -210,7 +214,7 @@ namespace data void NetDbRequests::ManageRequests () { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) { auto& dest = it->second; @@ -222,7 +226,8 @@ namespace data bool done = false; if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME) { - if (ts > dest->GetLastRequestTime () + MIN_REQUEST_TIME) // try next floodfill if no response after min interval + if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) + // try next floodfill if no response after min interval done = !SendNextRequest (dest); } else // request is expired @@ -328,7 +333,8 @@ namespace data void NetDbRequests::ScheduleManageRequests () { - m_ManageRequestsTimer.expires_from_now (boost::posix_time::seconds(MANAGE_REQUESTS_INTERVAL)); + m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL + + m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE)); m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer, this, std::placeholders::_1)); } diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h index 1758d498..53af2c6a 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,15 +24,17 @@ namespace i2p namespace data { const int MAX_NUM_REQUEST_ATTEMPTS = 5; - const uint64_t MANAGE_REQUESTS_INTERVAL = 1; // in seconds - const uint64_t MIN_REQUEST_TIME = 5; // in seconds - const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL); + const uint64_t MANAGE_REQUESTS_INTERVAL = 400; // in milliseconds + const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds + const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds + const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE); + const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds const uint64_t DISCOVERED_REQUEST_INTERVAL_VARIANCE = 540; // in milliseconds - const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30; // in seconds - const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40; // in seconds + const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30000; // in milliseconds + const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40000; // in milliseconds const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds class RequestedDestination @@ -51,6 +53,7 @@ namespace data bool IsExploratory () const { return m_IsExploratory; }; bool IsDirect () const { return m_IsDirect; }; bool IsActive () const { return m_IsActive; }; + bool IsSentDirectly () const { return m_IsSentDirectly; }; bool IsExcluded (const IdentHash& ident) const; uint64_t GetCreationTime () const { return m_CreationTime; }; uint64_t GetLastRequestTime () const { return m_LastRequestTime; }; @@ -69,9 +72,9 @@ namespace data private: IdentHash m_Destination; - bool m_IsExploratory, m_IsDirect, m_IsActive; + bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly; std::unordered_set m_ExcludedPeers; - uint64_t m_CreationTime, m_LastRequestTime; // in seconds + uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds std::list m_RequestComplete; int m_NumAttempts; }; @@ -115,9 +118,9 @@ namespace data private: + i2p::util::MemoryPoolMt m_RequestedDestinationsPool; std::unordered_map > m_RequestedDestinations; std::list m_DiscoveredRouterHashes; - i2p::util::MemoryPoolMt m_RequestedDestinationsPool; boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer, m_CleanupTimer, m_DiscoveredRoutersTimer; std::mt19937 m_Rng; diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 59d60c6d..fe7f9905 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,13 +27,15 @@ namespace data static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); static std::unordered_map > g_Profiles; static std::mutex g_ProfilesMutex; - + static std::list)> > > g_PostponedUpdates; + static std::mutex g_PostponedUpdatesMutex; + RouterProfile::RouterProfile (): m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0), - m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), - m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), - m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false), - m_IsDuplicated (false) + m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), m_LastAccessTime (0), + m_LastPersistTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), + m_NumTunnelsNonReplied (0),m_NumTimesTaken (0), m_NumTimesRejected (0), + m_HasConnected (false), m_IsDuplicated (false) { } @@ -78,6 +80,7 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { + m_IsUpdated = false; std::string ident = identHash.ToBase64 (); std::string path = g_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; @@ -255,30 +258,42 @@ namespace data std::unique_lock l(g_ProfilesMutex); auto it = g_Profiles.find (identHash); if (it != g_Profiles.end ()) + { + it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ()); return it->second; + } } auto profile = netdb.NewRouterProfile (); profile->Load (identHash); // if possible - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); g_Profiles.emplace (identHash, profile); return profile; } bool IsRouterBanned (const IdentHash& identHash) { - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); auto it = g_Profiles.find (identHash); if (it != g_Profiles.end ()) return it->second->IsUnreachable (); return false; } + + bool IsRouterDuplicated (const IdentHash& identHash) + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second->IsDuplicated (); + return false; + } void InitProfilesStorage () { g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); } - + static void SaveProfilesToDisk (std::list > >&& profiles) { for (auto& it: profiles) @@ -290,15 +305,17 @@ namespace data auto ts = i2p::util::GetSecondsSinceEpoch (); std::list > > tmp; { - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) { - if (ts - it->second->GetLastUpdateTime () > PEER_PROFILE_PERSIST_INTERVAL) + if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL) { - if (it->second->IsUpdated ()) - tmp.push_back (std::make_pair (it->first, it->second)); + tmp.push_back (*it); + it->second->SetLastPersistTime (ts); + it->second->SetUpdated (false); + } + if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL) it = g_Profiles.erase (it); - } else it++; } @@ -312,7 +329,7 @@ namespace data { std::unordered_map > tmp; { - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); std::swap (tmp, g_Profiles); } auto ts = i2p::util::GetSecondsSinceEpoch (); @@ -347,7 +364,7 @@ namespace data { { auto ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) { if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) @@ -359,5 +376,47 @@ namespace data return std::async (std::launch::async, DeleteFilesFromDisk); } + + bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update) + { + if (!update) return true; + std::shared_ptr profile; + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + profile = it->second; + } + if (profile) + { + update (profile); + return true; + } + // postpone + std::lock_guard l(g_PostponedUpdatesMutex); + g_PostponedUpdates.emplace_back (identHash, update); + return false; + } + + static void ApplyPostponedUpdates (std::list)> > >&& updates) + { + for (const auto& [ident, update] : updates) + { + auto profile = GetRouterProfile (ident); + update (profile); + } + } + + std::future FlushPostponedRouterProfileUpdates () + { + if (g_PostponedUpdates.empty ()) return std::future(); + + std::list)> > > updates; + { + std::lock_guard l(g_PostponedUpdatesMutex); + g_PostponedUpdates.swap (updates); + } + return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates)); + } } } diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 5d85cec3..59995b3f 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include "Identity.h" @@ -40,10 +41,12 @@ namespace data const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes) const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes) const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours) - const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes) + const int PEER_PROFILE_PERSIST_INTERVAL = 1320; // in seconds (22 minutes) const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) const int PEER_PROFILE_USEFUL_THRESHOLD = 3; const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined + const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds + const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds class RouterProfile { @@ -67,6 +70,11 @@ namespace data uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; bool IsUpdated () const { return m_IsUpdated; }; + void SetUpdated (bool updated) { m_IsUpdated = updated; } + uint64_t GetLastAccessTime () const { return m_LastAccessTime; }; + void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; }; + uint64_t GetLastPersistTime () const { return m_LastPersistTime; }; + void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; }; bool IsUseful() const; bool IsDuplicated () const { return m_IsDuplicated; }; @@ -88,7 +96,8 @@ namespace data private: bool m_IsUpdated; - uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime; // in seconds + uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, + m_LastAccessTime, m_LastPersistTime; // in seconds // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -104,10 +113,13 @@ namespace data std::shared_ptr GetRouterProfile (const IdentHash& identHash); bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles + bool IsRouterDuplicated (const IdentHash& identHash); // check only existing profiles void InitProfilesStorage (); std::future DeleteObsoleteProfiles (); void SaveProfiles (); std::future PersistProfiles (); + bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update); // return true if updated immediately, and false if postponed + std::future FlushPostponedRouterProfileUpdates (); } } diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index fdcdb145..6819a185 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,13 +33,14 @@ namespace i2p m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID), - m_PublishReplyToken (0), m_IsHiddenMode (false) + m_PublishReplyToken (0), m_IsHiddenMode (false), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL), m_IsSaving (false) { } void RouterContext::Init () { - srand (i2p::util::GetMillisecondsSinceEpoch () % 1000); + srand (m_Rng () % 1000); m_StartupTime = i2p::util::GetMonotonicSeconds (); if (!Load ()) @@ -76,7 +77,7 @@ namespace i2p m_CongestionUpdateTimer->cancel (); m_Service->Stop (); CleanUp (); // GarlicDestination - } + } } std::shared_ptr RouterContext::CopyRouterInfoBuffer () const @@ -253,11 +254,36 @@ namespace i2p void RouterContext::UpdateRouterInfo () { + std::shared_ptr buffer; { std::lock_guard l(m_RouterInfoMutex); m_RouterInfo.CreateBuffer (m_Keys); + buffer = m_RouterInfo.CopyBuffer (); } - m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO)); + { + // update save buffer to latest + std::lock_guard l(m_SaveBufferMutex); + m_SaveBuffer = buffer; + } + bool isSaving = false; + if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved + { + auto savingRouterInfo = std::async (std::launch::async, [this]() + { + std::shared_ptr buffer; + while (m_SaveBuffer) + { + { + std::lock_guard l(m_SaveBufferMutex); + buffer = m_SaveBuffer; + m_SaveBuffer = nullptr; + } + if (buffer) + i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer); + } + m_IsSaving = false; + }); + } m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } @@ -649,12 +675,12 @@ namespace i2p void RouterContext::SetBandwidth (int limit) { - if (limit > 2000) { SetBandwidth('X'); } - else if (limit > 256) { SetBandwidth('P'); } - else if (limit > 128) { SetBandwidth('O'); } - else if (limit > 64) { SetBandwidth('N'); } - else if (limit > 48) { SetBandwidth('M'); } - else if (limit > 12) { SetBandwidth('L'); } + if (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); } + else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); } + else if (limit > 128) { SetBandwidth('O'); } + else if (limit > 64) { SetBandwidth('N'); } + else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); } + else if (limit > 12) { SetBandwidth('L'); } else { SetBandwidth('K'); } m_BandwidthLimit = limit; // set precise limit } @@ -1359,7 +1385,7 @@ namespace i2p { m_PublishTimer->cancel (); m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL + - rand () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE)); + m_Rng () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE)); m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer, this, std::placeholders::_1)); } @@ -1434,7 +1460,7 @@ namespace i2p i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ())); } else - LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnles. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " seconds"); + LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnels. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " milliseconds"); } m_PublishExcluded.insert (floodfill->GetIdentHash ()); m_PublishReplyToken = replyToken; @@ -1448,7 +1474,7 @@ namespace i2p if (m_PublishTimer) { m_PublishTimer->cancel (); - m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONFIRMATION_TIMEOUT)); + m_PublishTimer->expires_from_now (boost::posix_time::milliseconds(ROUTER_INFO_CONFIRMATION_TIMEOUT)); m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer, this, std::placeholders::_1)); } @@ -1471,7 +1497,8 @@ namespace i2p if (m_CongestionUpdateTimer) { m_CongestionUpdateTimer->cancel (); - m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONGESTION_UPDATE_INTERVAL)); + m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds( + ROUTER_INFO_CONGESTION_UPDATE_INTERVAL + m_Rng () % ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE)); m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer, this, std::placeholders::_1)); } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 5060866a..c3336336 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "Identity.h" @@ -34,9 +35,10 @@ namespace garlic const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds - const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds + const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 1600; // in milliseconds const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; - const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds + const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 11*60; // in seconds + const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE = 130; // in seconds const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds enum RouterStatus @@ -263,6 +265,10 @@ namespace garlic uint32_t m_PublishReplyToken; bool m_IsHiddenMode; // not publish mutable std::mutex m_RouterInfoMutex; + std::mt19937 m_Rng; + std::shared_ptr m_SaveBuffer; + std::mutex m_SaveBufferMutex; // TODO: make m_SaveBuffer atomic + std::atomic m_IsSaving; }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 0d53f6cd..f5ed27e6 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,7 @@ #include "I2PEndian.h" #include #include -#include +#include #include // for boost::to_lower #ifndef __cpp_lib_atomic_shared_ptr #include @@ -106,8 +106,7 @@ namespace data // skip identity size_t identityLen = m_RouterIdentity->GetFullLen (); // read new RI - std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen)); - ReadFromStream (str); + ReadFromBuffer (buf + identityLen, len - identityLen); if (!m_IsUnreachable) UpdateBuffer (buf, len); // save buffer // don't delete buffer until saved to the file @@ -195,39 +194,34 @@ namespace data } } // parse RI - std::stringstream str; - str.write ((const char *)m_Buffer->data () + identityLen, bufferLen - identityLen); - ReadFromStream (str); - if (!str) + if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen)) { LogPrint (eLogError, "RouterInfo: Malformed message"); m_IsUnreachable = true; - } + } } - void RouterInfo::ReadFromStream (std::istream& s) + bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len) { - if (!s) return; + if (len < 9) return false; m_Caps = 0; m_Congestion = eLowCongestion; - s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); - m_Timestamp = be64toh (m_Timestamp); + m_Timestamp = bufbe64toh (buf); + size_t offset = 8; // timestamp // read addresses auto addresses = NewAddresses (); - uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); + uint8_t numAddresses = buf[offset]; offset++; for (int i = 0; i < numAddresses; i++) { + if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date uint8_t supportedTransports = 0; auto address = NewAddress (); - uint8_t cost; // ignore - s.read ((char *)&cost, sizeof (cost)); - s.read ((char *)&address->date, sizeof (address->date)); + offset++; // cost, ignore + address->date = bufbe64toh (buf + offset); offset += 8; // date bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false; - char transportStyle[6]; - ReadString (transportStyle, 6, s); - if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 + auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1; + if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2 address->transportStyle = eTransportNTCP2; - else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2 + else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 { address->transportStyle = eTransportSSU2; address->ssu.reset (new SSUExt ()); @@ -237,24 +231,22 @@ namespace data address->transportStyle = eTransportUnknown; address->caps = 0; address->port = 0; - uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); if (!s) return; - size = be16toh (size); + if (offset + 2 > len) return false; + uint16_t size = bufbe16toh (buf + offset); offset += 2; // size + if (offset + size >= len) return false; if (address->transportStyle == eTransportUnknown) { // skip unknown address - s.seekg (size, std::ios_base::cur); - if (s) continue; else return; + offset += size; + continue; } + size_t r = 0; while (r < size) { - char key[255], value[255]; - r += ReadString (key, 255, s); - s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, 255, s); - s.seekg (1, std::ios_base::cur); r++; // ; - if (!s) return; - if (!strcmp (key, "host")) + auto [key, value, sz] = ExtractParam (buf + offset, len - offset); + r += sz; offset += sz; + if (key.empty ()) continue; + if (key == "host") { boost::system::error_code ecode; address->host = boost::asio::ip::make_address (value, ecode); @@ -268,63 +260,53 @@ namespace data address->transportStyle = eTransportUnknown; } } - else if (!strcmp (key, "port")) + else if (key == "port") { - try - { - address->port = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); } - else if (!strcmp (key, "mtu")) + else if (key == "mtu") { if (address->ssu) { - try - { - address->ssu->mtu = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ()); } else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); } - else if (!strcmp (key, "caps")) + else if (key == "caps") address->caps = ExtractAddressCaps (value); - else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key + else if (key == "s") // ntcp2 or ssu2 static key { - if (Base64ToByteStream (value, strlen (value), address->s, 32) == 32 && + if (Base64ToByteStream (value.data (), value.length (), address->s, 32) == 32 && !(address->s[31] & 0x80)) // check if x25519 public key isStaticKey = true; else address->transportStyle = eTransportUnknown; // invalid address } - else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro + else if (key == "i") // ntcp2 iv or ssu2 intro { if (address->IsNTCP2 ()) { - if (Base64ToByteStream (value, strlen (value), address->i, 16) == 16) + if (Base64ToByteStream (value.data (), value.length (), address->i, 16) == 16) address->published = true; // presence of "i" means "published" NTCP2 else address->transportStyle = eTransportUnknown; // invalid address } else if (address->IsSSU2 ()) { - if (Base64ToByteStream (value, strlen (value), address->i, 32) == 32) + if (Base64ToByteStream (value.data (), value.length (), address->i, 32) == 32) isIntroKey = true; else address->transportStyle = eTransportUnknown; // invalid address } } - else if (!strcmp (key, "v")) + else if (key == "v") { - if (!strcmp (value, "2")) + if (value == "2") isV2 = true; else { @@ -340,13 +322,11 @@ namespace data LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; } - size_t l = strlen(key); - unsigned char index = key[l-1] - '0'; // TODO: - key[l-1] = 0; + unsigned char index = key[key.length () - 1] - '0'; // TODO: if (index > 9) { LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); - if (s) continue; else return; + continue; } if (index >= address->ssu->introducers.size ()) { @@ -355,34 +335,23 @@ namespace data address->ssu->introducers.resize (index + 1); } Introducer& introducer = address->ssu->introducers.at (index); - if (!strcmp (key, "itag")) + auto key1 = key.substr(0, key.length () - 1); + if (key1 == "itag") { - try - { - introducer.iTag = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ()); } - else if (!strcmp (key, "ih")) - Base64ToByteStream (value, strlen (value), introducer.iH, 32); - else if (!strcmp (key, "iexp")) + else if (key1 == "ih") + Base64ToByteStream (value.data (), value.length (), introducer.iH, 32); + else if (key1 == "iexp") { - try - { - introducer.iExp = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); } } - if (!s) return; - } - + } if (address->transportStyle == eTransportNTCP2) { if (isStaticKey) @@ -446,66 +415,73 @@ namespace data boost::atomic_store (&m_Addresses, addresses); #endif // read peers - uint8_t numPeers; - s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; - s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers + if (offset + 1 > len) return false; + uint8_t numPeers = buf[offset]; offset++; // num peers + offset += numPeers*32; // TODO: read peers // read properties + if (offset + 2 > len) return false; m_Version = 0; bool isNetId = false; std::string family; - uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); if (!s) return; - size = be16toh (size); + uint16_t size = bufbe16toh (buf + offset); offset += 2; // size + if (offset + size > len) return false; + size_t r = 0; while (r < size) { - char key[255], value[255]; - r += ReadString (key, 255, s); - s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, 255, s); - s.seekg (1, std::ios_base::cur); r++; // ; - if (!s) return; + auto [key, value, sz] = ExtractParam (buf + offset, len - offset); + r += sz; offset += sz; + if (key.empty ()) continue; SetProperty (key, value); // extract caps - if (!strcmp (key, "caps")) + if (key == "caps") { ExtractCaps (value); m_IsFloodfill = IsDeclaredFloodfill (); } // extract version - else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION)) + else if (key == ROUTER_INFO_PROPERTY_VERSION) { m_Version = 0; - char * ch = value; - while (*ch) + for (auto ch: value) { - if (*ch >= '0' && *ch <= '9') + if (ch >= '0' && ch <= '9') { m_Version *= 10; - m_Version += (*ch - '0'); + m_Version += (ch - '0'); } - ch++; } + if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6))) + { + auto addresses = GetAddresses (); + if (addresses) + { + if ((*addresses)[eSSU2V4Idx]) (*addresses)[eSSU2V4Idx]->caps &= ~eSSUTesting; + if ((*addresses)[eSSU2V6Idx]) (*addresses)[eSSU2V6Idx]->caps &= ~eSSUTesting; + } + } } // check netId - else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID)) + else if (key == ROUTER_INFO_PROPERTY_NETID) { isNetId = true; - if (atoi (value) != i2p::context.GetNetID ()) + int netID; + auto res = std::from_chars(value.data(), value.data() + value.size(), netID); + if (res.ec != std::errc() || netID != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } } // family - else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) + else if (key == ROUTER_INFO_PROPERTY_FAMILY) { family = value; boost::to_lower (family); } - else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) + else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG) { - if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) + if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); else { @@ -513,25 +489,24 @@ namespace data SetUnreachable (true); } } - - if (!s) return; } if (!m_SupportedTransports || !isNetId || !m_Version) SetUnreachable (true); - } - + + return true; + } + bool RouterInfo::IsFamily (FamilyID famid) const { return m_FamilyID == famid; } - void RouterInfo::ExtractCaps (const char * value) + void RouterInfo::ExtractCaps (std::string_view value) { - const char * cap = value; - while (*cap) + for (auto cap: value) { - switch (*cap) + switch (cap) { case CAPS_FLAG_FLOODFILL: m_Caps |= Caps::eFloodfill; @@ -540,16 +515,16 @@ namespace data case CAPS_FLAG_LOW_BANDWIDTH2: case CAPS_FLAG_LOW_BANDWIDTH3: case CAPS_FLAG_LOW_BANDWIDTH4: - m_BandwidthCap = *cap; + m_BandwidthCap = cap; break; case CAPS_FLAG_HIGH_BANDWIDTH: m_Caps |= Caps::eHighBandwidth; - m_BandwidthCap = *cap; + m_BandwidthCap = cap; break; case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH2: m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth; - m_BandwidthCap = *cap; + m_BandwidthCap = cap; break; case CAPS_FLAG_HIDDEN: m_Caps |= Caps::eHidden; @@ -571,17 +546,15 @@ namespace data break; default: ; } - cap++; } - } - - uint8_t RouterInfo::ExtractAddressCaps (const char * value) const + } + + uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const { uint8_t caps = 0; - const char * cap = value; - while (*cap) + for (auto cap: value) { - switch (*cap) + switch (cap) { case CAPS_FLAG_V4: caps |= AddressCaps::eV4; @@ -597,11 +570,10 @@ namespace data break; default: ; } - cap++; } return caps; - } - + } + void RouterInfo::UpdateIntroducers (std::shared_ptr
address, uint64_t ts) { if (!address || !address->ssu) return; @@ -661,25 +633,41 @@ namespace data return SaveToFile (fullPath, m_Buffer); } - size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const + std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const { - uint8_t l; - s.read ((char *)&l, 1); - if (l < len) - { - s.read (str, l); - if (!s) l = 0; // failed, return empty string - str[l] = 0; - } - else + uint8_t l = buf[0]; + if (l > len) { LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); - s.seekg (l, std::ios::cur); // skip - str[0] = 0; - } - return l+1; + l = len; + } + return { (const char *)(buf + 1), l }; } + std::tuple RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const + { + auto key = ExtractString (buf, len); + size_t offset = key.length () + 1; + if (offset >= len) return { std::string_view(), std::string_view(), len }; + if (buf[offset] != '=') + { + LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key); + key = std::string_view(); + } + offset++; + if (offset >= len) return { key, std::string_view(), len }; + auto value = ExtractString (buf + offset, len - offset); + offset += value.length () + 1; + if (offset >= len) return { key, std::string_view(), len }; + if (buf[offset] != ';') + { + LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value); + value = std::string_view(); + } + offset++; + return { key, value, offset }; + } + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) { auto addr = std::make_shared
(); @@ -1136,12 +1124,12 @@ namespace data void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len) { + m_IsBufferScheduledToDelete = false; if (!m_Buffer) m_Buffer = NewBuffer (); if (len > m_Buffer->size ()) len = m_Buffer->size (); memcpy (m_Buffer->data (), buf, len); m_Buffer->SetBufferLen (len); - m_IsBufferScheduledToDelete = false; } std::shared_ptr RouterInfo::CopyBuffer () const @@ -1195,6 +1183,19 @@ namespace data return false; } } + + std::string RouterInfo::GetTransportName (SupportedTransports tr) + { + switch (tr) + { + case eNTCP2V4: return "NTCP2V4"; + case eNTCP2V6: return "NTCP2V6"; + case eSSU2V4: return "SSU2V4"; + case eSSU2V6: return "SSU2V6"; + case eNTCP2V6Mesh: return "Mesh"; + default: return ""; + } + } void LocalRouterInfo::CreateBuffer (const PrivateKeys& privateKeys) { @@ -1380,9 +1381,9 @@ namespace data if (!introducer.iTag) continue; if (introducer.iExp) // expiration is specified { - WriteString ("iexp" + boost::lexical_cast(i), properties); + WriteString ("iexp" + std::to_string(i), properties); properties << '='; - WriteString (boost::lexical_cast(introducer.iExp), properties); + WriteString (std::to_string(introducer.iExp), properties); properties << ';'; } i++; @@ -1391,7 +1392,7 @@ namespace data for (const auto& introducer: address.ssu->introducers) { if (!introducer.iTag) continue; - WriteString ("ih" + boost::lexical_cast(i), properties); + WriteString ("ih" + std::to_string(i), properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64); @@ -1404,9 +1405,9 @@ namespace data for (const auto& introducer: address.ssu->introducers) { if (!introducer.iTag) continue; - WriteString ("itag" + boost::lexical_cast(i), properties); + WriteString ("itag" + std::to_string(i), properties); properties << '='; - WriteString (boost::lexical_cast(introducer.iTag), properties); + WriteString (std::to_string(introducer.iTag), properties); properties << ';'; i++; } @@ -1420,7 +1421,7 @@ namespace data { WriteString ("mtu", properties); properties << '='; - WriteString (boost::lexical_cast(address.ssu->mtu), properties); + WriteString (std::to_string(address.ssu->mtu), properties); properties << ';'; } } @@ -1428,7 +1429,7 @@ namespace data { WriteString ("port", properties); properties << '='; - WriteString (boost::lexical_cast(address.port), properties); + WriteString (std::to_string(address.port), properties); properties << ';'; } if (address.IsNTCP2 () || address.IsSSU2 ()) @@ -1463,9 +1464,11 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } - void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) + void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) { - m_Properties[key] = value; + auto [it, inserted] = m_Properties.emplace (key, value); + if (!inserted) + it->second = value; } void LocalRouterInfo::DeleteProperty (const std::string& key) diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index beeba5bf..387906ad 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -219,7 +221,7 @@ namespace data std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; - virtual void SetProperty (const std::string& key, const std::string& value) {}; + virtual void SetProperty (std::string_view key, std::string_view value) {}; virtual void ClearProperties () {}; AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2V4Address () const; @@ -294,6 +296,7 @@ namespace data std::shared_ptr GetSharedBuffer () const { return m_Buffer; }; std::shared_ptr CopyBuffer () const; void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; }; + void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; }; bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; }; bool IsUpdated () const { return m_IsUpdated; }; @@ -332,11 +335,12 @@ namespace data bool LoadFile (const std::string& fullPath); void ReadFromFile (const std::string& fullPath); - void ReadFromStream (std::istream& s); + bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed void ReadFromBuffer (bool verifySignature); - size_t ReadString (char* str, size_t len, std::istream& s) const; - void ExtractCaps (const char * value); - uint8_t ExtractAddressCaps (const char * value) const; + std::string_view ExtractString (const uint8_t * buf, size_t len) const; + std::tuple ExtractParam (const uint8_t * buf, size_t len) const; + void ExtractCaps (std::string_view value); + uint8_t ExtractAddressCaps (std::string_view value) const; void UpdateIntroducers (std::shared_ptr
address, uint64_t ts); template std::shared_ptr GetAddress (Filter filter) const; @@ -363,6 +367,10 @@ namespace data int m_Version; Congestion m_Congestion; mutable std::shared_ptr m_Profile; + + public: + + static std::string GetTransportName (SupportedTransports tr); }; class LocalRouterInfo: public RouterInfo @@ -374,7 +382,7 @@ namespace data void UpdateCaps (uint8_t caps); bool UpdateCongestion (Congestion c); // returns true if updated - void SetProperty (const std::string& key, const std::string& value) override; + void SetProperty (std::string_view key, std::string_view value) override; void DeleteProperty (const std::string& key); std::string GetProperty (const std::string& key) const; void ClearProperties () override { m_Properties.clear (); }; diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 982940c5..fc2355a5 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -890,7 +890,7 @@ namespace transport } auto session = std::make_shared (*this, router, address); - if (!isValidEndpoint && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) + if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) { // router doesn't publish endpoint, but we connected before and hole punch might be alive auto ep = router->GetProfile ()->GetLastEndpoint (); @@ -1251,18 +1251,21 @@ namespace transport } uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT))); + if (!token) token = 1; // token can't be zero + m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)); return token; } std::pair SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) { - m_IncomingTokens.erase (ep); // drop previous uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT)); - m_IncomingTokens.emplace (ep, ret); - return ret; + if (!token) token = 1; // token can't be zero + uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT; + auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires); + if (!inserted) + it->second = { token, expires }; // override + return it->second; } std::vector > SSU2Server::FindIntroducers (int maxNumIntroducers, @@ -1389,7 +1392,7 @@ namespace transport excluded.insert (ident); } - // sesssion about to expire are not counted + // session about to expire are not counted for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS + numOldSessions; i++) { auto introducer = i2p::data::netdb.GetRandomSSU2Introducer (v4, excluded); @@ -1516,6 +1519,23 @@ namespace transport } } + bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + m_ChaCha20 (msg, msgLen, key, nonce, out); + } + void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 715e72ab..a8598ce3 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -82,6 +82,11 @@ namespace transport bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true); void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts); std::mt19937& GetRng () { return m_Rng; } + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; void AdjustTimeOffset (int64_t offset, std::shared_ptr from); @@ -200,6 +205,9 @@ namespace transport std::unordered_map, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) std::list m_ReceivedPacketsQueue; mutable std::mutex m_ReceivedPacketsQueueMutex; + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + i2p::crypto::ChaCha20Context m_ChaCha20; // proxy bool m_IsThroughProxy; diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp index 651ed76c..3760e329 100644 --- a/libi2pd/SSU2OutOfSession.cpp +++ b/libi2pd/SSU2OutOfSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2024, The PurpleI2P Project +* Copyright (c) 2024-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -46,7 +46,7 @@ namespace transport } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); SetDestConnID (headerX[0]); // decrypt and handle payload uint8_t * payload = buf + 32; @@ -68,7 +68,7 @@ namespace transport void SSU2PeerTestSession::HandleAddress (const uint8_t * buf, size_t len) { if (!ExtractEndpoint (buf, len, m_OurEndpoint)) - LogPrint (eLogWarning, "SSU2: Can't hanlde address block from peer test message"); + LogPrint (eLogWarning, "SSU2: Can't handle address block from peer test message"); } void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len) @@ -89,7 +89,7 @@ namespace transport { if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ()) { - m_PeerTestResendTimer.cancel (); // calcel delayed msg 6 if any + m_PeerTestResendTimer.cancel (); // cancel delayed msg 6 if any m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ()); if (GetAddress ()) { @@ -183,7 +183,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16); + GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); // send GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); UpdateNumSentBytes (payloadSize + 32); @@ -191,12 +191,7 @@ namespace transport void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed) { -#if __cplusplus >= 202002L // C++20 m_SignedData.assign (signedData, signedData + signedDataLen); -#else - m_SignedData.resize (signedDataLen); - memcpy (m_SignedData.data (), signedData, signedDataLen); -#endif if (!delayed) SendPeerTest (msg); // schedule resend for msgs 5 or 6 @@ -257,7 +252,7 @@ namespace transport { // we are Charlie uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id - uint32_t sourceConnID = ~destConnID; + uint64_t sourceConnID = ~destConnID; SetSourceConnID (sourceConnID); SetDestConnID (destConnID); SetState (eSSU2SessionStateHolePunch); @@ -305,7 +300,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16); + GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); // send GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); UpdateNumSentBytes (payloadSize + 32); @@ -313,12 +308,7 @@ namespace transport void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen) { -#if __cplusplus >= 202002L // C++20 m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen); -#else - m_RelayResponseBlock.resize (relayResponseBlockLen); - memcpy (m_RelayResponseBlock.data (), relayResponseBlock, relayResponseBlockLen); -#endif SendHolePunch (); ScheduleResend (); } diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 96f38217..e0a9a48f 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -92,7 +92,7 @@ namespace transport m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose), m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size - m_LastResendTime (0), m_LastResendAttemptTime (0) + m_LastResendTime (0), m_LastResendAttemptTime (0), m_NumRanges (0) { if (noise) m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); @@ -189,7 +189,7 @@ namespace transport if (!asz) return false; payload[17] = asz; packet->payloadSize = asz + 18; - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash @@ -349,7 +349,7 @@ namespace transport void SSU2Session::Done () { - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::Terminate, shared_from_this ())); } void SSU2Session::SendLocalRouterInfo (bool update) @@ -357,7 +357,7 @@ namespace transport if (update || !IsOutgoing ()) { auto s = shared_from_this (); - m_Server.GetService ().post ([s]() + boost::asio::post (m_Server.GetService (), [s]() { if (!s->IsEstablished ()) return; uint8_t payload[SSU2_MAX_PACKET_SIZE]; @@ -389,7 +389,7 @@ namespace transport m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); } if (empty) - m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ())); + boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ())); } void SSU2Session::PostI2NPMessages () @@ -623,7 +623,8 @@ namespace transport } else { - uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize); + uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize, + it->second->numResends > 1 ? SSU2_FLAG_IMMEDIATE_ACK_REQUESTED : 0); it->second->numResends++; it->second->sendTime = ts; resentPackets.emplace (packetNum, it->second); @@ -682,7 +683,7 @@ namespace transport } const uint8_t nonce[12] = {0}; uint64_t headerX[2]; - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); break; } @@ -748,7 +749,7 @@ namespace transport payloadSize += 16; header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); - i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); + m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated m_SentHandshakePacket->payloadSize = payloadSize; // send @@ -775,7 +776,7 @@ namespace transport } const uint8_t nonce[12] = {0}; uint8_t headerX[48]; - i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); + m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); memcpy (&m_DestConnID, headerX, 8); uint64_t token; memcpy (&token, headerX + 8, 8); @@ -874,7 +875,7 @@ namespace transport m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created) header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); - i2p::crypto::ChaCha20 (headerX, 48, kh2, nonce, headerX); + m_Server.ChaCha20 (headerX, 48, kh2, nonce, headerX); m_State = eSSU2SessionStateSessionCreatedSent; m_SentHandshakePacket->payloadSize = payloadSize; // send @@ -902,7 +903,7 @@ namespace transport m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; const uint8_t nonce[12] = {0}; uint8_t headerX[48]; - i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX); + m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX); // KDF for SessionCreated m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk); @@ -1178,13 +1179,18 @@ namespace transport LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); return false; } - std::shared_ptr profile; // not null if older + + bool isOlder = false; if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) { // received RouterInfo is older than one in netdb - profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile - if (profile && profile->IsDuplicated ()) - return false; + isOlder = true; + if (ri->HasProfile ()) + { + auto profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile + if (profile && profile->IsDuplicated ()) + return false; + } } ri = ri1; @@ -1198,15 +1204,28 @@ namespace transport (!m_RemoteEndpoint.address ().is_v6 () || memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address { - if (profile) // older router? - profile->Duplicated (); // mark router as duplicated in profile + if (isOlder) // older router? + i2p::data::UpdateRouterProfile (ri->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->Duplicated (); // mark router as duplicated in profile + }); else LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host, " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); return false; } if (!m_Address->published) - ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); + { + if (ri->HasProfile ()) + ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); + else + i2p::data::UpdateRouterProfile (ri->GetIdentHash (), + [ep = m_RemoteEndpoint](std::shared_ptr profile) + { + if (profile) profile->SetLastEndpoint (ep); + }); + } SetRemoteIdentity (ri->GetRouterIdentity ()); AdjustMaxPayloadSize (); m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now @@ -1264,7 +1283,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); memset (nonce, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); + m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); // send if (m_Server.AddPendingOutgoingSession (shared_from_this ())) m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); @@ -1286,7 +1305,7 @@ namespace transport uint8_t nonce[12] = {0}; uint8_t h[32]; memcpy (h, header.buf, 16); - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); memcpy (&m_DestConnID, h + 16, 8); // decrypt CreateNonce (be32toh (header.h.packetNum), nonce); @@ -1338,7 +1357,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12)); memset (nonce, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + m_Server.ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); // send m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); } @@ -1362,7 +1381,7 @@ namespace transport } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); uint64_t token = headerX[1]; if (token) m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); @@ -1411,7 +1430,7 @@ namespace transport } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); m_DestConnID = headerX[0]; // decrypt and handle payload uint8_t * payload = buf + 32; @@ -1455,7 +1474,7 @@ namespace transport uint8_t nonce[12]; CreateNonce (m_SendPacketNum, nonce); uint8_t payload[SSU2_MAX_PACKET_SIZE]; - i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE, true); + m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8)); header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4)); m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint); @@ -1495,8 +1514,8 @@ namespace transport uint32_t packetNum = be32toh (header.h.packetNum); uint8_t nonce[12]; CreateNonce (packetNum, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16, - m_KeyDataReceive, nonce, payload, payloadSize, false)) + if (!m_Server.AEADChaCha20Poly1305Decrypt (buf + 16, payloadSize, header.buf, 16, + m_KeyDataReceive, nonce, payload, payloadSize)) { LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); return; @@ -1744,6 +1763,7 @@ namespace transport HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt // ranges len -= 5; + if (!len || m_SentPackets.empty ()) return; // don't handle ranges if nothing to acknowledge const uint8_t * ranges = buf + 5; while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS) { @@ -1945,6 +1965,7 @@ namespace transport void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) { // we are Bob + if (len < 9) return; auto mts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t relayTag = bufbe32toh (buf + 5); // relay tag @@ -1978,7 +1999,7 @@ namespace transport packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; if (!packet->payloadSize && r) session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len -1); + packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len - 1); if (packet->payloadSize < m_MaxPayloadSize) packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize); @@ -1993,18 +2014,24 @@ namespace transport void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) { // we are Charlie + if (len < 47) return; SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; boost::asio::ip::udp::endpoint ep; std::shared_ptr addr; auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice if (r) { - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (i2p::context.GetIdentHash (), 32); // chash s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz uint8_t asz = buf[46]; + if (asz + 47 + r->GetIdentity ()->GetSignatureLen () > len) + { + LogPrint (eLogWarning, "SSU2: Malformed RelayIntro len=", len); + return; + } s.Insert (buf + 47, asz); // Alice Port, Alice IP if (s.Verify (r->GetIdentity (), buf + 47 + asz)) { @@ -2052,7 +2079,7 @@ namespace transport auto vec = std::make_shared >(len); memcpy (vec->data (), buf, len); auto s = shared_from_this (); - m_Server.GetService ().post ([s, vec, attempts]() + boost::asio::post (m_Server.GetService (), [s, vec, attempts]() { LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1); s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1); @@ -2093,6 +2120,7 @@ namespace transport void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) { + if (len < 6) return; uint32_t nonce = bufbe32toh (buf + 2); if (m_State == eSSU2SessionStateIntroduced) { @@ -2113,7 +2141,9 @@ namespace transport auto it = m_RelaySessions.find (nonce); if (it != m_RelaySessions.end ()) { - if (it->second.first && it->second.first->IsEstablished ()) + auto relaySession = it->second.first; + m_RelaySessions.erase (it); + if (relaySession && relaySession->IsEstablished ()) { // we are Bob, message from Charlie auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); @@ -2123,12 +2153,12 @@ namespace transport memcpy (payload + 3, buf, len); // forward to Alice as is packet->payloadSize = len + 3; packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = it->second.first->SendData (packet->payload, packet->payloadSize); + uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) { // sometimes Alice doesn't ack this RelayResponse in older versions packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); - it->second.first->m_SentPackets.emplace (packetNum, packet); + relaySession->m_SentPackets.emplace (packetNum, packet); } } else @@ -2137,25 +2167,31 @@ namespace transport if (!buf[1]) // status code accepted? { // verify signature - uint8_t csz = buf[11]; - SignedData s; + uint8_t csz = (len >= 12) ? buf[11] : 0; + if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len) + { + LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len); + relaySession->Done (); + return; + } + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint - if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz)) + if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz)) { - if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet + if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet { // update Charlie's endpoint - if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint)) + if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) { // update token uint64_t token; memcpy (&token, buf + len - 8, 8); - m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint, + m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); // connect to Charlie, HolePunch will be ignored - it->second.first->ConnectAfterIntroduction (); + relaySession->ConnectAfterIntroduction (); } else LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); @@ -2164,16 +2200,15 @@ namespace transport else { LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); - it->second.first->Done (); + relaySession->Done (); } } else { LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); - it->second.first->Done (); + relaySession->Done (); } } - m_RelaySessions.erase (it); } else LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); @@ -2242,10 +2277,13 @@ namespace transport case 2: // Charlie from Bob { // sign with Charlie's key + if (len < offset + 9) return; uint8_t asz = buf[offset + 9]; - std::vector newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ()); + size_t l = asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen (); + if (len < offset + l) return; + std::vector newSignedData (l); memcpy (newSignedData.data (), buf + offset, asz + 10); - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 3, 32); // ahash @@ -2353,10 +2391,16 @@ namespace transport if (GetRouterStatus () == eRouterStatusUnknown) SetTestingState (true); auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie - if (r) + if (r && len >= offset + 9) { uint8_t asz = buf[offset + 9]; - SignedData s; + if (len < offset + asz + 10 + r->GetIdentity ()->GetSignatureLen ()) + { + LogPrint (eLogWarning, "Malformed PeerTest 4 len=", len); + session->Done (); + return; + } + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash @@ -2624,17 +2668,17 @@ namespace transport size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len) { if (len < 8) return 0; - int maxNumRanges = (len - 8) >> 1; - if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; buf[0] = eSSU2BlkAck; uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); htobe32buf (buf + 3, ackThrough); // Ack Through uint16_t acnt = 0; - int numRanges = 0; if (ackThrough) { if (m_OutOfSequencePackets.empty ()) + { acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps + m_NumRanges = 0; + } else { auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num @@ -2647,93 +2691,102 @@ namespace transport it++; } // ranges - uint32_t lastNum = ackThrough - acnt; - if (acnt > SSU2_MAX_NUM_ACNT) - { - auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); - acnt = SSU2_MAX_NUM_ACNT; - if (d.quot > maxNumRanges) + if (!m_NumRanges) + { + int maxNumRanges = (len - 8) >> 1; + if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; + int numRanges = 0; + uint32_t lastNum = ackThrough - acnt; + if (acnt > SSU2_MAX_NUM_ACNT) { - d.quot = maxNumRanges; - d.rem = 0; - } - // Acks only ranges for acnt - for (int i = 0; i < d.quot; i++) - { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 - numRanges++; - } - if (d.rem > 0) - { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem; - numRanges++; - } - } - int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; - while (it != m_OutOfSequencePackets.rend () && - numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) - { - if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) - { - // NACKs only ranges - if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs - while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); + acnt = SSU2_MAX_NUM_ACNT; + if (d.quot > maxNumRanges) { - buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 - lastNum -= SSU2_MAX_NUM_ACNT; + d.quot = maxNumRanges; + d.rem = 0; + } + // Acks only ranges for acnt + for (int i = 0; i < d.quot; i++) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 + numRanges++; + } + if (d.rem > 0) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = d.rem; numRanges++; - numPackets += SSU2_MAX_NUM_ACNT; } } - // NACKs and Acks ranges - buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs - numPackets += buf[8 + numRanges*2]; - lastNum = *it; it++; - int numAcks = 1; - while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; + while (it != m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - numAcks++; lastNum--; - it++; - } - while (numAcks > SSU2_MAX_NUM_ACNT) - { - // Acks only ranges - buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 - numAcks -= SSU2_MAX_NUM_ACNT; - numRanges++; - numPackets += SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2] = 0; // NACKs 0 - if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; - } - if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks - numPackets += numAcks; - numRanges++; - } - if (it == m_OutOfSequencePackets.rend () && - numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) - { - // add range between out-of-sequence and received - int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; - if (nacks > 0) - { - if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2] = nacks; - buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + // NACKs only ranges + if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + m_Ranges[numRanges*2] = SSU2_MAX_NUM_ACNT; m_Ranges[numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + } + } + // NACKs and Acks ranges + m_Ranges[numRanges*2] = lastNum - (*it) - 1; // NACKs + numPackets += m_Ranges[numRanges*2]; + lastNum = *it; it++; + int numAcks = 1; + while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + while (numAcks > SSU2_MAX_NUM_ACNT) + { + // Acks only ranges + m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 + numAcks -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = 0; // NACKs 0 + if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; + } + if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numPackets += numAcks; numRanges++; } - } + if (it == m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) + { + // add range between out-of-sequence and received + int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; + if (nacks > 0) + { + if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = nacks; + m_Ranges[numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + numRanges++; + } + } + m_NumRanges = numRanges; + } + if (m_NumRanges) + memcpy (buf + 8, m_Ranges, m_NumRanges*2); } } buf[7] = (uint8_t)acnt; // acnt - htobe16buf (buf + 1, 5 + numRanges*2); - return 8 + numRanges*2; + htobe16buf (buf + 1, 5 + m_NumRanges*2); + return 8 + m_NumRanges*2; } size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) { if (len < 3 || len < minSize) return 0; - size_t paddingSize = m_Server.GetRng ()() & 0x0F; // 0 - 15 + size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 if (paddingSize + 3 > len) paddingSize = len - 3; else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; buf[0] = eSSU2BlkPadding; @@ -2835,7 +2888,7 @@ namespace transport LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); return 0; } - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash @@ -2897,7 +2950,7 @@ namespace transport size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); signedData[9] = asz; // signature - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint @@ -2961,11 +3014,17 @@ namespace transport } m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it); } + m_NumRanges = 0; // recalculate ranges when create next Ack } m_ReceivePacketNum = packetNum; } else + { + if (m_NumRanges && (m_OutOfSequencePackets.empty () || + packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1)) + m_NumRanges = 0; // reset ranges if received packet is not next m_OutOfSequencePackets.insert (packetNum); + } return true; } @@ -3114,5 +3173,10 @@ namespace transport else if (!sent && !m_SentPackets.empty ()) // if only acks received, nothing sent and we still have something to resend Resend (i2p::util::GetMillisecondsSinceEpoch ()); // than right time to resend } + + i2p::data::RouterInfo::SupportedTransports SSU2Session::GetTransportType () const + { + return m_RemoteEndpoint.address ().is_v4 () ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6; + } } } diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index 4b3139a7..ee295acb 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2024, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -267,6 +267,7 @@ namespace transport size_t Resend (uint64_t ts); // return number of resent packets uint64_t GetLastResendTime () const { return m_LastResendTime; }; bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; }; + i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; uint64_t GetConnID () const { return m_SourceConnID; }; SSU2SessionState GetState () const { return m_State; }; void SetState (SSU2SessionState state) { m_State = state; }; @@ -396,6 +397,8 @@ namespace transport std::unique_ptr m_PathChallenge; std::unordered_map m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds + int m_NumRanges; + uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any }; inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 342b6d03..f684f10f 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -149,5 +149,179 @@ namespace crypto LogPrint (eLogError, "EdDSA signing key is not set"); } #endif + +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) + static const OSSL_PARAM EDDSA25519phParams[] = + { + OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9), + OSSL_PARAM_END + }; + + bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + auto pkey = GetPkey (); + if (pkey) + { + uint8_t digest[64]; + SHA512 (buf, len, digest); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); + auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64); + EVP_MD_CTX_destroy (ctx); + return ret; + } + else + LogPrint (eLogError, "EdDSA verification key is not set"); + return false; + } + + EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): + EDDSA25519Signer (signingPrivateKey) + { + } + + void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + auto pkey = GetPkey (); + if (pkey) + { + uint8_t digest[64]; + SHA512 (buf, len, digest); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + size_t l = 64; + uint8_t sig[64]; + EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); + if (!EVP_DigestSign (ctx, sig, &l, digest, 64)) + LogPrint (eLogError, "EdDSA signing failed"); + memcpy (signature, sig, 64); + EVP_MD_CTX_destroy (ctx); + } + else + LogPrint (eLogError, "EdDSA signing key is not set"); + } +#endif + +#if OPENSSL_PQ +#include + + MLDSA44Verifier::MLDSA44Verifier (): + m_Pkey (nullptr) + { + } + + MLDSA44Verifier::~MLDSA44Verifier () + { + EVP_PKEY_free (m_Pkey); + } + + void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey) + { + if (m_Pkey) + { + EVP_PKEY_free (m_Pkey); + m_Pkey = nullptr; + } + OSSL_PARAM params[] = + { + OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()), + OSSL_PARAM_END + }; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); + if (ctx) + { + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLDSA44 can't create PKEY context"); + } + + bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + bool ret = false; + if (m_Pkey) + { + EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (vctx) + { + EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); + if (sig) + { + int encode = 1; + OSSL_PARAM params[] = + { + OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), + OSSL_PARAM_END + }; + EVP_PKEY_verify_message_init (vctx, sig, params); + ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len); + EVP_SIGNATURE_free (sig); + } + EVP_PKEY_CTX_free (vctx); + } + else + LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); + } + else + LogPrint (eLogError, "MLDSA44 verification key is not set"); + return ret; + } + + MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey): + m_Pkey (nullptr) + { + OSSL_PARAM params[] = + { + OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH), + OSSL_PARAM_END + }; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); + if (ctx) + { + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLDSA44 can't create PKEY context"); + } + + MLDSA44Signer::~MLDSA44Signer () + { + if (m_Pkey) EVP_PKEY_free (m_Pkey); + } + + void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + if (m_Pkey) + { + EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (sctx) + { + EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); + if (sig) + { + int encode = 1; + OSSL_PARAM params[] = + { + OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), + OSSL_PARAM_END + }; + EVP_PKEY_sign_message_init (sctx, sig, params); + size_t siglen = MLDSA44_SIGNATURE_LENGTH; + EVP_PKEY_sign (sctx, signature, &siglen, buf, len); + EVP_SIGNATURE_free (sig); + } + EVP_PKEY_CTX_free (sctx); + } + else + LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); + } + else + LogPrint (eLogError, "MLDSA44 signing key is not set"); + } + +#endif } } diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 8bd94357..c77e10dd 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -303,14 +303,28 @@ namespace crypto private: -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA + EVP_PKEY * m_Pkey; + + protected: + + EVP_PKEY * GetPkey () const { return m_Pkey; }; #else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; #endif }; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + class EDDSA25519phVerifier: public EDDSA25519Verifier + { + public: + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + }; +#endif + class EDDSA25519SignerCompat: public Signer { public: @@ -339,6 +353,10 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + protected: + + EVP_PKEY * GetPkey () const { return m_Pkey; }; + private: EVP_PKEY * m_Pkey; @@ -350,6 +368,18 @@ namespace crypto #endif +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + class EDDSA25519phSigner: public EDDSA25519Signer + { + public: + + EDDSA25519phSigner (const uint8_t * signingPrivateKey); + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + }; + +#endif + inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { #if OPENSSL_EDDSA @@ -530,6 +560,57 @@ namespace crypto RedDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); } + +#if OPENSSL_PQ +#include + + // Post-Quantum + const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312; + const size_t MLDSA44_SIGNATURE_LENGTH = 2420; + const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560; + class MLDSA44Verifier: public Verifier + { + public: + + MLDSA44Verifier (); + void SetPublicKey (const uint8_t * signingKey); + ~MLDSA44Verifier (); + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + + size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; }; + size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; }; + size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; }; + + private: + + EVP_PKEY * m_Pkey; + }; + + class MLDSA44Signer: public Signer + { + public: + + MLDSA44Signer (const uint8_t * signingPrivateKey); + ~MLDSA44Signer (); + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + + private: + + EVP_PKEY * m_Pkey; + }; + + inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44"); + size_t len = MLDSA44_PUBLIC_KEY_LENGTH; + EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len); + len = MLDSA44_PRIVATE_KEY_LENGTH; + EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len); + EVP_PKEY_free (pkey); + } +#endif } } diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 7a0a39a4..0ed6067c 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -72,15 +72,15 @@ namespace stream m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed m_Status (eStreamStatusNew), m_IsIncoming (false), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), - m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), + m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_RemoteLeaseChangeTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { @@ -99,15 +99,15 @@ namespace stream m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0), m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed - m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), + m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_PrevRTTSample (INITIAL_RTT), m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_RemoteLeaseChangeTime (0), + m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { @@ -364,12 +364,14 @@ namespace stream } if (delayRequested >= DELAY_CHOKING) { - if (!m_IsWinDropped) + if (!m_IsClientChoked) { + LogPrint (eLogDebug, "Streaming: Client choked, set min. window size"); m_WindowDropTargetSize = MIN_WINDOW_SIZE; m_LastWindowDropSize = 0; m_WindowIncCounter = 0; - m_IsWinDropped = true; // don't drop window twice + m_IsClientChoked = true; + m_IsWinDropped = false; m_DropWindowDelaySequenceNumber = m_SequenceNumber; UpdatePacingTime (); } @@ -537,12 +539,21 @@ namespace stream m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; - if (m_WindowSize < MAX_WINDOW_SIZE && !m_IsFirstACK) + if (m_WindowIncCounter < MAX_WINDOW_SIZE && !m_IsFirstACK && !m_IsWinDropped) incCounter++; } else break; } + if (m_LastACKRecieveTime) + { + uint64_t interval = ts - m_LastACKRecieveTime; + if (m_ACKRecieveInterval) + m_ACKRecieveInterval = (m_ACKRecieveInterval + interval) / 2; + else + m_ACKRecieveInterval = interval; + } + m_LastACKRecieveTime = ts; if (rttSample != INT_MAX) { if (m_IsFirstRttSample && !m_IsFirstACK) @@ -588,36 +599,31 @@ namespace stream m_WindowIncCounter = m_WindowIncCounter + incCounter; // // delay-based CC - if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped) // Drop window if RTT grows too fast, late detection + if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast, late detection + { + LogPrint (eLogDebug, "Streaming: Congestion detected, reduce window size"); ProcessWindowDrop (); + } UpdatePacingTime (); m_PrevRTTSample = rttSample; bool wasInitial = m_RTO == INITIAL_RTO; - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter)); // TODO: implement it better + m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter + m_ACKRecieveInterval)); // TODO: implement it better if (wasInitial) ScheduleResend (); } + if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber) + m_IsClientChoked = false; if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) { m_IsFirstRttSample = true; m_IsWinDropped = false; } - if (m_WindowDropTargetSize && m_WindowSize <= m_WindowDropTargetSize) + if (m_WindowDropTargetSize && int(m_SentPackets.size ()) <= m_WindowDropTargetSize) { + m_WindowSize = m_WindowDropTargetSize; m_WindowDropTargetSize = 0; - m_DropWindowDelaySequenceNumber = m_SequenceNumber; - } - if (acknowledged && m_WindowDropTargetSize && m_WindowSize > m_WindowDropTargetSize) - { - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter)); // we assume that the next rtt sample may be much larger than the current - m_IsResendNeeded = true; - m_WindowSize = m_SentPackets.size () + 1; // if there are no packets to resend, just send one regular packet - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; - m_WindowIncCounter = 0; - UpdatePacingTime (); } if (acknowledged || m_IsNAcked) { @@ -626,12 +632,14 @@ namespace stream if (m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss { m_IsResendNeeded = true; - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter)); // to prevent spurious retransmit + m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter + m_ACKRecieveInterval)); // to prevent spurious retransmit } if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) { m_ResendTimer.cancel (); m_SendTimer.cancel (); + m_LastACKRecieveTime = 0; + m_ACKRecieveInterval = m_AckDelay; } if (acknowledged && m_IsFirstACK) { @@ -958,7 +966,7 @@ namespace stream if (choking || requestImmediateAck) { htobe16buf (packet + size, 2); // 2 bytes delay interval - htobe16buf (packet + size + 2, choking ? DELAY_CHOKING : 0); // set choking or immediated ack interval + htobe16buf (packet + size + 2, choking ? DELAY_CHOKING : 0); // set choking or immediate ack interval size += 2; if (requestImmediateAck) // ack request sent { @@ -1147,7 +1155,6 @@ namespace stream m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter)); // TODO: implement it better } } @@ -1159,9 +1166,10 @@ namespace stream } if (m_RemoteLeaseChangeTime && m_IsRemoteLeaseChangeInProgress && ts > m_RemoteLeaseChangeTime + INITIAL_RTO) { + LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); CancelRemoteLeaseChange (); m_CurrentRemoteLease = m_NextRemoteLease; - HalveWindowSize (); + ResetWindowSize (); } auto currentRemoteLease = m_CurrentRemoteLease; if (!m_IsRemoteLeaseChangeInProgress && m_RemoteLeaseSet && m_CurrentRemoteLease && ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) @@ -1196,7 +1204,8 @@ namespace stream } if (freshTunnel) { - m_RTO = INITIAL_RTO; + LogPrint (eLogDebug, "Streaming: OutboundTunnel changed, set initial window size"); + ResetWindowSize (); // m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely } @@ -1286,31 +1295,64 @@ namespace stream m_NumPacketsToSend = 1; m_PacingTimeRem = 0; } m_IsSendTime = true; - if (m_WindowIncCounter && m_WindowSize < MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) + if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT) { + float winSize = m_WindowSize; + if (m_WindowDropTargetSize) + winSize = m_WindowDropTargetSize; + float maxWinSize = MAX_WINDOW_SIZE; + if (m_LastWindowIncTime) + maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize; for (int i = 0; i < m_NumPacketsToSend; i++) { if (m_WindowIncCounter) { - if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowSize)) - m_WindowSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowSize)); // some magic here - else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize)) - m_WindowSize += (m_WindowSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; // some magic here + if (m_WindowDropTargetSize) + { + if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize)) + m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here + else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize)) + m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here + else + m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; + if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE; + m_WindowIncCounter--; + if (m_WindowDropTargetSize >= maxWinSize) + { + m_WindowDropTargetSize = maxWinSize; + break; + } + } else - m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; - if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; - m_WindowIncCounter--; + { + if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowSize)) + m_WindowSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowSize)); // some magic here + else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize)) + m_WindowSize += (m_WindowSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; // some magic here + else + m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; + if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; + m_WindowIncCounter--; + if (m_WindowSize >= maxWinSize) + { + m_WindowSize = maxWinSize; + break; + } + } } else break; } + m_LastWindowIncTime = ts; UpdatePacingTime (); } - if (m_IsNAcked) + else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) + { + m_WindowSizeTail = m_WindowSizeTail + m_WindowIncCounter; + if (m_WindowSizeTail > MAX_WINDOW_SIZE) m_WindowSizeTail = MAX_WINDOW_SIZE; + } + if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) // resend packets ResendPacket (); - else if (m_IsResendNeeded) // resend packets - ResendPacket (); - // delay-based CC else if (m_WindowSize > int(m_SentPackets.size ())) // send packets SendBuffer (); } @@ -1409,8 +1451,11 @@ namespace stream if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) { // loss-based CC - if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED) + if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED && !m_IsClientChoked) + { + LogPrint (eLogDebug, "Streaming: Packet loss, reduce window size"); ProcessWindowDrop (); + } } else if (m_IsTimeOutResend) { @@ -1423,6 +1468,8 @@ namespace stream m_IsFirstRttSample = true; m_DropWindowDelaySequenceNumber = 0; m_IsFirstACK = true; + m_LastACKRecieveTime = 0; + m_ACKRecieveInterval = m_AckDelay; UpdatePacingTime (); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); if (m_NumResendAttempts & 1) @@ -1443,11 +1490,12 @@ namespace stream SendPackets (packets); m_LastSendTime = ts; m_IsSendTime = false; - if (m_IsNAcked || m_IsResendNeeded) ScheduleSend (); + if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) ScheduleSend (); } - else + else if (!m_IsClientChoked) SendBuffer (); if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); + if (m_IsClientChoked) ScheduleSend (); } void Stream::ScheduleAck (int timeout) @@ -1586,7 +1634,8 @@ namespace stream } if (isLeaseChanged && !m_IsRemoteLeaseChangeInProgress) { - HalveWindowSize (); + LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); + ResetWindowSize (); } } @@ -1602,43 +1651,60 @@ namespace stream void Stream::UpdatePacingTime () { - m_PacingTime = std::round (m_RTT*1000/m_WindowSize); + if (m_WindowDropTargetSize) + m_PacingTime = std::round (m_RTT*1000/m_WindowDropTargetSize); + else + m_PacingTime = std::round (m_RTT*1000/m_WindowSize); if (m_MinPacingTime && m_PacingTime < m_MinPacingTime) m_PacingTime = m_MinPacingTime; } void Stream::ProcessWindowDrop () { - if (m_WindowSize > m_LastWindowDropSize) - m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize) / 2; + if (m_WindowDropTargetSize) + m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue else - m_LastWindowDropSize = m_WindowSize; - m_WindowDropTargetSize = m_LastWindowDropSize - (m_LastWindowDropSize / 4); // -25%; - if (m_WindowDropTargetSize < MIN_WINDOW_SIZE + 1) - m_WindowDropTargetSize = MIN_WINDOW_SIZE + 1; - m_WindowSize = m_SentPackets.size (); // stop sending now - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; + { + if (m_WindowSize < m_LastWindowDropSize) + { + m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize))); + if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE; + } + else + { + m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2)); + if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE; + } + m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue + } + if (m_WindowDropTargetSize < MIN_WINDOW_SIZE) + m_WindowDropTargetSize = MIN_WINDOW_SIZE; m_WindowIncCounter = 0; // disable window growth - m_DropWindowDelaySequenceNumber = m_SequenceNumber; + m_DropWindowDelaySequenceNumber = m_SequenceNumber + int(m_WindowDropTargetSize); m_IsFirstACK = true; // ignore first RTT sample m_IsWinDropped = true; // don't drop window twice + m_WindowSizeTail = 0; UpdatePacingTime (); } - void Stream::HalveWindowSize () + void Stream::ResetWindowSize () { m_RTO = INITIAL_RTO; - if (m_WindowSize > INITIAL_WINDOW_SIZE) + if (!m_IsClientChoked) { - m_WindowDropTargetSize = std::max (m_WindowSize/2, (float)INITIAL_WINDOW_SIZE); - m_IsWinDropped = true; + if (m_WindowSize > INITIAL_WINDOW_SIZE) + { + m_WindowDropTargetSize = (float)INITIAL_WINDOW_SIZE; + m_IsWinDropped = true; + } + else + m_WindowSize = INITIAL_WINDOW_SIZE; } - else - m_WindowSize = INITIAL_WINDOW_SIZE; m_LastWindowDropSize = 0; m_WindowIncCounter = 0; m_IsFirstRttSample = true; m_IsFirstACK = true; + m_WindowSizeTail = 0; UpdatePacingTime (); } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 36c13eff..d75cb6eb 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -57,6 +57,7 @@ namespace stream const int INITIAL_WINDOW_SIZE = 10; const int MIN_WINDOW_SIZE = 3; const int MAX_WINDOW_SIZE = 512; + const int MAX_WINDOW_SIZE_INC_PER_RTT = 16; const double RTT_EWMA_ALPHA = 0.25; const double SLOWRTT_EWMA_ALPHA = 0.05; const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer @@ -251,7 +252,7 @@ namespace stream void UpdatePacingTime (); void ProcessWindowDrop (); - void HalveWindowSize (); + void ResetWindowSize (); void CancelRemoteLeaseChange (); private: @@ -272,6 +273,7 @@ namespace stream bool m_IsFirstRttSample; bool m_IsSendTime; bool m_IsWinDropped; + bool m_IsClientChoked; bool m_IsTimeOutResend; bool m_IsImmediateAckRequested; bool m_IsRemoteLeaseChangeInProgress; @@ -295,10 +297,10 @@ namespace stream SendBufferQueue m_SendBuffer; double m_RTT, m_SlowRTT, m_SlowRTT2; float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize; - int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample; + int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail; double m_Jitter; uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds - m_LastSendTime, m_RemoteLeaseChangeTime; // miliseconds + m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime; // milliseconds uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed int m_NumResendAttempts, m_NumPacketsToSend; size_t m_MTU; diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index 72f181a2..af2de154 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,10 +12,14 @@ #include #include #include +#include +#include #include "Base.h" -namespace i2p { -namespace data { +namespace i2p +{ +namespace data +{ template class Tag { @@ -65,19 +69,17 @@ namespace data { std::string ToBase32 (size_t len = sz) const { - char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2); - return std::string (str, str + l); + return i2p::data::ByteStreamToBase32 (m_Buf, len); } - size_t FromBase32 (const std::string& s) + size_t FromBase32 (std::string_view s) { - return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + return i2p::data::Base32ToByteStream (s, m_Buf, sz); } - size_t FromBase64 (const std::string& s) + size_t FromBase64 (std::string_view s) { - return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + return i2p::data::Base64ToByteStream (s.data (), s.length (), m_Buf, sz); } uint8_t GetBit (int i) const diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 8284dc14..b24c8ac5 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,8 @@ #include "I2PEndian.h" #include "Crypto.h" #include "Log.h" +#include "Identity.h" +#include "RouterInfo.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "Garlic.h" @@ -41,6 +43,21 @@ namespace tunnel i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } + std::string TransitTunnel::GetNextPeerName () const + { + return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()); + } + + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) + { + LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); + } + + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + { + LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); + } + TransitTunnelParticipant::~TransitTunnelParticipant () { } @@ -62,44 +79,100 @@ namespace tunnel auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); - i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear } } - void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) + std::string TransitTunnelParticipant::GetNextPeerName () const { - LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); - } - - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) - { - LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); - } - + if (m_Sender) + { + auto transport = m_Sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.SendBuffer (); } + std::string TransitTunnelGateway::GetNextPeerName () const + { + const auto& sender = m_Gateway.GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + std::lock_guard l(m_HandleMutex); + if (!m_Endpoint) m_Endpoint = std::make_unique(false); // transit endpoint is always outbound + m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg); } + void TransitTunnelEndpoint::FlushTunnelDataMsgs () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->FlushI2NPMsgs (); + } + } + + void TransitTunnelEndpoint::Cleanup () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->Cleanup (); + } + } + + std::string TransitTunnelEndpoint::GetNextPeerName () const + { + if (!m_Endpoint) return ""; + auto hash = m_Endpoint->GetCurrentHash (); + if (hash) + { + const auto& sender = m_Endpoint->GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return i2p::data::GetIdentHashAbbreviation (*hash) + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + else + return i2p::data::GetIdentHashAbbreviation (*hash); + } + } + return ""; + } + std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, @@ -123,7 +196,7 @@ namespace tunnel } TransitTunnels::TransitTunnels (): - m_IsRunning (false) + m_IsRunning (false), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) { } @@ -263,19 +336,44 @@ namespace tunnel // check if we accept this tunnel std::shared_ptr transitTunnel; uint8_t retCode = 0; - if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL) + if (i2p::context.AcceptsTunnels ()) + { + auto congestionLevel = i2p::context.GetCongestionLevel (false); + if (congestionLevel < CONGESTION_LEVEL_FULL) + { + if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) + { + // random reject depending on congestion level + int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; + if (congestionLevel > level) + retCode = 30; + } + } + else + retCode = 30; + } + else retCode = 30; + if (!retCode) { - // create new transit tunnel - transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - layerKey, ivKey, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - if (!AddTransitTunnel (transitTunnel)) + i2p::data::IdentHash nextIdent(clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) + { + // create new transit tunnel + transitTunnel = CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + nextIdent, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + isEndpoint); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + else + // decline tunnel going to duplicated router retCode = 30; } @@ -387,7 +485,7 @@ namespace tunnel if (congestionLevel < CONGESTION_LEVEL_FULL) { // random reject depending on congestion level - int level = i2p::tunnel::tunnels.GetRng ()() % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; + int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; if (congestionLevel > level) accept = false; } @@ -395,23 +493,32 @@ namespace tunnel accept = false; } } - // replace record to reply + if (accept) { - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - if (!AddTransitTunnel (transitTunnel)) + i2p::data::IdentHash nextIdent(clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET); + bool isEndpoint = clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) + { + auto transitTunnel = CreateTransitTunnel ( + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + nextIdent, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + isEndpoint); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + else + // decline tunnel going to duplicated router retCode = 30; } else retCode = 30; // always reject with bandwidth reason (30) + // replace record to reply memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; // encrypt reply @@ -434,8 +541,7 @@ namespace tunnel else { encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, reply); } } return true; diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index 14f9b997..34bcc79f 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,11 +33,13 @@ namespace tunnel const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; + virtual std::string GetNextPeerName () const; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg) override; void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; + private: i2p::crypto::AESKey m_LayerKey, m_IVKey; @@ -56,6 +58,7 @@ namespace tunnel ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; }; + std::string GetNextPeerName () const override; void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; void FlushTunnelDataMsgs () override; @@ -63,6 +66,7 @@ namespace tunnel size_t m_NumTransmittedBytes; std::list > m_TunnelDataMsgs; + std::unique_ptr m_Sender; }; class TransitTunnelGateway: public TransitTunnel @@ -78,7 +82,8 @@ namespace tunnel void SendTunnelDataMsg (std::shared_ptr msg) override; void FlushTunnelDataMsgs () override; size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); }; - + std::string GetNextPeerName () const override; + private: std::mutex m_SendMutex; @@ -92,17 +97,19 @@ namespace tunnel TransitTunnelEndpoint (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): - TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), - m_Endpoint (false) {}; // transit endpoint is always outbound - - void Cleanup () override { m_Endpoint.Cleanup (); } + TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {}; + void Cleanup () override; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; - size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); } - + void FlushTunnelDataMsgs () override; + size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; } + std::string GetNextPeerName () const override; + private: - TunnelEndpoint m_Endpoint; + std::mutex m_HandleMutex; + std::unique_ptr m_Endpoint; }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, @@ -144,6 +151,7 @@ namespace tunnel std::unique_ptr m_Thread; std::list > m_TransitTunnels; i2p::util::Queue > m_TunnelBuildMsgQueue; + std::mt19937 m_Rng; public: diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 6c878d11..2cff0b1f 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,7 @@ #define TRANSPORT_SESSION_H__ #include -#include +#include #include #include #include @@ -28,45 +28,51 @@ namespace transport const size_t IPV6_HEADER_SIZE = 40; const size_t UDP_HEADER_SIZE = 8; + template class SignedData { public: - SignedData () {} + SignedData (): m_Size(0) {} SignedData (const SignedData& other) { - m_Stream << other.m_Stream.rdbuf (); + m_Size = other.m_Size; + memcpy (m_Buf, other.m_Buf, m_Size); } void Reset () { - m_Stream.str(""); + m_Size = 0; } - void Insert (const uint8_t * buf, size_t len) + size_t Insert (const uint8_t * buf, size_t len) { - m_Stream.write ((char *)buf, len); + if (m_Size + len > sz) len = sz - m_Size; + memcpy (m_Buf + m_Size, buf, len); + m_Size += len; + return len; } template void Insert (T t) { - m_Stream.write ((char *)&t, sizeof (T)); + Insert ((const uint8_t *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { - return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + return ident->Verify (m_Buf, m_Size, signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { - keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + keys.Sign (m_Buf, m_Size, signature); } private: - std::stringstream m_Stream; + uint8_t m_Buf[sz]; + size_t m_Size; }; const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds @@ -151,6 +157,7 @@ namespace transport }; virtual void SendI2NPMessages (std::list >& msgs) = 0; virtual bool IsEstablished () const = 0; + virtual i2p::data::RouterInfo::SupportedTransports GetTransportType () const = 0; private: diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 03416137..98dbcd94 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -55,6 +55,13 @@ namespace transport m_Thread->join (); m_Thread = nullptr; } + if (!m_Queue.empty ()) + { + // clean up queue + std::queue > tmp; + std::swap (m_Queue, tmp); + } + m_KeysPool.CleanUpMt (); } template @@ -153,7 +160,8 @@ namespace transport m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), - m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0) + m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) { } @@ -331,7 +339,7 @@ namespace transport if (m_IsNAT) { - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } @@ -454,13 +462,6 @@ namespace transport return {}; // invalid future } - std::future > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >& msgs) - { - std::list > msgs1; - msgs.swap (msgs1); - return SendMessages (ident, std::move (msgs1)); - } - std::future > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs) { return boost::asio::post (*m_Service, boost::asio::use_future ([this, ident, msgs = std::move(msgs)] () mutable @@ -560,7 +561,14 @@ namespace transport bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer) { if (!peer->router) // reconnect - peer->SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb + { + auto r = netdb.FindRouter (ident); // try to get new one from netdb + if (r) + { + peer->SetRouter (r); + r->CancelBufferToDelete (); + } + } if (peer->router) // we have RI already { if (peer->priority.empty ()) @@ -647,7 +655,7 @@ namespace transport return true; } - void Transports::SetPriority (std::shared_ptr peer) const + void Transports::SetPriority (std::shared_ptr peer) { static const std::vector ntcp2Priority = @@ -672,8 +680,21 @@ namespace transport auto directTransports = compatibleTransports & peer->router->GetPublishedTransports (); peer->numAttempts = 0; peer->priority.clear (); - bool isReal = peer->router->GetProfile ()->IsReal (); - bool ssu2 = isReal ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real + + std::shared_ptr profile; + if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory + bool ssu2 = false; // NTCP2 by default + bool isReal = profile ? profile->IsReal () : true; + if (isReal) + { + ssu2 = m_Rng () & 1; // 1/2 + if (ssu2 && !profile) + { + profile = peer->router->GetProfile (); // load profile if necessary + isReal = profile->IsReal (); + if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real + } + } const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; if (directTransports) { @@ -701,7 +722,8 @@ namespace transport // try recently connected SSU2 if any auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & peer->router->GetCompatibleTransports (false); - if (supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) + if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && + peer->router->HasProfile ()) { auto ep = peer->router->GetProfile ()->GetLastEndpoint (); if (!ep.address ().is_unspecified () && ep.port ()) @@ -791,7 +813,7 @@ namespace transport } else { - testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE; + testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; if (m_Service) { auto delayTimer = std::make_shared(*m_Service); @@ -829,7 +851,7 @@ namespace transport } else { - testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE; + testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; if (m_Service) { auto delayTimer = std::make_shared(*m_Service); @@ -886,7 +908,11 @@ namespace transport auto transport = peer->priority[peer->numAttempts-1]; if (transport == i2p::data::RouterInfo::eNTCP2V4 || transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh) - peer->router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real + i2p::data::UpdateRouterProfile (ident, + [](std::shared_ptr profile) + { + if (profile) profile->Connected (); // outgoing NTCP2 connection if always real + }); i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable } peer->numAttempts = 0; @@ -921,7 +947,11 @@ namespace transport session->SendI2NPMessages (msgs); // send DatabaseStore } auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed - if (r) r->GetProfile ()->Connected (); + i2p::data::UpdateRouterProfile (ident, + [](std::shared_ptr profile) + { + if (profile) profile->Connected (); + }); auto ts = i2p::util::GetSecondsSinceEpoch (); auto peer = std::make_shared(r, ts); peer->sessions.push_back (session); @@ -955,8 +985,13 @@ namespace transport } else { - std::lock_guard l(m_PeersMutex); - m_Peers.erase (it); + { + std::lock_guard l(m_PeersMutex); + m_Peers.erase (it); + } + // delete buffer of just disconnected router + auto r = i2p::data::netdb.FindRouter (ident); + if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete (); } } } @@ -1006,7 +1041,7 @@ namespace transport if (session) session->SendLocalRouterInfo (true); it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + - rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; + m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; } ++it; } @@ -1020,7 +1055,7 @@ namespace transport // if still testing or unknown, repeat peer test if (ipv4Testing || ipv6Testing) PeerTest (ipv4Testing, ipv6Testing); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } @@ -1030,7 +1065,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { PeerTest (); - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } @@ -1160,7 +1195,7 @@ namespace transport std::lock_guard lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) - m_TrustedRouters.push_back(ri); + m_TrustedRouters.insert(ri); } bool Transports::RoutesRestricted() const @@ -1177,7 +1212,7 @@ namespace transport } /** XXX: if routes are not restricted this dies */ - std::shared_ptr Transports::GetRestrictedPeer() const + std::shared_ptr Transports::GetRestrictedPeer() { { std::lock_guard l(m_FamilyMutex); @@ -1186,7 +1221,7 @@ namespace transport if(sz > 1) { auto it = m_TrustedFamilies.begin (); - std::advance(it, rand() % sz); + std::advance(it, m_Rng() % sz); fam = *it; } else if (sz == 1) @@ -1201,23 +1236,32 @@ namespace transport auto sz = m_TrustedRouters.size(); if (sz) { - if(sz == 1) - return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); auto it = m_TrustedRouters.begin(); - std::advance(it, rand() % sz); + if(sz > 1) + std::advance(it, m_Rng() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } - bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const + bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const { - { - std::lock_guard l(m_TrustedRoutersMutex); - for (const auto & r : m_TrustedRouters ) - if ( r == ih ) return true; - } + if (m_TrustedRouters.empty ()) return false; + std::lock_guard l(m_TrustedRoutersMutex); +#if __cplusplus >= 202002L // C++20 + if (m_TrustedRouters.contains (ih)) +#else + if (m_TrustedRouters.count (ih) > 0) +#endif + return true; + return false; + } + + bool Transports::IsRestrictedPeer(const i2p::data::IdentHash& ih) const + { + if (IsTrustedRouter (ih)) return true; + { std::lock_guard l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index b72d5b70..fcd2cfc6 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,11 +15,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include "TransportSession.h" #include "SSU2.h" @@ -54,8 +56,8 @@ namespace transport private: const int m_QueueSize; - std::queue > m_Queue; i2p::util::MemoryPoolMt m_KeysPool; + std::queue > m_Queue; bool m_IsRunning; std::unique_ptr m_Thread; @@ -106,7 +108,8 @@ namespace transport }; const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds - const int PEER_TEST_INTERVAL = 71; // in minutes + const int PEER_TEST_INTERVAL = 68*60; // in seconds + const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds const int MAX_NUM_DELAYED_MESSAGES = 150; @@ -145,7 +148,6 @@ namespace transport void ReuseX25519KeysPair (std::shared_ptr pair); std::future > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); - std::future > SendMessages (const i2p::data::IdentHash& ident, std::list >& msgs); std::future > SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs); void PeerConnected (std::shared_ptr session); @@ -169,7 +171,7 @@ namespace transport std::shared_ptr GetRandomPeer (bool isHighBandwidth) const; /** get a trusted first hop for restricted routes */ - std::shared_ptr GetRestrictedPeer() const; + std::shared_ptr GetRestrictedPeer(); /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ @@ -177,7 +179,8 @@ namespace transport /** restrict routes to use only these routers for first hops */ void RestrictRoutesToRouters(const std::set& routers); - bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; + bool IsTrustedRouter (const i2p::data::IdentHash& ih) const; + bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const; void PeerTest (bool ipv4 = true, bool ipv6 = true); @@ -192,7 +195,7 @@ namespace transport void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); std::shared_ptr PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs); bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer); - void SetPriority (std::shared_ptr peer) const; + void SetPriority (std::shared_ptr peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); @@ -236,10 +239,11 @@ namespace transport mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ - std::vector m_TrustedRouters; + std::unordered_set m_TrustedRouters; mutable std::mutex m_TrustedRoutersMutex; i2p::I2NPMessagesHandler m_LoopbackHandler; + std::mt19937 m_Rng; public: diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index e7443b6c..1b317121 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -103,7 +103,7 @@ namespace tunnel if (m_Config->IsShort ()) { auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; - if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP + if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); if (msg1) msg = msg1; @@ -179,9 +179,12 @@ namespace tunnel { uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); - auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); - if (profile) - profile->TunnelBuildResponse (ret); + if (hop->ident) + i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), + [ret](std::shared_ptr profile) + { + if (profile) profile->TunnelBuildResponse (ret); + }); if (ret) // if any of participants declined the tunnel is not established established = false; @@ -278,6 +281,21 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } + bool InboundTunnel::Recreate () + { + if (!IsRecreated ()) + { + auto pool = GetTunnelPool (); + if (pool) + { + SetRecreated (true); + pool->RecreateInboundTunnel (std::static_pointer_cast(shared_from_this ())); + return true; + } + } + return false; + } + ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) @@ -297,22 +315,28 @@ namespace tunnel void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; + block.tunnelID = 0; // Initialize tunnelID to a default value + if (gwHash) { block.hash = gwHash; if (gwTunnel) { block.deliveryType = eDeliveryTypeTunnel; - block.tunnelID = gwTunnel; + block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero } else + { block.deliveryType = eDeliveryTypeRouter; + } } else + { block.deliveryType = eDeliveryTypeLocal; + } + block.data = msg; - - SendTunnelDataMsgs ({block}); + SendTunnelDataMsgs({block}); } void OutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) @@ -328,6 +352,21 @@ namespace tunnel LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); } + bool OutboundTunnel::Recreate () + { + if (!IsRecreated ()) + { + auto pool = GetTunnelPool (); + if (pool) + { + SetRecreated (true); + pool->RecreateOutboundTunnel (std::static_pointer_cast(shared_from_this ())); + return true; + } + } + return false; + } + ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) @@ -434,7 +473,7 @@ namespace tunnel std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; - uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; + uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { @@ -711,8 +750,17 @@ namespace tunnel void Tunnels::ManageTunnels (uint64_t ts) { ManagePendingTunnels (ts); - ManageInboundTunnels (ts); - ManageOutboundTunnels (ts); + std::vector > tunnelsToRecreate; + ManageInboundTunnels (ts, tunnelsToRecreate); + ManageOutboundTunnels (ts, tunnelsToRecreate); + // rec-create in random order + if (!tunnelsToRecreate.empty ()) + { + if (tunnelsToRecreate.size () > 1) + std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng); + for (auto& it: tunnelsToRecreate) + it->Recreate (); + } } void Tunnels::ManagePendingTunnels (uint64_t ts) @@ -743,11 +791,11 @@ namespace tunnel while (hop) { if (hop->ident) - { - auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); - if (profile) - profile->TunnelNonReplied (); - } + i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->TunnelNonReplied (); + }); hop = hop->next; } } @@ -775,7 +823,7 @@ namespace tunnel } } - void Tunnels::ManageOutboundTunnels (uint64_t ts) + void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { @@ -799,10 +847,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool has been reconfigured and this is old if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateOutboundTunnel (tunnel); - } + toRecreate.push_back (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); @@ -827,7 +872,7 @@ namespace tunnel } } - void Tunnels::ManageInboundTunnels (uint64_t ts) + void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { @@ -851,10 +896,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool was reconfigured and has different number of hops if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateInboundTunnel (tunnel); - } + toRecreate.push_back (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index e11a360d..5d21cd8b 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -99,6 +99,7 @@ namespace tunnel void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; + virtual bool Recreate () = 0; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; @@ -150,6 +151,7 @@ namespace tunnel void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; bool IsInbound() const override { return false; } + bool Recreate () override; private: @@ -166,6 +168,7 @@ namespace tunnel void HandleTunnelDataMsg (std::shared_ptr&& msg) override; virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; bool IsInbound() const override { return true; } + bool Recreate () override; // override TunnelBase void Cleanup () override { m_Endpoint.Cleanup (); }; @@ -245,8 +248,6 @@ namespace tunnel void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels); uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } - - std::mt19937& GetRng () { return m_Rng; }; private: @@ -264,8 +265,8 @@ namespace tunnel void Run (); void ManageTunnels (uint64_t ts); - void ManageOutboundTunnels (uint64_t ts); - void ManageInboundTunnels (uint64_t ts); + void ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate); + void ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate); void ManagePendingTunnels (uint64_t ts); template void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); @@ -296,6 +297,8 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; @@ -306,8 +309,6 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; - i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; uint32_t m_MaxNumTransitTunnels; // count of tunnels for total TCSR algorithm int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations; diff --git a/libi2pd/TunnelBase.cpp b/libi2pd/TunnelBase.cpp new file mode 100644 index 00000000..b5a4a0b3 --- /dev/null +++ b/libi2pd/TunnelBase.cpp @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include "Transports.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >&& msgs) + { + if (msgs.empty ()) return; + auto currentTransport = m_CurrentTransport.lock (); + if (!currentTransport) + { + // try to obtain transport from pending request or send thought transport is not complete + if (m_PendingTransport.valid ()) // pending request? + { + if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + { + // pending request complete + currentTransport = m_PendingTransport.get (); // take transports used in pending request + if (currentTransport) + { + if (currentTransport->IsEstablished ()) + m_CurrentTransport = currentTransport; + else + currentTransport = nullptr; + } + } + else // still pending + { + // send through transports, but don't update pending transport + i2p::transport::transports.SendMessages (to, std::move (msgs)); + return; + } + } + } + if (currentTransport) // session is good + // send to session directly + currentTransport->SendI2NPMessages (msgs); + else // no session yet + // send through transports + m_PendingTransport = i2p::transport::transports.SendMessages (to, std::move (msgs)); + + } + + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >& msgs) + { + std::list > msgs1; + msgs.swap (msgs1); + SendMessagesTo (to, std::move (msgs1)); + } + + void TunnelTransportSender::Reset () + { + m_CurrentTransport.reset (); + if (m_PendingTransport.valid ()) + m_PendingTransport = std::future >(); + } +} +} diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index d58ec2d7..39d6e780 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,12 +11,19 @@ #include #include +#include +#include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { +namespace transport +{ + class TransportSession; +} + namespace tunnel { const size_t TUNNEL_DATA_MSG_SIZE = 1028; @@ -76,6 +83,25 @@ namespace tunnel return t1 < t2; } }; + + class TunnelTransportSender final + { + public: + + TunnelTransportSender () = default; + ~TunnelTransportSender () = default; + + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >&& msgs); + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >& msgs); // send and clear + + std::shared_ptr GetCurrentTransport () const { return m_CurrentTransport.lock (); } + void Reset (); + + private: + + std::weak_ptr m_CurrentTransport; + std::future > m_PendingTransport; + }; } } diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index e19b515d..fe0e8573 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -79,8 +79,7 @@ namespace tunnel uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); - decryption.SetIV (replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record); } void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 3dc0dc07..66b7effa 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,10 +21,7 @@ namespace i2p { namespace tunnel { - TunnelEndpoint::~TunnelEndpoint () - { - } - + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; @@ -261,9 +258,8 @@ namespace tunnel void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size) { - std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); - memcpy (f->data.data (), fragment, size); - if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) + if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, + isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second) LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -293,7 +289,7 @@ namespace tunnel if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); - size_t size = it->second->data.size (); + size_t size = it->second.data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); @@ -301,9 +297,9 @@ namespace tunnel *newMsg = *(msg.data); msg.data = newMsg; } - if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment + if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); - if (it->second->isLastFragment) + if (it->second.isLastFragment) // message complete msg.nextFragmentNum = 0; else @@ -331,13 +327,13 @@ namespace tunnel break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel - i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); + SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel - i2p::transport::transports.SendMessage (msg.hash, msg.data); + i2p::transport::transports.SendMessage (msg.hash, msg.data); // send right away, because most likely it's single message else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; @@ -352,7 +348,7 @@ namespace tunnel // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { - if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) + if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; @@ -366,5 +362,35 @@ namespace tunnel ++it; } } + + void TunnelEndpoint::SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr msg) + { + if (msg) + { + if (!m_Sender && m_I2NPMsgs.empty ()) // first message + m_CurrentHash = to; + else if (m_CurrentHash != to) // new target router + { + FlushI2NPMsgs (); // flush message to previous + if (m_Sender) m_Sender->Reset (); // reset sender + m_CurrentHash = to; // set new target router + } // otherwise add msg to the list for current target router + m_I2NPMsgs.push_back (msg); + } + } + + void TunnelEndpoint::FlushI2NPMsgs () + { + if (!m_I2NPMsgs.empty ()) + { + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (m_CurrentHash, m_I2NPMsgs); // send and clear + } + } + + const i2p::data::IdentHash * TunnelEndpoint::GetCurrentHash () const + { + return (m_Sender || !m_I2NPMsgs.empty ()) ? &m_CurrentHash : nullptr; + } } } diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 17590a5f..1e81c445 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -20,7 +22,7 @@ namespace i2p { namespace tunnel { - class TunnelEndpoint + class TunnelEndpoint final { struct TunnelMessageBlockEx: public TunnelMessageBlock { @@ -30,7 +32,8 @@ namespace tunnel struct Fragment { - Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; + Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): + isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); }; bool isLastFragment; uint64_t receiveTime; // milliseconds since epoch std::vector data; @@ -39,18 +42,23 @@ namespace tunnel public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; - ~TunnelEndpoint (); + ~TunnelEndpoint () = default; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); + void FlushI2NPMsgs (); + const i2p::data::IdentHash * GetCurrentHash () const; // return null if not available + const std::unique_ptr& GetSender () const { return m_Sender; }; + private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success void HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); + void SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added @@ -60,11 +68,15 @@ namespace tunnel private: std::unordered_map m_IncompleteMessages; - std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; uint32_t m_CurrentMsgID; + // I2NP messages to send + std::list > m_I2NPMsgs; // to send + i2p::data::IdentHash m_CurrentHash; // send msgs to + std::unique_ptr m_Sender; }; } } diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index fabc71bb..9e27d207 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -235,40 +235,9 @@ namespace tunnel m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } m_Buffer.ClearTunnelDataMsgs (); - // send - auto currentTransport = m_CurrentTransport.lock (); - if (!currentTransport) - { - // try to obtain transport from peding reequest or send thought transport is not complete - if (m_PendingTransport.valid ()) // pending request? - { - if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready) - { - // pending request complete - currentTransport = m_PendingTransport.get (); // take tarnsports used in pending request - if (currentTransport) - { - if (currentTransport->IsEstablished ()) - m_CurrentTransport = currentTransport; - else - currentTransport = nullptr; - } - } - else // still pending - { - // send through transports, but don't update pedning transport - i2p::transport::transports.SendMessages (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs)); - return; - } - } - } - if (currentTransport) // session is good - // send to session directly - currentTransport->SendI2NPMessages (newTunnelMsgs); - else // no session yet - // send through transports - m_PendingTransport = i2p::transport::transports.SendMessages (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs)); + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs)); } } } diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h index 4cfd5308..75f27581 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -12,9 +12,7 @@ #include #include #include -#include #include "I2NPProtocol.h" -#include "TransportSession.h" #include "TunnelBase.h" namespace i2p @@ -53,14 +51,14 @@ namespace tunnel void PutTunnelDataMsg (const TunnelMessageBlock& block); void SendBuffer (); size_t GetNumSentBytes () const { return m_NumSentBytes; }; + const std::unique_ptr& GetSender () const { return m_Sender; }; private: TunnelBase& m_Tunnel; TunnelGatewayBuffer m_Buffer; size_t m_NumSentBytes; - std::weak_ptr m_CurrentTransport; - std::future > m_PendingTransport; + std::unique_ptr m_Sender; }; } } diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 3d50738c..26367aa6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -351,10 +351,13 @@ namespace tunnel { it.second.first->SetState (eTunnelStateFailed); std::unique_lock l(m_OutboundTunnelsMutex); - if (m_OutboundTunnels.size () > 1 || m_NumOutboundTunnels <= 1) // don't fail last tunnel + if (m_OutboundTunnels.size () > 1) // don't fail last tunnel m_OutboundTunnels.erase (it.second.first); else + { it.second.first->SetState (eTunnelStateTestFailed); + CreateOutboundTunnel (); // create new tunnel immediately because last one failed + } } else if (it.second.first->GetState () != eTunnelStateExpiring) it.second.first->SetState (eTunnelStateTestFailed); @@ -368,13 +371,16 @@ namespace tunnel bool failed = false; { std::unique_lock l(m_InboundTunnelsMutex); - if (m_InboundTunnels.size () > 1 || m_NumInboundTunnels <= 1) // don't fail last tunnel + if (m_InboundTunnels.size () > 1) // don't fail last tunnel { m_InboundTunnels.erase (it.second.second); failed = true; } else + { it.second.second->SetState (eTunnelStateTestFailed); + CreateInboundTunnel (); // create new tunnel immediately because last one failed + } } if (failed && m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (true); @@ -560,7 +566,7 @@ namespace tunnel i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); if (hop) { - if (!hop->GetProfile ()->IsBad ()) + if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ()) break; } else if (tryClient) @@ -588,7 +594,7 @@ namespace tunnel (inbound && i2p::transport::transports.GetNumPeers () > 25)) { auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); - if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && + if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 { prevHop = r; diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 05f962f3..7dc11157 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -37,9 +37,7 @@ namespace api i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); - bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); - i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt); + i2p::crypto::InitCrypto (precomputation); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index aff3d0e0..925cf629 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -123,8 +124,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) #endif #endif -#define address_pair_v4(a,b) { boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () } -#define address_pair_v6(a,b) { boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () } +#define address_pair_v4(a,b) std::pair{ boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () } +#define address_pair_v6(a,b) std::pair{ boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () } namespace i2p { @@ -454,9 +455,9 @@ namespace net #ifdef _WIN32 LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32"); if (ipv6) - return boost::asio::ip::address::from_string("::1"); + return boost::asio::ip::make_address("::1"); else - return boost::asio::ip::address::from_string("127.0.0.1"); + return boost::asio::ip::make_address("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs *addrs; @@ -647,7 +648,8 @@ namespace net if (host.is_unspecified ()) return false; if (host.is_v4()) { - static const std::vector< std::pair > reservedIPv4Ranges { + static const std::array, 14> reservedIPv4Ranges + { address_pair_v4("0.0.0.0", "0.255.255.255"), address_pair_v4("10.0.0.0", "10.255.255.255"), address_pair_v4("100.64.0.0", "100.127.255.255"), @@ -672,7 +674,8 @@ namespace net } if (host.is_v6()) { - static const std::vector< std::pair > reservedIPv6Ranges { + static const std::array, 7> reservedIPv6Ranges + { address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64 address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), diff --git a/libi2pd/version.h b/libi2pd/version.h index 40d07845..1e63ae08 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -18,7 +18,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 54 +#define I2PD_VERSION_MINOR 56 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #ifdef GITVER @@ -33,7 +33,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 64 +#define I2P_VERSION_MICRO 65 #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/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index c9452cc6..c0f440f9 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -17,6 +17,7 @@ #include #include "Base.h" #include "util.h" +#include "Timestamp.h" #include "Identity.h" #include "FS.h" #include "Log.h" @@ -49,22 +50,23 @@ namespace client if (m_IsPersist) i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); } - std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; - void AddAddress (std::shared_ptr address); - void RemoveAddress (const i2p::data::IdentHash& ident); + std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) override; + void AddAddress (std::shared_ptr address) override; + void RemoveAddress (const i2p::data::IdentHash& ident) override; + void CleanUpCache () override; - bool Init (); - int Load (std::map > & addresses); - int LoadLocal (std::map >& addresses); - int Save (const std::map >& addresses); - - void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); - bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); - void ResetEtags (); + bool Init () override; + int Load (Addresses& addresses) override; + int LoadLocal (Addresses& addresses) override; + int Save (const Addresses& addresses) override; + void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified) override; + bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) override; + void ResetEtags () override; + private: - int LoadFromFile (const std::string& filename, std::map >& addresses); // returns -1 if can't open file, otherwise number of records + int LoadFromFile (const std::string& filename, Addresses& addresses); // returns -1 if can't open file, otherwise number of records private: @@ -72,6 +74,8 @@ namespace client std::string etagsPath, indexPath, localPath; bool m_IsPersist; std::string m_HostsFile; // file to dump hosts.txt, empty if not used + std::unordered_map, uint64_t> > m_FullAddressCache; // ident hash -> (full ident buffer, last access timestamp) + std::mutex m_FullAddressCacheMutex; }; bool AddressBookFilesystemStorage::Init() @@ -92,8 +96,19 @@ namespace client return false; } - std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) { + auto ts = i2p::util::GetMonotonicSeconds (); + { + std::lock_guard l(m_FullAddressCacheMutex); + auto it = m_FullAddressCache.find (ident); + if (it != m_FullAddressCache.end ()) + { + it->second.second = ts; + return std::make_shared(it->second.first.data (), it->second.first.size ()); + } + } + if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); @@ -101,48 +116,72 @@ namespace client } std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); - if (!f.is_open ()) { + if (!f.is_open ()) + { LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); return nullptr; } f.seekg (0,std::ios::end); size_t len = f.tellg (); - if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { + if (len < i2p::data::DEFAULT_IDENTITY_SIZE) + { LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); return nullptr; } f.seekg(0, std::ios::beg); - uint8_t * buf = new uint8_t[len]; - f.read((char *)buf, len); - auto address = std::make_shared(buf, len); - delete[] buf; - return address; + std::vector buf(len); + f.read((char *)buf.data (), len); + if (!f) + { + LogPrint (eLogError, "Addressbook: Couldn't read ", filename); + return nullptr; + } + { + std::lock_guard l(m_FullAddressCacheMutex); + m_FullAddressCache.try_emplace (ident, buf, ts); + } + return std::make_shared(buf.data (), len); } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - if (!m_IsPersist) return; - std::string path = storage.Path( address->GetIdentHash().ToBase32() ); - std::ofstream f (path, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) { - LogPrint (eLogError, "Addressbook: Can't open file ", path); - return; - } + if (!address) return; size_t len = address->GetFullLen (); - uint8_t * buf = new uint8_t[len]; - address->ToBuffer (buf, len); - f.write ((char *)buf, len); - delete[] buf; + std::vector buf; + if (!len) return; // invalid address + { + std::lock_guard l(m_FullAddressCacheMutex); + auto [it, inserted] = m_FullAddressCache.try_emplace (address->GetIdentHash(), len, i2p::util::GetMonotonicSeconds ()); + if (inserted) + address->ToBuffer (it->second.first.data (), len); + if (m_IsPersist) + buf = it->second.first; + } + if (m_IsPersist && !buf.empty ()) + { + std::string path = storage.Path(address->GetIdentHash().ToBase32()); + std::ofstream f (path, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) + { + LogPrint (eLogError, "Addressbook: Can't open file ", path); + return; + } + f.write ((const char *)buf.data (), len); + } } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { + { + std::lock_guard l(m_FullAddressCacheMutex); + m_FullAddressCache.erase (ident); + } if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } - int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map >& addresses) + int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode @@ -168,7 +207,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Load (std::map >& addresses) + int AddressBookFilesystemStorage::Load (Addresses& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) @@ -182,7 +221,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::LoadLocal (std::map >& addresses) + int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; @@ -190,7 +229,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Save (const std::map >& addresses) + int AddressBookFilesystemStorage::Save (const Addresses& addresses) { if (addresses.empty()) { @@ -281,9 +320,22 @@ namespace client } } + void AddressBookFilesystemStorage::CleanUpCache () + { + auto ts = i2p::util::GetMonotonicSeconds (); + std::lock_guard l(m_FullAddressCacheMutex); + for (auto it = m_FullAddressCache.begin (); it != m_FullAddressCache.end ();) + { + if (ts > it->second.second + ADDRESS_CACHE_EXPIRATION_TIMEOUT) + it = m_FullAddressCache.erase (it); + else + it++; + } + } + //--------------------------------------------------------------------- - Address::Address (const std::string& b32): + Address::Address (std::string_view b32): addressType (eAddressInvalid) { if (b32.length () <= B33_ADDRESS_THRESHOLD) @@ -327,6 +379,7 @@ namespace client LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); StartLookups (); + ScheduleCacheUpdate (); } } @@ -341,9 +394,14 @@ namespace client StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { - delete m_SubscriptionsUpdateTimer; + m_SubscriptionsUpdateTimer->cancel (); m_SubscriptionsUpdateTimer = nullptr; } + if (m_AddressCacheUpdateTimer) + { + m_AddressCacheUpdateTimer->cancel (); + m_AddressCacheUpdateTimer = nullptr; + } bool isDownloading = m_Downloading.valid (); if (isDownloading) { @@ -377,7 +435,7 @@ namespace client m_Subscriptions.clear (); } - std::shared_ptr AddressBook::GetAddress (const std::string& address) + std::shared_ptr AddressBook::GetAddress (std::string_view address) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) @@ -385,17 +443,18 @@ namespace client auto addr = std::make_shared(address.substr (0, pos)); return addr->IsValid () ? addr : nullptr; } - else + else +#if __cplusplus >= 202002L // C++20 + if (address.ends_with (".i2p")) +#else + if (address.find (".i2p") != std::string::npos) +#endif { - pos = address.find (".i2p"); - if (pos != std::string::npos) - { - if (!m_IsEnabled) return nullptr; - auto addr = FindAddress (address); - if (!addr) - LookupAddress (address); // TODO: - return addr; - } + if (!m_IsEnabled) return nullptr; + auto addr = FindAddress (address); + if (!addr) + LookupAddress (address); // TODO: + return addr; } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; @@ -404,7 +463,7 @@ namespace client return std::make_shared(dest.GetIdentHash ()); } - std::shared_ptr AddressBook::FindAddress (const std::string& address) + std::shared_ptr AddressBook::FindAddress (std::string_view address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) @@ -508,29 +567,35 @@ namespace client if (pos != std::string::npos) { - std::string name = s.substr(0, pos++); - std::string addr = s.substr(pos); + std::string_view name = std::string_view(s).substr(0, pos++); + std::string_view addr = std::string_view(s).substr(pos); size_t pos = addr.find('#'); - if (pos != std::string::npos) + if (pos != addr.npos) addr = addr.substr(0, pos); // remove comments - - pos = name.find(".b32.i2p"); - if (pos != std::string::npos) +#if __cplusplus >= 202002L // C++20 + if (name.ends_with (".b32.i2p")) +#else + if (name.find(".b32.i2p") != name.npos) +#endif { LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); continue; } - pos = name.find(".i2p"); - if (pos == std::string::npos) +#if __cplusplus >= 202002L // C++20 + if (name.ends_with (".i2p")) +#else + if (name.find(".i2p") == name.npos) +#endif { LogPrint (eLogError, "Addressbook: Malformed domain: ", name); continue; } auto ident = std::make_shared (); - if (!ident->FromBase64(addr)) { + if (!ident->FromBase64(addr)) + { LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); incomplete = f.eof (); continue; @@ -609,7 +674,7 @@ namespace client void AddressBook::LoadLocal () { if (!m_Storage) return; - std::map> localAddresses; + AddressBookStorage::Addresses localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { @@ -682,7 +747,7 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ()); + m_SubscriptionsUpdateTimer = std::make_unique(dest->GetService ()); m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); @@ -766,7 +831,7 @@ namespace client } } - void AddressBook::LookupAddress (const std::string& address) + void AddressBook::LookupAddress (std::string_view address) { std::shared_ptr addr; auto dot = address.find ('.'); @@ -796,7 +861,7 @@ namespace client memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); - memcpy (buf + 9, address.c_str (), address.length ()); + memcpy (buf + 9, address.data (), address.length ()); datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } @@ -833,6 +898,29 @@ namespace client } } + void AddressBook::ScheduleCacheUpdate () + { + if (!m_AddressCacheUpdateTimer) + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + if(dest) + m_AddressCacheUpdateTimer = std::make_unique(dest->GetService ()); + } + if (m_AddressCacheUpdateTimer) + { + m_AddressCacheUpdateTimer->expires_from_now (boost::posix_time::seconds(ADDRESS_CACHE_UPDATE_INTERVAL )); + m_AddressCacheUpdateTimer->async_wait ( + [this](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_Storage) m_Storage->CleanUpCache (); + ScheduleCacheUpdate (); + } + }); + } + } + AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link): m_Book (book), m_Link (link) { diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 553ae51b..8b32aa93 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -34,7 +34,9 @@ namespace client const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES = 10; // then update timeout - const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in second + const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in seconds + const int ADDRESS_CACHE_EXPIRATION_TIMEOUT = 710; // in seconds + const int ADDRESS_CACHE_UPDATE_INTERVAL = 76; // in seconds const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; @@ -47,7 +49,7 @@ namespace client i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; - Address (const std::string& b32); + Address (std::string_view b32); Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; bool IsValid () const { return addressType != eAddressInvalid; }; @@ -59,15 +61,18 @@ namespace client { public: + typedef std::map, std::less<> > Addresses; + virtual ~AddressBookStorage () {}; - virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 0; + virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) = 0; virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; + virtual void CleanUpCache () = 0; virtual bool Init () = 0; - virtual int Load (std::map >& addresses) = 0; - virtual int LoadLocal (std::map >& addresses) = 0; - virtual int Save (const std::map >& addresses) = 0; + virtual int Load (Addresses& addresses) = 0; + virtual int LoadLocal (Addresses& addresses) = 0; + virtual int Save (const Addresses& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0; @@ -79,16 +84,16 @@ namespace client class AddressBook { public: - + AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); - std::shared_ptr GetAddress (const std::string& address); + std::shared_ptr GetAddress (std::string_view address); std::shared_ptr GetFullAddress (const std::string& address); - std::shared_ptr FindAddress (const std::string& address); - void LookupAddress (const std::string& address); + std::shared_ptr FindAddress (std::string_view address); + void LookupAddress (std::string_view address); void InsertAddress (const std::string& address, const std::string& jump); // for jump links void InsertFullAddress (std::shared_ptr address); @@ -118,10 +123,12 @@ namespace client void StopLookups (); void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void ScheduleCacheUpdate (); + private: std::mutex m_AddressBookMutex; - std::map > m_Addresses; + AddressBookStorage::Addresses m_Addresses; std::map > m_Resolvers; // local destination->resolver std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address @@ -131,7 +138,7 @@ namespace client int m_NumRetries; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet - boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; + std::unique_ptr m_SubscriptionsUpdateTimer, m_AddressCacheUpdateTimer; bool m_IsEnabled; }; diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index f2dab223..8d94e94b 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,24 @@ namespace i2p { namespace client { + void BOBI2PTunnelIncomingConnection::Established () + { + if (m_IsQuiet) + StreamReceive (); + else + { + // send destination first like received from I2P + std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 (); + dest += "\n"; + if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE) + memcpy (GetStreamBuffer (), dest.c_str (), dest.size ()); + else + memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + HandleStreamReceive (boost::system::error_code (), dest.size ()); + } + Receive (); + } + BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) { @@ -156,7 +174,7 @@ namespace client { if (stream) { - auto conn = std::make_shared (this, stream, m_Endpoint, m_IsQuiet); + auto conn = std::make_shared (this, stream, m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 79066154..f5aefd0a 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -71,6 +71,23 @@ namespace client const char BOB_HELP_STATUS[] = "status - Display status of a nicknamed tunnel."; const char BOB_HELP_HELP [] = "help - Get help on a command."; + class BOBI2PTunnelIncomingConnection: public I2PTunnelConnection + { + public: + + BOBI2PTunnelIncomingConnection (I2PService * owner, std::shared_ptr stream, + const boost::asio::ip::tcp::endpoint& target, bool quiet): + I2PTunnelConnection (owner, stream, target), m_IsQuiet (quiet) {}; + + protected: + + void Established () override; + + private: + + bool m_IsQuiet; // don't send destination + }; + class BOBI2PTunnel: public I2PService { public: diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 8c8c2bfa..d26e33ab 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -265,11 +265,15 @@ namespace client } } - bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { - static const std::string transient("transient"); +#if __cplusplus >= 202002L // C++20 + if (filename.starts_with ("transient")) +#else + std::string_view transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient +#endif { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); @@ -297,7 +301,7 @@ namespace client } else { - LogPrint (eLogCritical, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); + LogPrint (eLogInfo, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); @@ -416,15 +420,10 @@ namespace client void ClientContext::CreateNewSharedLocalDestination () { - std::map params - { - { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, - { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, - { I2CP_PARAM_LEASESET_TYPE, "3" }, - { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" }, - { I2CP_PARAM_OUTBOUND_NICKNAME, "SharedDest" }, - { I2CP_PARAM_STREAMING_PROFILE, "2" } - }; + std::map params; + ReadI2CPOptionsFromConfig ("shareddest.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SharedDest"; + m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA m_SharedLocalDestination->Acquire (); @@ -548,7 +547,11 @@ namespace client { for (auto& it: files) { +#if __cplusplus >= 202002L // C++20 + if (!it.ends_with (".conf")) continue; +#else if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" +#endif LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } @@ -604,7 +607,9 @@ namespace client options[I2CP_PARAM_OUTBOUND_NICKNAME] = name; std::shared_ptr localDestination = nullptr; - if (keys.length () > 0) + if (keys == "shareddest") + localDestination = m_SharedLocalDestination; + else if (keys.length () > 0) { auto it = destinations.find (keys); if (it != destinations.end ()) @@ -763,26 +768,31 @@ namespace client options[I2CP_PARAM_INBOUND_NICKNAME] = name; std::shared_ptr localDestination = nullptr; - auto it = destinations.find (keys); - if (it != destinations.end ()) - { - localDestination = it->second; - localDestination->SetPublic (true); - } + if (keys == "shareddest") + localDestination = m_SharedLocalDestination; else - { - i2p::data::PrivateKeys k; - if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) - continue; - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) + { + auto it = destinations.find (keys); + if (it != destinations.end ()) { - localDestination = CreateNewLocalDestination (k, true, &options); - destinations[keys] = localDestination; + localDestination = it->second; + localDestination->SetPublic (true); } else - localDestination->SetPublic (true); - } + { + i2p::data::PrivateKeys k; + if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) + continue; + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + { + localDestination = CreateNewLocalDestination (k, true, &options); + destinations[keys] = localDestination; + } + else + localDestination->SetPublic (true); + } + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel @@ -876,7 +886,7 @@ namespace client } else - LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); + LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { @@ -902,7 +912,12 @@ namespace client i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); - if (httpProxyKeys.length () > 0) + if (httpProxyKeys == "shareddest") + { + localDestination = m_SharedLocalDestination; + localDestination->Acquire (); + } + else if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) @@ -946,7 +961,12 @@ namespace client uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - if (httpProxyKeys == socksProxyKeys && m_HttpProxy) + if (socksProxyKeys == "shareddest") + { + localDestination = m_SharedLocalDestination; + localDestination->Acquire (); + } + else if (httpProxyKeys == socksProxyKeys && m_HttpProxy) { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index febd5f16..3f7eaf9a 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -92,7 +92,7 @@ namespace client const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; - bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 13e0f571..4c2771b5 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -60,7 +60,7 @@ namespace proxy { "\r\n" ; - static bool str_rmatch(std::string & str, const char *suffix) + static bool str_rmatch(std::string_view str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) @@ -84,16 +84,16 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ - void GenericProxyError(const std::string& title, const std::string& description); - void GenericProxyInfo(const std::string& title, const std::string& description); - void HostNotFound(const std::string& host); - void SendProxyError(const std::string& content); + void GenericProxyError(std::string_view title, std::string_view description); + void GenericProxyInfo(std::string_view title, std::string_view description); + void HostNotFound(std::string_view host); + void SendProxyError(std::string_view content); void SendRedirect(const std::string& address); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); - void HTTPConnect(const std::string & host, uint16_t port); + void HTTPConnect(std::string_view host, uint16_t port); void HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); typedef std::function ProxyResolvedHandler; @@ -162,23 +162,23 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { + void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) + { std::stringstream ss; ss << "

" << tr("Proxy error") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; - std::string content = ss.str(); - SendProxyError(content); + SendProxyError(ss.str ()); } - void HTTPReqHandler::GenericProxyInfo(const std::string& title, const std::string& description) { + void HTTPReqHandler::GenericProxyInfo(std::string_view title, std::string_view description) + { std::stringstream ss; ss << "

" << tr("Proxy info") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; - std::string content = ss.str(); - SendProxyError(content); + SendProxyError(ss.str ()); } - void HTTPReqHandler::HostNotFound(const std::string& host) + void HTTPReqHandler::HostNotFound(std::string_view host) { std::stringstream ss; ss << "

" << tr("Proxy error: Host not found") << "

\r\n" @@ -192,11 +192,10 @@ namespace proxy { ss << "
  • second << host << "\">" << js->first << "
  • \r\n"; } ss << "\r\n"; - std::string content = ss.str(); - SendProxyError(content); + SendProxyError(ss.str ()); } - void HTTPReqHandler::SendProxyError(const std::string& content) + void HTTPReqHandler::SendProxyError(std::string_view content) { i2p::http::HTTPRes res; res.code = 500; @@ -473,7 +472,7 @@ namespace proxy { if (dest_host != "") { /* absolute url, replace 'Host' header */ - std::string h = dest_host; + std::string h (dest_host); if (dest_port != 0 && dest_port != 80) h += ":" + std::to_string(dest_port); m_ClientRequest.UpdateHeader("Host", h); @@ -513,7 +512,7 @@ namespace proxy { GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); - std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str()); + std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ()); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; @@ -653,11 +652,10 @@ namespace proxy { Terminate(); } - void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port) + void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); - std::string hostname(host); - if(str_rmatch(hostname, ".i2p")) + if(str_rmatch(host, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 1949746a..df93082b 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,13 +29,15 @@ namespace client const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), - m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), m_LeaseSetCreationTimer (service) + m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), + m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service) { } void I2CPDestination::Stop () { m_LeaseSetCreationTimer.cancel (); + m_ReadinessCheckTimer.cancel (); LeaseSetDestination::Stop (); m_Owner = nullptr; } @@ -88,7 +90,7 @@ namespace client void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { - boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); + boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels)); } void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) @@ -98,6 +100,20 @@ namespace client LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); return; } + m_ReadinessCheckTimer.cancel (); + auto pool = GetTunnelPool (); + if (!pool || pool->GetOutboundTunnels ().empty ()) + { + // try again later + m_ReadinessCheckTimer.expires_from_now (boost::posix_time::seconds(I2CP_DESTINATION_READINESS_CHECK_INTERVAL)); + m_ReadinessCheckTimer.async_wait( + [s=GetSharedFromThis (), tunnels=std::move(tunnels)](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + s->PostCreateNewLeaseSet (tunnels); + }); + return; + } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); @@ -555,30 +571,30 @@ namespace client m_IsSending = false; } - std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) + std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len) const { uint8_t l = buf[0]; if (l > len) l = len; - return std::string ((const char *)(buf + 1), l); + return { (const char *)(buf + 1), l }; } - size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) + size_t I2CPSession::PutString (uint8_t * buf, size_t len, std::string_view str) { auto l = str.length (); if (l + 1 >= len) l = len - 1; if (l > 255) l = 255; // 1 byte max buf[0] = l; - memcpy (buf + 1, str.c_str (), l); + memcpy (buf + 1, str.data (), l); return l + 1; } - void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) + void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const // TODO: move to Base.cpp { size_t offset = 0; while (offset < len) { - std::string param = ExtractString (buf + offset, len - offset); + auto param = ExtractString (buf + offset, len - offset); offset += param.length () + 1; if (buf[offset] != '=') { @@ -587,7 +603,7 @@ namespace client } offset++; - std::string value = ExtractString (buf + offset, len - offset); + auto value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { @@ -595,7 +611,7 @@ namespace client break; } offset++; - mapping.insert (std::make_pair (param, value)); + mapping.emplace (param, value); } } @@ -764,6 +780,7 @@ namespace client void I2CPSession::AddRoutingSession (const i2p::data::IdentHash& signingKey, std::shared_ptr remoteSession) { if (!remoteSession) return; + remoteSession->SetAckRequestInterval (I2CP_SESSION_ACK_REQUEST_INTERVAL); std::lock_guard l(m_RoutingSessionsMutex); m_RoutingSessions[signingKey] = remoteSession; } @@ -879,7 +896,7 @@ namespace client if (!remoteSession || !m_Destination->SendMsg (buf + offset, payloadLen, remoteSession, nonce)) { i2p::data::IdentHash identHash; - SHA256(ident, identSize, identHash); // caclulate ident hash, because we don't need full identity + SHA256(ident, identSize, identHash); // calculate ident hash, because we don't need full identity m_Destination->SendMsgTo (buf + offset, payloadLen, identHash, nonce); } } @@ -1110,12 +1127,12 @@ namespace client void I2CPServer::Stop () { m_Acceptor.cancel (); - { - auto sessions = m_Sessions; - for (auto& it: sessions) - it.second->Stop (); - } - m_Sessions.clear (); + + decltype(m_Sessions) sessions; + m_Sessions.swap (sessions); + for (auto& it: sessions) + it.second->Stop (); + StopIOService (); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index e929c1e1..d3949eaa 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,8 @@ namespace client const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds + const int I2CP_DESTINATION_READINESS_CHECK_INTERVAL = 5; // in seconds + const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -129,7 +132,7 @@ namespace client uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; bool m_IsCreatingLeaseSet, m_IsSameThread; - boost::asio::deadline_timer m_LeaseSetCreationTimer; + boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer; i2p::util::MemoryPoolMt > m_I2NPMsgsPool; }; @@ -191,9 +194,9 @@ namespace client void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - std::string ExtractString (const uint8_t * buf, size_t len); - size_t PutString (uint8_t * buf, size_t len, const std::string& str); - void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); + std::string_view ExtractString (const uint8_t * buf, size_t len) const; + size_t PutString (uint8_t * buf, size_t len, std::string_view str); + void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const; void SendSessionStatusMessage (I2CPSessionStatus status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index e9513e48..4ec2648a 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -107,7 +107,7 @@ namespace client m_ReadyTimerTriggered = false; } - void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port) { + void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port) { assert(streamRequestComplete); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); if (address) diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h index d3ed36fa..d19c28e2 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -59,7 +59,7 @@ namespace client if (dest) dest->Acquire (); m_LocalDestination = dest; } - void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port = 0); void CreateStream(StreamRequestComplete complete, std::shared_ptr address, uint16_t port); auto& GetService () { return m_LocalDestination->GetService (); } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 3a3c491e..d6436c78 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -32,8 +32,7 @@ namespace client I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, uint16_t port): - I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), - m_IsQuiet (true) + I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -41,14 +40,13 @@ namespace client I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), - m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) + m_RemoteEndpoint (socket->remote_endpoint ()) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, bool quiet, - std::shared_ptr sslCtx): - I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) + const boost::asio::ip::tcp::endpoint& target,std::shared_ptr sslCtx): + I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target) { m_Socket = std::make_shared (owner->GetService ()); if (sslCtx) @@ -292,18 +290,7 @@ namespace client void I2PTunnelConnection::Established () { - if (m_IsQuiet) - StreamReceive (); - else - { - // send destination first like received from I2P - std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); - dest += "\n"; - if(sizeof(m_StreamBuffer) >= dest.size()) { - memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); - } - HandleStreamReceive (boost::system::error_code (), dest.size ()); - } + StreamReceive (); Receive (); } @@ -377,7 +364,7 @@ namespace client I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, std::shared_ptr sslCtx): - I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host), m_XI2P (XI2P), + I2PTunnelConnection (owner, stream, target, sslCtx), m_Host (host), m_XI2P (XI2P), m_HeaderSent (false), m_ResponseHeaderSent (false) { if (sslCtx) @@ -404,7 +391,7 @@ namespace client else { // strip up some headers - static const std::vector excluded // list of excluded headers + static const std::array excluded // list of excluded headers { "Keep-Alive:", "X-I2P" }; @@ -487,7 +474,7 @@ namespace client if (line == "\r") endOfHeader = true; else { - static const std::vector excluded // list of excluded headers + static const std::array excluded // list of excluded headers { "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" }; @@ -528,7 +515,7 @@ namespace client I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass, std::shared_ptr sslCtx): - I2PTunnelConnection (owner, stream, target, true, sslCtx), m_From (stream->GetRemoteIdentity ()), + I2PTunnelConnection (owner, stream, target, sslCtx), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } @@ -857,7 +844,7 @@ namespace client std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, GetEndpoint (), true, m_SSLCtx); + return std::make_shared (this, stream, GetEndpoint (), m_SSLCtx); } diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index aa53f0b6..7d4c3400 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,7 +27,7 @@ namespace i2p { namespace client { - const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; + const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 16384; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels @@ -45,7 +45,7 @@ namespace client I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, bool quiet = true, + const boost::asio::ip::tcp::endpoint& target, std::shared_ptr sslCtx = nullptr); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); @@ -54,25 +54,27 @@ namespace client protected: + virtual void Established (); void Terminate (); void Receive (); void StreamReceive (); + void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded std::shared_ptr GetSocket () const { return m_Socket; }; + std::shared_ptr GetStream () const { return m_Stream; }; std::shared_ptr > GetSSL () const { return m_SSL; }; - + uint8_t * GetStreamBuffer () { return m_StreamBuffer; }; + private: void HandleConnect (const boost::system::error_code& ecode); void HandleHandshake (const boost::system::error_code& ecode); - void Established (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleWrite (const boost::system::error_code& ecode); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - + private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; @@ -80,7 +82,6 @@ namespace client std::shared_ptr > m_SSL; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; - bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection @@ -94,7 +95,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: @@ -112,8 +113,8 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); - void WriteToStream (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; + void WriteToStream (const uint8_t * buf, size_t len) override; private: @@ -132,7 +133,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index c124be9b..6c5dd15d 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -415,7 +415,7 @@ namespace client { session->UDPEndpoint = forward; auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); - auto port = std::stoi(params[SAM_PARAM_PORT]); + auto port = forward ? std::stoi(params[SAM_PARAM_PORT]) : 0; 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), @@ -470,15 +470,11 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - uint8_t buf[1024]; - char priv[1024]; - size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024); - size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024); - priv[l1] = 0; + std::string priv = session->GetLocalDestination ()->GetPrivateKeys ().ToBase64 (); #ifdef _MSC_VER - size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); + size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); #else - size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); + size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); #endif SendMessageReply (m_Buffer, l2, false); } @@ -1350,12 +1346,14 @@ namespace client LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); } + decltype(m_Sessions) sessions; { std::unique_lock l(m_SessionsMutex); - for (auto& it: m_Sessions) - it.second->Close (); - m_Sessions.clear (); - } + m_Sessions.swap (sessions); + } + for (auto& it: sessions) + it.second->Close (); + StopIOService (); } diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp index 9f7fe864..b173fc0f 100644 --- a/libi2pd_client/UDPTunnel.cpp +++ b/libi2pd_client/UDPTunnel.cpp @@ -86,7 +86,7 @@ namespace client } else { - LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from differend address. Removed"); + LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from different address. Removed"); m_Sessions.erase (it); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6353a683..fb03d434 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,6 +65,10 @@ set(test-eddsa_SRCS test-eddsa.cpp ) +set(test-aes_SRCS + test-aes.cpp +) + add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS}) add_executable(test-http-req ${test-http-req_SRCS}) add_executable(test-http-res ${test-http-res_SRCS}) @@ -77,6 +81,7 @@ add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) add_executable(test-blinding ${test-blinding_SRCS}) add_executable(test-elligator ${test-elligator_SRCS}) add_executable(test-eddsa ${test-eddsa_SRCS}) +add_executable(test-aes ${test-aes_SRCS}) set(LIBS libi2pd @@ -101,6 +106,7 @@ target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) target_link_libraries(test-blinding ${LIBS}) target_link_libraries(test-elligator ${LIBS}) target_link_libraries(test-eddsa ${LIBS}) +target_link_libraries(test-aes ${LIBS}) add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked) add_test(test-http-req ${TEST_PATH}/test-http-req) @@ -114,3 +120,4 @@ add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) add_test(test-blinding ${TEST_PATH}/test-blinding) add_test(test-elligator ${TEST_PATH}/test-elligator) add_test(test-eddsa ${TEST_PATH}/test-eddsa) +add_test(test-aes ${TEST_PATH}/test-aes) diff --git a/tests/Makefile b/tests/Makefile index 51a11dfe..b020427d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -8,7 +8,7 @@ LIBI2PD = ../libi2pd.a TESTS = \ test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \ test-gost test-gost-sig test-base-64 test-aeadchacha20poly1305 test-blinding \ - test-elligator test-eddsa + test-elligator test-eddsa test-aes ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) CXXFLAGS += -DWIN32_LEAN_AND_MEAN @@ -56,6 +56,9 @@ test-elligator: test-elligator.cpp $(LIBI2PD) test-eddsa: test-eddsa.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-aes: test-aes.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + run: $(TESTS) @for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp index 64a0f358..2ba6a253 100644 --- a/tests/test-aeadchacha20poly1305.cpp +++ b/tests/test-aeadchacha20poly1305.cpp @@ -43,18 +43,20 @@ uint8_t encrypted[114] = int main () { uint8_t buf[114+16]; + i2p::crypto::AEADChaCha20Poly1305Encryptor encryptor; // test encryption - i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true); + encryptor.Encrypt ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16); assert (memcmp (buf, encrypted, 114) == 0); assert (memcmp (buf + 114, tag, 16) == 0); // test decryption uint8_t buf1[114]; - assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false)); + i2p::crypto::AEADChaCha20Poly1305Decryptor decryptor; + assert (decryptor.Decrypt (buf, 114, ad, 12, key, nonce, buf1, 114)); assert (memcmp (buf1, text, 114) == 0); // test encryption of multiple buffers memcpy (buf, text, 114); std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; - i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); - i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); + encryptor.Encrypt (bufs, key, nonce, buf + 114); + decryptor.Decrypt (buf, 114, nullptr, 0, key, nonce, buf1, 114); assert (memcmp (buf1, text, 114) == 0); } diff --git a/tests/test-aes.cpp b/tests/test-aes.cpp new file mode 100644 index 00000000..15f4de1e --- /dev/null +++ b/tests/test-aes.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "Crypto.h" + +uint8_t ecb_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t ecb_plain1[16] = +{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a +}; + +uint8_t ecb_cipher1[16] = +{ + 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8 +}; + +uint8_t cbc_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t cbc_iv1[16] = +{ + 0xF5, 0x8C, 0x4C, 0x04, 0xD6, 0xE5, 0xF1, 0xBA, 0x77, 0x9E, 0xAB, 0xFB, 0x5F, 0x7B, 0xFB, 0xD6 +}; + +uint8_t cbc_plain1[16] = +{ + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51 +}; + +uint8_t cbc_cipher1[16] = +{ + 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d +}; + +int main () +{ + // ECB encrypt test1 + i2p::crypto::ECBEncryption ecbencryption; + ecbencryption.SetKey (ecb_key1); + uint8_t out[16]; + ecbencryption.Encrypt (ecb_plain1, out); + assert (memcmp (ecb_cipher1, out, 16) == 0); + + // ECB decrypt test1 + i2p::crypto::ECBDecryption ecbdecryption; + ecbdecryption.SetKey (ecb_key1); + ecbdecryption.Decrypt (ecb_cipher1, out); + assert (memcmp (ecb_plain1, out, 16) == 0); + // CBC encrypt test + i2p::crypto::CBCEncryption cbcencryption; + cbcencryption.SetKey (cbc_key1); + cbcencryption.Encrypt (cbc_plain1, 16, cbc_iv1, out); + assert (memcmp (cbc_cipher1, out, 16) == 0); + // CBC decrypt test + i2p::crypto::CBCDecryption cbcdecryption; + cbcdecryption.SetKey (cbc_key1); + cbcdecryption.Decrypt (cbc_cipher1, 16, cbc_iv1, out); + assert (memcmp (cbc_plain1, out, 16) == 0); +} +