diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 06bc898c..d587ba05 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -128,8 +128,11 @@ jobs: cache: true update: true - - name: Clone MinGW packages repository - run: git clone https://github.com/msys2/MINGW-packages + - name: Clone MinGW packages repository and revert boost to 1.85.0 + run: | + git clone https://github.com/msys2/MINGW-packages + cd MINGW-packages + git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost # headers - name: Get headers package version @@ -205,21 +208,23 @@ jobs: run: | cd MINGW-packages/mingw-w64-openssl gpg --recv-keys D894E2CE8B3D79F5 + gpg --recv-keys 216094DFD0CB81EF MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - name: Install openssl package run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst # Boost - - name: Get boost package version - id: version-boost - run: | - echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + #- name: Get boost package version + # id: version-boost + # run: | + # echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - name: Cache boost package uses: actions/cache@v4 id: cache-boost with: path: MINGW-packages/mingw-w64-boost/*.zst - key: winxp-winpthreads-${{ steps.version-boost.outputs.version }} + key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }} + # Rebuild package if packages above has changed - name: Build WinXP-capable boost package if: steps.cache-boost.outputs.cache-hit != 'true' run: | diff --git a/ChangeLog b/ChangeLog index 13b8bf50..b3559fb7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,44 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.54.0] - 2024-10-06 +### Added +- Maintain recently connected routers list to avoid false-positive peer test +- Limited connectivity mode(through proxy) +- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers +- Limit stream's inbound speed +- Periodic ack requests in ratchets session +- Set congestion cap G immediately if through proxy +- Show tunnel's routers bandwidth caps in web console +- Handle immediate ack requested flag in SSU2 data packets +- Resend and ack peer test and relay messages +- "senduseragent" HTTP proxy's param to pass through user's User-Agent +### Changed +- Exclude 'N' routers from high-bandwidth routers for client tunnels +- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers +- Removed dependency from boost::date_time and boost::filesystem +- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels +- 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 +- 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 +### Fixed +- Jump link crash if address book is disabled +- Race condition if connect through an introducer +- "Date" header in I2PControl response +- Incomplete response from web console +- AEAD verification with LibreSSL +- Number of generated tags and new keys for follow-on tagsets +- Expired leases in LeaseSet +- Attempts to send HolePunch to 0.0.0.0 +- Incorrect options size in quick ack streaming packet +- Low bandwidth router appeared as first peer in high-bandwidth client tunnel + ## [2.53.1] - 2024-07-29 ### Changed - I2CP performance improvement diff --git a/Makefile.bsd b/Makefile.bsd index f9a47375..4cf8f80a 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -1,18 +1,22 @@ CXX = clang++ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation +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 + ## 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. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -CXXVER := $(shell $(CXX) -dumpversion) -ifeq (${CXXVER}, "4.2.1") # older clang always returned 4.2.1 - NEEDED_CXXFLAGS = -std=c++11 -else # newer versions support C++17 +CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2) +ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1 + $(error Compiler too old) +else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19 + NEEDED_CXXFLAGS = -std=c++20 +else NEEDED_CXXFLAGS = -std=c++17 endif -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 -lboost_system -lboost_filesystem -lboost_program_options -lpthread + diff --git a/Makefile.haiku b/Makefile.haiku index b5ecb1e8..d0824d73 100644 --- a/Makefile.haiku +++ b/Makefile.haiku @@ -1,8 +1,8 @@ CXX = g++ -CXXFLAGS := -Wall -std=c++11 +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_filesystem -lboost_program_options -lpthread +LDLIBS = -lbe -lbsd -lnetwork -lz -lcrypto -lssl -lboost_system -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP diff --git a/Makefile.homebrew b/Makefile.homebrew index f19d64bb..e14ea955 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -5,7 +5,7 @@ SSLROOT = ${BREWROOT}/opt/openssl@1.1 UPNPROOT = ${BREWROOT}/opt/miniupnpc CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual -NEEDED_CXXFLAGS ?= -std=c++11 +NEEDED_CXXFLAGS ?= -std=c++17 INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include LDFLAGS ?= ${LD_DEBUG} DEFINES += -DMAC_OSX diff --git a/Makefile.linux b/Makefile.linux index 86527b9a..aa67626a 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -9,24 +9,20 @@ LDFLAGS ?= ${LD_DEBUG} ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FDLAGS to work at build-time. -# detect proper flag for c++11 support by compilers +# detect proper flag for c++17 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match $(CXX) 'clang'),5) - NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 - NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 - NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 - NEEDED_CXXFLAGS += -std=c++11 - LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9 NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+ -# NEEDED_CXXFLAGS += -std=c++20 +else ifeq ($(shell expr match ${CXXVER} "7"),1) # gcc 7 NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -latomic + LDLIBS = -lboost_filesystem +else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9 + NEEDED_CXXFLAGS += -std=c++17 + LDLIBS = -lstdc++fs +else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12 + NEEDED_CXXFLAGS += -std=c++17 +else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+ + NEEDED_CXXFLAGS += -std=c++20 else # not supported $(error Compiler too old) endif @@ -39,7 +35,6 @@ ifeq ($(USE_STATIC),yes) # the shared libraries from the glibc version used for linking LIBDIR := /usr/lib/$(SYS) LDLIBS += $(LIBDIR)/libboost_system.a - LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_program_options.a LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a @@ -49,7 +44,7 @@ ifeq ($(USE_UPNP),yes) endif LDLIBS += -lpthread -ldl else - LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_filesystem -lboost_program_options -lpthread + LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_program_options -lpthread -latomic ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif diff --git a/Makefile.mingw b/Makefile.mingw index 4a9d033b..fc92e9b0 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -7,7 +7,7 @@ CXXFLAGS := $(CXX_DEBUG) -fPIC -msse INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32 LDFLAGS := ${LD_DEBUG} -static -fPIC -msse -NEEDED_CXXFLAGS += -std=c++17 +NEEDED_CXXFLAGS += -std=c++20 DEFINES += -DWIN32_LEAN_AND_MEAN # UPNP Support @@ -16,8 +16,11 @@ ifeq ($(USE_UPNP),yes) LDLIBS = -lminiupnpc endif +ifeq ($(USE_WINXP_FLAGS), yes) + DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 +endif + LDLIBS += \ - $(MINGW_PREFIX)/lib/libboost_system-mt.a \ $(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \ $(MINGW_PREFIX)/lib/libboost_program_options-mt.a \ $(MINGW_PREFIX)/lib/libssl.a \ @@ -39,10 +42,6 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_WINXP_FLAGS), yes) - DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -endif - ifeq ($(USE_AESNI),yes) NEEDED_CXXFLAGS += -maes LDFLAGS += -maes diff --git a/Makefile.osx b/Makefile.osx index 963d4898..48eb1a51 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,5 +1,5 @@ CXX = clang++ -CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 +CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17 INCFLAGS = -I/usr/local/include DEFINES := -DMAC_OSX LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 742ad30d..0e29c517 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -313,7 +313,7 @@ namespace win32 } case ID_DATADIR: { - std::string datadir(i2p::fs::GetUTF8DataDir()); + std::string datadir(i2p::fs::GetDataDir()); ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); return 0; } @@ -355,9 +355,7 @@ namespace win32 } } } -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif } case WM_TRAYICON: { diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp index 095afe45..4ef768c8 100644 --- a/Win32/Win32NetState.cpp +++ b/Win32/Win32NetState.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h index 5006daad..c1f47a24 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index be75ca3c..954f5de9 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -156,20 +156,6 @@ else() endif() set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections") set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above - - # check for c++17 & c++11 support - include(CheckCXXCompilerFlag) - - CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) - CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) - - if(CXX17_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") - elseif(CXX11_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - else() - message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?") - endif() endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -223,6 +209,10 @@ if(WITH_THREADSANITIZER) endif() endif() +if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 + list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") +endif() + # Use std::atomic instead of GCC builtins on macOS PowerPC: # For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 # This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. @@ -284,7 +274,7 @@ else() endif() endif() -find_package(Boost REQUIRED COMPONENTS system filesystem program_options OPTIONAL_COMPONENTS atomic) +find_package(Boost REQUIRED COMPONENTS system filesystem program_options) if(NOT DEFINED Boost_FOUND) message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!") endif() @@ -312,6 +302,26 @@ if(ZLIB_FOUND) link_directories(${ZLIB_ROOT}/lib) endif() +# C++ standard to use, based on compiler and version of boost +if(NOT MSVC) +# check for c++20 & c++17 support + include(CheckCXXCompilerFlag) + + if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20 + CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED) + endif() + CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) + + + if(CXX20_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") + elseif(CXX17_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + else() + message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?") + endif() +endif() + # load includes include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) @@ -322,6 +332,7 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") 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}") diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake index d5ec6a0a..4954e3e5 100644 --- a/build/cmake_modules/CheckAtomic.cmake +++ b/build/cmake_modules/CheckAtomic.cmake @@ -8,7 +8,7 @@ INCLUDE(CheckLibraryExists) function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; @@ -25,7 +25,7 @@ endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 862c618d..05158bc9 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.53.1 +Version: 2.54.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -24,7 +24,7 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units -%if 0%{?fedora} > 40 || 0%{?eln} +%if 0%{?fedora} == 41 BuildRequires: openssl-devel-engine %endif @@ -148,6 +148,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Sun Oct 6 2024 orignal - 2.54.0 +- update to 2.54.0 + * Tue Jul 30 2024 orignal - 2.53.1 - update to 2.53.1 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 6b999798..4b1e573b 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.53.1 +Version: 2.54.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -22,7 +22,7 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units -%if 0%{?fedora} > 40 || 0%{?eln} +%if 0%{?fedora} == 41 BuildRequires: openssl-devel-engine %endif @@ -146,6 +146,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Sun Oct 6 2024 orignal - 2.54.0 +- update to 2.54.0 + * Tue Jul 30 2024 orignal - 2.53.1 - update to 2.53.1 diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 7a6656dc..21c7b6c6 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1480,7 +1480,7 @@ namespace http { reply.body = content; m_SendBuffer = reply.to_string(); - boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), + boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } diff --git a/debian/changelog b/debian/changelog index c5da4628..0fe779da 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.54.0-1) unstable; urgency=medium + + * updated to version 2.54.0/0.9.64 + +-- orignal Sun, 6 Oct 2024 16:00:00 +0000 + i2pd (2.53.1-1) unstable; urgency=medium * updated to version 2.53.1 diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index a3fe63c7..ab237613 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -117,9 +117,14 @@ 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.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") + ("httpproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec") + ("httpproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec") + ("httpproxy.i2p.streaming.profile", value()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") + ; options_description socksproxy("SOCKS Proxy options"); @@ -144,6 +149,9 @@ namespace config { ("socksproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("socksproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") + ("socksproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec") + ("socksproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec") + ("socksproxy.i2p.streaming.profile", value()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") ; options_description sam("SAM bridge options"); diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 5db072a4..1e0c06cc 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -422,7 +422,7 @@ namespace datagram { auto pool = m_LocalDestination->GetTunnelPool (); if (pool) - idx = m_LocalDestination->GetTunnelPool ()->GetRng ()() % sz; + idx = pool->GetRng ()() % sz; } if (idx < 0) idx = rand () % sz; path->remoteLease = ls[idx]; @@ -455,7 +455,7 @@ namespace datagram { auto pool = m_LocalDestination->GetTunnelPool (); if (pool) - idx = m_LocalDestination->GetTunnelPool ()->GetRng ()() % sz; + idx = pool->GetRng ()() % sz; } if (idx < 0) idx = rand () % sz; path->remoteLease = ls[idx]; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 9285b05d..28b23950 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -37,6 +37,7 @@ namespace client int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE; int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE; int numTags = DEFAULT_TAGS_TO_SEND; + bool isHighBandwidth = true; std::shared_ptr > explicitPeers; try { @@ -92,7 +93,7 @@ namespace client it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); if (it != params->end ()) { - // oveeride isPublic + // override isPublic m_IsPublic = (it->second != "true"); } it = params->find (I2CP_PARAM_LEASESET_TYPE); @@ -121,6 +122,9 @@ namespace client m_LeaseSetPrivKey.reset (nullptr); } } + it = params->find (I2CP_PARAM_STREAMING_PROFILE); + if (it != params->end ()) + isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE; } } catch (std::exception & ex) @@ -128,7 +132,7 @@ namespace client LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } SetNumTags (numTags); - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -1013,18 +1017,15 @@ namespace client } } // if no param or valid crypto type use from identity - bool isSingleKey = false; if (encryptionKeyTypes.empty ()) - { - isSingleKey = true; - encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ()); - } + encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (), + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4 for (auto& it: encryptionKeyTypes) { auto encryptionKey = new EncryptionKey (it); if (IsPublic ()) - PersistTemporaryKeys (encryptionKey, isSingleKey); + PersistTemporaryKeys (encryptionKey); else encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); @@ -1383,12 +1384,11 @@ namespace client return ret; } - void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey) + void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys) { if (!keys) return; std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", - isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat")); + std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat"); std::ifstream f(path, std::ifstream::binary); if (f) { diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 9dcc64c6..4a51a257 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -90,6 +90,10 @@ namespace client const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; const int DEFAULT_ANSWER_PINGS = true; + const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile"; + const int STREAMING_PROFILE_BULK = 1; // high bandwidth + const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth + const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK; typedef std::function stream)> StreamRequestComplete; @@ -289,7 +293,7 @@ namespace client std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } - void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); + void PersistTemporaryKeys (EncryptionKey * keys); void ReadAuthKey (const std::string& group, const std::map * params); template diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 9f23482b..138d21e9 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -801,9 +801,7 @@ namespace garlic m_State = eSessionStateEstablished; m_NSRSendTagset = nullptr; m_EphemeralKeys = nullptr; -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif case eSessionStateEstablished: if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ()) m_SendReverseKey = false; // tag received on new tagset diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 10645251..bcc07b30 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -35,7 +35,7 @@ namespace garlic 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; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 800; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 47a3e52c..a623a4eb 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -7,10 +7,11 @@ */ #include -#include #if defined(MAC_OSX) +#if !STD_FILESYSTEM #include +#endif #include #endif @@ -25,6 +26,14 @@ #include "Log.h" #include "Garlic.h" +#if STD_FILESYSTEM +#include +namespace fs_lib = std::filesystem; +#else +#include +namespace fs_lib = boost::filesystem; +#endif + namespace i2p { namespace fs { std::string appName = "i2pd"; @@ -54,15 +63,17 @@ namespace fs { const std::string GetUTF8DataDir () { #ifdef _WIN32 -#if (BOOST_VERSION >= 108500) - boost::filesystem::path path (dataDir); -#else - boost::filesystem::wpath path (dataDir); -#endif - auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16() ) ); // convert path to UTF-8 - auto dataDirUTF8 = path.string(); - boost::filesystem::path::imbue(loc); // Return locale settings back - return dataDirUTF8; + int size = MultiByteToWideChar(CP_ACP, 0, + dataDir.c_str(), dataDir.size(), nullptr, 0); + std::wstring utf16Str(size, L'\0'); + MultiByteToWideChar(CP_ACP, 0, + dataDir.c_str(), dataDir.size(), &utf16Str[0], size); + int utf8Size = WideCharToMultiByte(CP_UTF8, 0, + utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr); + std::string utf8Str(utf8Size, '\0'); + WideCharToMultiByte(CP_UTF8, 0, + utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr); + return utf8Str; #else return dataDir; // linux, osx, android uses UTF-8 by default #endif @@ -91,10 +102,10 @@ namespace fs { } else { -#if (BOOST_VERSION >= 108500) - dataDir = boost::filesystem::path(commonAppData).string() + "\\" + appName; +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + dataDir = fs_lib::path(commonAppData).string() + "\\" + appName; #else - dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName; + dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName; #endif } #else @@ -120,14 +131,14 @@ namespace fs { } else { -#if (BOOST_VERSION >= 108500) - auto execPath = boost::filesystem::path(localAppData).parent_path(); +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + auto execPath = fs_lib::path(localAppData).parent_path(); #else - auto execPath = boost::filesystem::wpath(localAppData).parent_path(); + auto execPath = fs_lib::wpath(localAppData).parent_path(); #endif // if config file exists in .exe's folder use it - if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string { dataDir = execPath.string (); } else // otherwise %appdata% @@ -143,10 +154,10 @@ namespace fs { } else { -#if (BOOST_VERSION >= 108500) - dataDir = boost::filesystem::path(localAppData).string() + "\\" + appName; +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + dataDir = fs_lib::path(localAppData).string() + "\\" + appName; #else - dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; + dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName; #endif } } @@ -169,7 +180,7 @@ namespace fs { #if defined(ANDROID) const char * ext = getenv("EXTERNAL_STORAGE"); if (!ext) ext = "/sdcard"; - if (boost::filesystem::exists(ext)) + if (fs_lib::exists(ext)) { dataDir = std::string (ext) + "/" + appName; return; @@ -202,16 +213,16 @@ namespace fs { } bool Init() { - if (!boost::filesystem::exists(dataDir)) - boost::filesystem::create_directory(dataDir); + if (!fs_lib::exists(dataDir)) + fs_lib::create_directory(dataDir); std::string destinations = DataDirPath("destinations"); - if (!boost::filesystem::exists(destinations)) - boost::filesystem::create_directory(destinations); + if (!fs_lib::exists(destinations)) + fs_lib::create_directory(destinations); std::string tags = DataDirPath("tags"); - if (!boost::filesystem::exists(tags)) - boost::filesystem::create_directory(tags); + if (!fs_lib::exists(tags)) + fs_lib::create_directory(tags); else i2p::garlic::CleanUpTagsFiles (); @@ -219,13 +230,13 @@ namespace fs { } bool ReadDir(const std::string & path, std::vector & files) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return false; - boost::filesystem::directory_iterator it(path); - boost::filesystem::directory_iterator end; + fs_lib::directory_iterator it(path); + fs_lib::directory_iterator end; for ( ; it != end; it++) { - if (!boost::filesystem::is_regular_file(it->status())) + if (!fs_lib::is_regular_file(it->status())) continue; files.push_back(it->path().string()); } @@ -234,29 +245,42 @@ namespace fs { } bool Exists(const std::string & path) { - return boost::filesystem::exists(path); + return fs_lib::exists(path); } uint32_t GetLastUpdateTime (const std::string & path) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return 0; +#if STD_FILESYSTEM + std::error_code ec; + auto t = std::filesystem::last_write_time (path, ec); + if (ec) return 0; +/*#if __cplusplus >= 202002L // C++ 20 or higher + const auto sctp = std::chrono::clock_cast(t); +#else */ // TODO: wait until implemented + const auto sctp = std::chrono::time_point_cast( + t - decltype(t)::clock::now() + std::chrono::system_clock::now()); +/*#endif */ + return std::chrono::system_clock::to_time_t(sctp); +#else boost::system::error_code ec; auto t = boost::filesystem::last_write_time (path, ec); return ec ? 0 : t; +#endif } bool Remove(const std::string & path) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return false; - return boost::filesystem::remove(path); + return fs_lib::remove(path); } bool CreateDirectory (const std::string& path) { - if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path))) + if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path))) return true; - return boost::filesystem::create_directory(path); + return fs_lib::create_directory(path); } void HashedStorage::SetPlace(const std::string &path) { @@ -264,18 +288,18 @@ namespace fs { } bool HashedStorage::Init(const char * chars, size_t count) { - if (!boost::filesystem::exists(root)) { - boost::filesystem::create_directories(root); + if (!fs_lib::exists(root)) { + fs_lib::create_directories(root); } for (size_t i = 0; i < count; i++) { auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; - if (boost::filesystem::exists(p)) + if (fs_lib::exists(p)) continue; #if TARGET_OS_SIMULATOR // ios simulator fs says it is case sensitive, but it is not boost::system::error_code ec; - if (boost::filesystem::create_directory(p, ec)) + if (fs_lib::create_directory(p, ec)) continue; switch (ec.value()) { case boost::system::errc::file_exists: @@ -285,7 +309,7 @@ namespace fs { throw boost::system::system_error( ec, __func__ ); } #else - if (boost::filesystem::create_directory(p)) + if (fs_lib::create_directory(p)) continue; /* ^ throws exception on failure */ #endif return false; @@ -308,9 +332,9 @@ namespace fs { void HashedStorage::Remove(const std::string & ident) { std::string path = Path(ident); - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return; - boost::filesystem::remove(path); + fs_lib::remove(path); } void HashedStorage::Traverse(std::vector & files) { @@ -321,12 +345,12 @@ namespace fs { void HashedStorage::Iterate(FilenameVisitor v) { - boost::filesystem::path p(root); - boost::filesystem::recursive_directory_iterator it(p); - boost::filesystem::recursive_directory_iterator end; + fs_lib::path p(root); + fs_lib::recursive_directory_iterator it(p); + fs_lib::recursive_directory_iterator end; for ( ; it != end; it++) { - if (!boost::filesystem::is_regular_file( it->status() )) + if (!fs_lib::is_regular_file( it->status() )) continue; const std::string & t = it->path().string(); v(t); diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 7911c6a0..7af8f494 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,16 @@ #include #include +#ifndef STD_FILESYSTEM +# if (_WIN32 && __GNUG__) // MinGW GCC somehow incorrectly converts paths +# define STD_FILESYSTEM 0 +# elif (!TARGET_OS_SIMULATOR && __has_include()) // supports std::filesystem +# define STD_FILESYSTEM 1 +# else +# define STD_FILESYSTEM 0 +# endif +#endif + namespace i2p { namespace fs { extern std::string dirSep; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 54fc781e..1705b03a 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -541,34 +541,7 @@ namespace garlic // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming if (!session->HandleNextMessage (buf, length, nullptr, 0)) - { - // try to generate more tags for last tagset - if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) - { - uint64_t missingTag; memcpy (&missingTag, buf, 8); - auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); - LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags"); - for (int i = 0; i < maxTags; i++) - { - auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); - if (!nextTag) - { - LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); - break; - } - if (nextTag == missingTag) - { - LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); - if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) - found = true; - break; - } - } - if (!found) m_LastTagset = nullptr; - } - if (!found) - LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); - } + LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -583,9 +556,7 @@ namespace garlic auto it = m_ECIESx25519Tags.find (tag); if (it != m_ECIESx25519Tags.end ()) { - if (it->second.tagset && it->second.tagset->HandleNextMessage (buf, len, it->second.index)) - m_LastTagset = it->second.tagset; - else + if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index)) LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it); return true; @@ -893,8 +864,6 @@ namespace garlic } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); - if (m_LastTagset && m_LastTagset->IsExpired (ts)) - m_LastTagset = nullptr; } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -1031,9 +1000,7 @@ namespace garlic case eGarlicDeliveryTypeDestination: LogPrint (eLogDebug, "Garlic: Type destination"); buf += 32; // TODO: check destination -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here case eGarlicDeliveryTypeLocal: { diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index d1b97ade..a4475dc7 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -288,7 +288,6 @@ namespace garlic int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session - ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index f4c3dcb9..990781bc 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -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 * @@ -40,12 +40,13 @@ namespace http inline bool is_http_method(const std::string & str) { return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); } - - void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { + + static void strsplit(std::stringstream& ss, std::vector &tokens, char delim, std::size_t limit = 0) + { std::size_t count = 0; - std::stringstream ss(line); std::string token; - while (1) { + while (1) + { count++; if (limit > 0 && count >= limit) delim = '\n'; /* reset delimiter */ @@ -55,21 +56,33 @@ namespace http } } - static std::pair parse_header_line(const std::string& line) + 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); + } + + static std::pair parse_header_line(std::string_view line) { std::size_t pos = 0; std::size_t len = 1; /*: */ std::size_t max = line.length(); if ((pos = line.find(':', pos)) == std::string::npos) - return std::make_pair("", ""); // no ':' found + return std::pair{"", ""}; // no ':' found if (pos + 1 < max) // ':' at the end of header is valid { while ((pos + len) < max && isspace(line.at(pos + len))) len++; if (len == 1) - return std::make_pair("", ""); // no following space, but something else + return std::pair{"", ""}; // no following space, but something else } - return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))}; } void gen_rfc7231_date(std::string & out) { @@ -83,15 +96,17 @@ namespace http out = buf; } - bool URL::parse(const char *str, std::size_t len) { - std::string url(str, len ? len : strlen(str)); - return parse(url); + bool URL::parse(const char *str, std::size_t len) + { + return parse({str, len ? len : strlen(str)}); } - bool URL::parse(const std::string& url) { + bool URL::parse(std::string_view url) + { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) { + if(url.at(0) != '/' || pos_p > 0) + { std::size_t pos_s = 0; /* schema */ @@ -141,7 +156,7 @@ namespace http /* port[/path] */ pos_p = pos_c + 1; pos_c = url.find('/', pos_p); - std::string port_str = (pos_c == std::string::npos) + std::string_view port_str = (pos_c == std::string::npos) ? url.substr(pos_p, std::string::npos) : url.substr(pos_p, pos_c - pos_p); /* stoi throws exception on failure, we don't need it */ @@ -253,7 +268,7 @@ namespace http return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) { add_header(name, value.c_str(), replace); } @@ -272,12 +287,13 @@ namespace http headers.erase(name); } - int HTTPReq::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); + int HTTPReq::parse(const char *buf, size_t len) + { + return parse({buf, len}); } - int HTTPReq::parse(const std::string& str) { + int HTTPReq::parse(std::string_view str) + { enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; std::size_t eoh = str.find(HTTP_EOH); /* request head size */ std::size_t eol = 0, pos = 0; @@ -286,9 +302,11 @@ namespace http if (eoh == std::string::npos) return 0; /* str not contains complete request */ - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == REQ_LINE) { - std::string line = str.substr(pos, eol - pos); + while ((eol = str.find(CRLF, pos)) != std::string::npos) + { + if (expect == REQ_LINE) + { + std::string_view line = str.substr(pos, eol - pos); std::vector tokens; strsplit(line, tokens, ' '); if (tokens.size() != 3) @@ -307,7 +325,7 @@ namespace http } else { - std::string line = str.substr(pos, eol - pos); + std::string_view line = str.substr(pos, eol - pos); auto p = parse_header_line(line); if (p.first.length () > 0) headers.push_back (p); @@ -413,12 +431,13 @@ namespace http return length; } - int HTTPRes::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); + int HTTPRes::parse(const char *buf, size_t len) + { + return parse({buf,len}); } - int HTTPRes::parse(const std::string& str) { + int HTTPRes::parse(std::string_view str) + { enum { RES_LINE, HEADER_LINE } expect = RES_LINE; std::size_t eoh = str.find(HTTP_EOH); /* request head size */ std::size_t eol = 0, pos = 0; @@ -426,9 +445,11 @@ namespace http if (eoh == std::string::npos) return 0; /* str not contains complete request */ - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == RES_LINE) { - std::string line = str.substr(pos, eol - pos); + while ((eol = str.find(CRLF, pos)) != std::string::npos) + { + if (expect == RES_LINE) + { + std::string_view line = str.substr(pos, eol - pos); std::vector tokens; strsplit(line, tokens, ' ', 3); if (tokens.size() != 3) @@ -442,8 +463,10 @@ namespace http version = tokens[0]; status = tokens[2]; expect = HEADER_LINE; - } else { - std::string line = str.substr(pos, eol - pos); + } + else + { + std::string_view line = str.substr(pos, eol - pos); auto p = parse_header_line(line); if (p.first.length () > 0) headers.insert (p); @@ -508,14 +531,14 @@ namespace http return ptr; } - std::string UrlDecode(const std::string& data, bool allow_null) + std::string UrlDecode(std::string_view data, bool allow_null) { std::string decoded(data); size_t pos = 0; while ((pos = decoded.find('%', pos)) != std::string::npos) { - char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); - if (c == '\0' && !allow_null) + char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16); + if (!c && !allow_null) { pos += 3; continue; diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 41f0560a..438ef953 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.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 * @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace i2p @@ -45,7 +46,7 @@ namespace http * @return true on success, false on invalid url */ bool parse (const char *str, std::size_t len = 0); - bool parse (const std::string& url); + bool parse (std::string_view url); /** * @brief Parse query part of url to key/value map @@ -69,7 +70,7 @@ namespace http { std::map headers; - void add_header(const char *name, std::string & value, bool replace = false); + void add_header(const char *name, const std::string & value, bool replace = false); void add_header(const char *name, const char *value, bool replace = false); void del_header(const char *name); @@ -92,7 +93,7 @@ namespace http * @note Positive return value is a size of header */ int parse(const char *buf, size_t len); - int parse(const std::string& buf); + int parse(std::string_view buf); /** @brief Serialize HTTP request to string */ std::string to_string(); @@ -128,7 +129,7 @@ namespace http * @note Positive return value is a size of header */ int parse(const char *buf, size_t len); - int parse(const std::string& buf); + int parse(const std::string_view buf); /** * @brief Serialize HTTP response to string @@ -161,7 +162,7 @@ namespace http * @param null If set to true - decode also %00 sequence, otherwise - skip * @return Decoded string */ - std::string UrlDecode(const std::string& data, bool null = false); + std::string UrlDecode(std::string_view data, bool null = false); /** * @brief Merge HTTP response content with Transfer-Encoding: chunked diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 1acd4242..4e26fc94 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -107,7 +107,6 @@ namespace i2p enum I2NPMessageType { - eI2NPDummyMsg = 0, eI2NPDatabaseStore = 1, eI2NPDatabaseLookup = 2, eI2NPDatabaseSearchReply = 3, diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 0164ea4f..18592c9e 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -172,16 +172,6 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept s << std::forward(arg); } -#if (__cplusplus < 201703L) // below C++ 17 -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept -{ - LogPrint (s, std::forward(arg)); - LogPrint (s, std::forward(args)...); -} -#endif - /** * @brief Create log message and send it to queue * @param level Message level (eLogError, eLogInfo, ...) @@ -194,13 +184,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept // fold message to single string std::stringstream ss; - -#if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - auto msg = std::make_shared(level, std::time(nullptr), std::move(ss).str()); msg->tid = std::this_thread::get_id(); i2p::log::Logger().Append(msg); @@ -217,11 +201,7 @@ void ThrowFatal (TArgs&&... args) noexcept if (!f) return; // fold message to single string std::stringstream ss(""); -#if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif f (ss.str ()); } diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 7f0f2189..728ac01d 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -695,7 +695,7 @@ namespace transport return; } auto size = bufbe16toh (buf.data () + 1); - if (size > buf.size () - 3) + if (size > buf.size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1) { LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); Terminate (); @@ -724,8 +724,28 @@ namespace transport SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () : - (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ()); + // update RouterInfo in netdb + auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now + if (!ri1) + { + LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb"); + Terminate (); + return; + } + std::shared_ptr profile; // not null if older + 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 ()) + { + SendTerminationAndTerminate (eNTCP2Banned); + return; + } + } + + auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () : + (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ()); if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) { LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); @@ -737,16 +757,17 @@ 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 { - LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); - Terminate (); + if (profile) // older router? + 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); return; } - i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice // TODO: process options // ready to communicate - auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ()); + SetRemoteIdentity (ri1->GetRouterIdentity ()); if (m_Server.AddNTCP2Session (shared_from_this (), true)) { Established (); @@ -938,8 +959,20 @@ namespace transport break; case eNTCP2BlkRouterInfo: { - LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); - i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size)); + LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); + if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1) + { + auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1); + if (newRi) + { + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) + // peer's RouterInfo update + SetRemoteIdentity (newRi->GetIdentity ()); + } + } + else + LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size); break; } case eNTCP2BlkI2NPMessage: diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 9bd78776..24269015 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -69,7 +69,7 @@ namespace data { Reseed (); } - else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false)) + else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false)) Reseed (); // we don't have a router we can connect to. Trying to reseed auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); @@ -141,10 +141,6 @@ namespace data case eI2NPDatabaseLookup: HandleDatabaseLookupMsg (msg); break; - case eI2NPDummyMsg: - // plain RouterInfo from NTCP2 with flags for now - HandleNTCP2RouterInfoMsg (msg); - break; default: // WTF? LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ()); //i2p::HandleI2NPMessage (msg); @@ -299,7 +295,8 @@ namespace data { auto mts = i2p::util::GetMillisecondsSinceEpoch (); isValid = mts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp () && // from future - mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; // too old + (mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL || // too old + context.GetUptime () < NETDB_CHECK_FOR_EXPIRATION_UPTIME/10); // enough uptime } if (isValid) { @@ -763,7 +760,8 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct) { - if (direct && i2p::transport::transports.RoutesRestricted ()) direct = false; // always use tunnels for restricted routes + if (direct && (i2p::transport::transports.RoutesRestricted () || i2p::context.IsLimitedConnectivity ())) + direct = false; // always use tunnels for restricted routes or limited connectivity if (m_Requests) m_Requests->PostRequestDestination (destination, requestComplete, direct); else @@ -1133,15 +1131,18 @@ namespace data } std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, - bool reverse, bool endpoint) const + bool reverse, bool endpoint, bool clientTunnel) const { + bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate + context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime return GetRandomRouter ( - [compatibleWith, reverse, endpoint](std::shared_ptr router)->bool + [compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)): router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && - router->IsECIES () && !router->IsHighCongestion (false) && + router->IsECIES () && !router->IsHighCongestion (clientTunnel) && + (!checkIsReal || router->GetProfile ()->IsReal ()) && (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) }); } @@ -1314,12 +1315,8 @@ namespace data { // update selection m_ExploratorySelection.clear (); -#if (__cplusplus >= 201703L) // C++ 17 or higher std::vector > eligible; - eligible.reserve (m_RouterInfos.size ()); -#else - auto& eligible = m_ExploratorySelection; -#endif + eligible.reserve (m_RouterInfos.size ()); { // collect eligible from current netdb bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD; // too low rate @@ -1329,22 +1326,13 @@ namespace data (!checkIsReal || (it.second->HasProfile () && it.second->GetProfile ()->IsReal ()))) eligible.push_back (it.second); } -#if (__cplusplus >= 201703L) // C++ 17 or higher 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)); } else - std::swap (m_ExploratorySelection, eligible); -#else - if (m_ExploratorySelection.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE) - { - // reduce number of eligible to max selection size - std::shuffle (m_ExploratorySelection.begin(), m_ExploratorySelection.end(), std::mt19937(ts)); - m_ExploratorySelection.resize (NETDB_MAX_EXPLORATORY_SELECTION_SIZE); - } -#endif + std::swap (m_ExploratorySelection, eligible); m_LastExploratorySelectionUpdateTime = ts; } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 8efba61a..b84387de 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -87,7 +87,7 @@ namespace data void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true); std::shared_ptr GetRandomRouter () const; - std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint) const; + std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint, bool clientTunnel) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint) const; std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set& excluded) const; std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::unordered_set& excluded) const; @@ -127,12 +127,12 @@ namespace data } bool PopulateRouterInfoBuffer (std::shared_ptr r); std::shared_ptr NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); }; - boost::shared_ptr NewRouterInfoAddresses () + RouterInfo::AddressesPtr NewRouterInfoAddresses () { - return boost::shared_ptr(m_RouterInfoAddressVectorsPool.AcquireMt (), + return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (), std::bind ::*)(RouterInfo::Addresses *)> (&i2p::util::MemoryPoolMt::ReleaseMt, - &m_RouterInfoAddressVectorsPool, std::placeholders::_1)); + &m_RouterInfoAddressVectorsPool, std::placeholders::_1)}; }; std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); }; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 5461cdfd..1efb25db 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -323,6 +323,10 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; + case eRouterStatusProxy: + m_AcceptsTunnels = false; + UpdateCongestion (); + break; default: ; } @@ -553,6 +557,12 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + { + if (m_RouterInfo.UpdateSSU2Introducer (h, v4, iTag, iExp)) + UpdateRouterInfo (); + } + void RouterContext::ClearSSU2Introducers (bool v4) { auto addr = m_RouterInfo.GetSSU2Address (v4); @@ -610,8 +620,8 @@ namespace i2p case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break; case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low; break; // 48 case i2p::data::CAPS_FLAG_LOW_BANDWIDTH3 : limit = 64; type = low; break; - case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 128; type = high; break; - case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high; break; // 256 + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH4 : limit = 128; type = low; break; + case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high; break; // 256 case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = i2p::data::EXTRA_BANDWIDTH_LIMIT; type = extra; break; // 2048 case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s default: @@ -626,9 +636,7 @@ namespace i2p case low : /* not set */; break; case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P' case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here, extra + high means 'X' case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 09c3678f..c620f8b1 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -154,6 +154,7 @@ namespace garlic void PublishSSU2Address (int port, bool publish, bool v4, bool v6); bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4); void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4); + void UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); void ClearSSU2Introducers (bool v4); bool IsUnreachable () const; void SetUnreachable (bool v4, bool v6); @@ -177,6 +178,7 @@ namespace garlic void SetMTU (int mtu, bool v4); void SetHidden(bool hide) { m_IsHiddenMode = hide; }; bool IsHidden() const { return m_IsHiddenMode; }; + bool IsLimitedConnectivity () const { return m_Status == eRouterStatusProxy; }; // TODO: implement other cases i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index d819999e..2da40ae8 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -10,10 +10,10 @@ #include #include "I2PEndian.h" #include +#include #include -#include #include // for boost::to_lower -#if (BOOST_VERSION >= 105300) +#ifndef __cpp_lib_atomic_shared_ptr #include #endif #include "version.h" @@ -40,7 +40,7 @@ namespace data RouterInfo::RouterInfo (): m_Buffer (nullptr) { - m_Addresses = boost::make_shared(); // create empty list + m_Addresses = AddressesPtr(new Addresses ()); // create empty list } RouterInfo::RouterInfo (const std::string& fullPath): @@ -48,8 +48,8 @@ namespace data m_SupportedTransports (0),m_ReachableTransports (0), m_PublishedTransports (0), m_Caps (0), m_Version (0), m_Congestion (eLowCongestion) { - m_Addresses = boost::make_shared(); // create empty list - m_Buffer = NewBuffer (); // always RouterInfo's + m_Addresses = AddressesPtr(new Addresses ()); // create empty list + m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's ReadFromFile (fullPath); } @@ -60,7 +60,7 @@ namespace data { if (len <= MAX_RI_BUFFER_SIZE) { - m_Addresses = boost::make_shared(); // create empty list + m_Addresses = AddressesPtr(new Addresses ()); // create empty list m_Buffer = buf; if (m_Buffer) m_Buffer->SetBufferLen (len); ReadFromBuffer (true); @@ -74,7 +74,7 @@ namespace data } RouterInfo::RouterInfo (const uint8_t * buf, size_t len): - RouterInfo (std::make_shared (buf, len), len) + RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len) { } @@ -439,10 +439,10 @@ namespace data } m_ReachableTransports |= m_PublishedTransports; // update addresses -#if (BOOST_VERSION >= 105300) +#ifdef __cpp_lib_atomic_shared_ptr + m_Addresses = addresses; +#else boost::atomic_store (&m_Addresses, addresses); -#else - m_Addresses = addresses; // race condition #endif // read peers uint8_t numPeers; @@ -538,10 +538,10 @@ namespace data case CAPS_FLAG_LOW_BANDWIDTH1: case CAPS_FLAG_LOW_BANDWIDTH2: case CAPS_FLAG_LOW_BANDWIDTH3: + case CAPS_FLAG_LOW_BANDWIDTH4: m_BandwidthCap = *cap; break; - case CAPS_FLAG_HIGH_BANDWIDTH1: - case CAPS_FLAG_HIGH_BANDWIDTH2: + case CAPS_FLAG_HIGH_BANDWIDTH: m_Caps |= Caps::eHighBandwidth; m_BandwidthCap = *cap; break; @@ -692,12 +692,12 @@ namespace data if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; - (*m_Addresses)[eNTCP2V4Idx] = addr; + (*GetAddresses ())[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eNTCP2V6; - (*m_Addresses)[eNTCP2V6Idx] = addr; + (*GetAddresses ())[eNTCP2V6Idx] = addr; } } @@ -718,11 +718,12 @@ namespace data if (host.is_v4 ()) addr->caps |= eV4; if (host.is_v6 ()) addr->caps |= eV6; } + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; m_ReachableTransports |= eNTCP2V4; - (*m_Addresses)[eNTCP2V4Idx] = addr; + (*addresses)[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { @@ -730,30 +731,31 @@ namespace data { m_SupportedTransports |= eNTCP2V6Mesh; m_ReachableTransports |= eNTCP2V6Mesh; - (*m_Addresses)[eNTCP2V6MeshIdx] = addr; + (*addresses)[eNTCP2V6MeshIdx] = addr; } else { m_SupportedTransports |= eNTCP2V6; m_ReachableTransports |= eNTCP2V6; - (*m_Addresses)[eNTCP2V6Idx] = addr; + (*addresses)[eNTCP2V6Idx] = addr; } } } void RouterInfo::RemoveNTCP2Address (bool v4) { + auto addresses = GetAddresses (); if (v4) { - if ((*m_Addresses)[eNTCP2V6Idx]) - (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eNTCP2V4Idx].reset (); + if ((*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); } else { - if ((*m_Addresses)[eNTCP2V4Idx]) - (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eNTCP2V6Idx].reset (); + if ((*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eNTCP2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -769,15 +771,16 @@ namespace data addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; - (*m_Addresses)[eSSU2V4Idx] = addr; + (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; - (*m_Addresses)[eSSU2V6Idx] = addr; + (*addresses)[eSSU2V6Idx] = addr; } } @@ -802,33 +805,35 @@ namespace data if (host.is_v4 ()) addr->caps |= eV4; if (host.is_v6 ()) addr->caps |= eV6; } + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; m_ReachableTransports |= eSSU2V4; - (*m_Addresses)[eSSU2V4Idx] = addr; + (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; m_ReachableTransports |= eSSU2V6; - (*m_Addresses)[eSSU2V6Idx] = addr; + (*addresses)[eSSU2V6Idx] = addr; } } void RouterInfo::RemoveSSU2Address (bool v4) { + auto addresses = GetAddresses (); if (v4) { - if ((*m_Addresses)[eSSU2V6Idx]) - (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eSSU2V4Idx].reset (); + if ((*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } else { - if ((*m_Addresses)[eSSU2V4Idx]) - (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eSSU2V6Idx].reset (); + if ((*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -869,17 +874,18 @@ namespace data { if (IsV6 ()) { - if ((*m_Addresses)[eNTCP2V6Idx]) + auto addresses = GetAddresses (); + if ((*addresses)[eNTCP2V6Idx]) { - if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx]) - (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eNTCP2V6Idx].reset (); + if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eNTCP2V6Idx].reset (); } - if ((*m_Addresses)[eSSU2V6Idx]) + if ((*addresses)[eSSU2V6Idx]) { - if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx]) - (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eSSU2V6Idx].reset (); + if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -889,17 +895,18 @@ namespace data { if (IsV4 ()) { - if ((*m_Addresses)[eNTCP2V4Idx]) + auto addresses = GetAddresses (); + if ((*addresses)[eNTCP2V4Idx]) { - if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx]) - (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eNTCP2V4Idx].reset (); + if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); } - if ((*m_Addresses)[eSSU2V4Idx]) + if ((*addresses)[eSSU2V4Idx]) { - if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx]) - (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eSSU2V4Idx].reset (); + if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } UpdateSupportedTransports (); } @@ -920,7 +927,7 @@ namespace data { m_SupportedTransports &= ~eNTCP2V6Mesh; m_ReachableTransports &= ~eNTCP2V6Mesh; - (*m_Addresses)[eNTCP2V6MeshIdx].reset (); + (*GetAddresses ())[eNTCP2V6MeshIdx].reset (); } } @@ -949,12 +956,12 @@ namespace data return nullptr; } - boost::shared_ptr RouterInfo::GetAddresses () const + RouterInfo::AddressesPtr RouterInfo::GetAddresses () const { -#if (BOOST_VERSION >= 105300) - return boost::atomic_load (&m_Addresses); -#else +#ifdef __cpp_lib_atomic_shared_ptr return m_Addresses; +#else + return boost::atomic_load (&m_Addresses); #endif } @@ -962,10 +969,10 @@ namespace data std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator -#if (BOOST_VERSION >= 105300) +#ifdef __cpp_lib_atomic_shared_ptr + AddressesPtr addresses = m_Addresses; +#else auto addresses = boost::atomic_load (&m_Addresses); -#else - auto addresses = m_Addresses; #endif for (const auto& address : *addresses) if (address && filter (address)) return address; @@ -1062,7 +1069,7 @@ namespace data void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { - for (auto& addr: *m_Addresses) + for (auto& addr: *GetAddresses ()) { if (addr && !addr->published) { @@ -1076,7 +1083,7 @@ namespace data { m_SupportedTransports = 0; m_ReachableTransports = 0; - for (const auto& addr: *m_Addresses) + for (const auto& addr: *GetAddresses ()) { if (!addr) continue; uint8_t transports = 0; @@ -1151,7 +1158,7 @@ namespace data return netdb.NewRouterInfoAddress (); } - boost::shared_ptr RouterInfo::NewAddresses () const + RouterInfo::AddressesPtr RouterInfo::NewAddresses () const { return netdb.NewRouterInfoAddresses (); } @@ -1224,7 +1231,7 @@ namespace data CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else - caps += CAPS_FLAG_HIGH_BANDWIDTH2; // 'O' + caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else @@ -1232,7 +1239,7 @@ namespace data if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ else - caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH2 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth + caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable @@ -1503,9 +1510,9 @@ namespace data return std::make_shared
(); } - boost::shared_ptr LocalRouterInfo::NewAddresses () const + RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const { - return boost::make_shared (); + return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); } std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const @@ -1547,5 +1554,23 @@ namespace data } return false; } + + bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + { + auto addresses = GetAddresses (); + if (!addresses) return false; + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) + { + for (auto& it: addr->ssu->introducers) + if (h == it.iH) + { + it.iTag = iTag; + it.iExp = iExp; + return true; + } + } + return false; + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index c2668bc4..72521797 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -15,8 +15,11 @@ #include #include #include +#include #include +#ifndef __cpp_lib_atomic_shared_ptr #include +#endif #include "Identity.h" #include "Profiling.h" #include "Family.h" @@ -40,8 +43,8 @@ namespace data const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */ const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */ const char CAPS_FLAG_LOW_BANDWIDTH3 = 'M'; /* 48-64 KBps */ - const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'N'; /* 64-128 KBps */ - const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'O'; /* 128-256 KBps */ + const char CAPS_FLAG_LOW_BANDWIDTH4 = 'N'; /* 64-128 KBps */ + const char CAPS_FLAG_HIGH_BANDWIDTH = 'O'; /* 128-256 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2048 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2048 KBps */ // bandwidth limits in kBps @@ -199,7 +202,11 @@ namespace data }; typedef std::array, eNumTransports> Addresses; - +#ifdef __cpp_lib_atomic_shared_ptr + typedef std::shared_ptr AddressesPtr; +#else + typedef boost::shared_ptr AddressesPtr; +#endif RouterInfo (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; @@ -214,7 +221,7 @@ namespace data int GetVersion () const { return m_Version; }; virtual void SetProperty (const std::string& key, const std::string& value) {}; virtual void ClearProperties () {}; - boost::shared_ptr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr + AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2V4Address () const; std::shared_ptr GetNTCP2V6Address () const; std::shared_ptr GetPublishedNTCP2V4Address () const; @@ -333,7 +340,7 @@ namespace data std::shared_ptr GetAddress (Filter filter) const; virtual std::shared_ptr NewBuffer () const; virtual std::shared_ptr
NewAddress () const; - virtual boost::shared_ptr NewAddresses () const; + virtual AddressesPtr NewAddresses () const; virtual std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const; private: @@ -342,7 +349,11 @@ namespace data std::shared_ptr m_RouterIdentity; std::shared_ptr m_Buffer; uint64_t m_Timestamp; // in milliseconds - boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 +#ifdef __cpp_lib_atomic_shared_ptr + std::atomic m_Addresses; +#else + AddressesPtr m_Addresses; +#endif bool m_IsUpdated, m_IsUnreachable, m_IsFloodfill; CompatibleTransports m_SupportedTransports, m_ReachableTransports, m_PublishedTransports; uint8_t m_Caps; @@ -369,6 +380,7 @@ namespace data bool AddSSU2Introducer (const Introducer& introducer, bool v4); bool RemoveSSU2Introducer (const IdentHash& h, bool v4); + bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); private: @@ -377,7 +389,7 @@ namespace data void WriteString (const std::string& str, std::ostream& s) const; std::shared_ptr NewBuffer () const override; std::shared_ptr
NewAddress () const override; - boost::shared_ptr NewAddresses () const override; + RouterInfo::AddressesPtr NewAddresses () const override; std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index b86e3743..83d23dd2 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -152,8 +152,11 @@ namespace transport m_SessionsByRouterHash.clear (); m_PendingOutgoingSessions.clear (); m_Relays.clear (); + m_PeerTests.clear (); m_Introducers.clear (); m_IntroducersV6.clear (); + m_ConnectedRecently.clear (); + m_RequestedPeerTests.clear (); } void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) @@ -210,6 +213,29 @@ namespace transport return ep.port (); } + bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep) + { + if (!ep.port () || ep.address ().is_unspecified ()) return false; + auto it = m_ConnectedRecently.find (ep); + if (it != m_ConnectedRecently.end ()) + { + if (i2p::util::GetSecondsSinceEpoch () <= it->second + SSU2_HOLE_PUNCH_EXPIRATION) + return true; + else + m_ConnectedRecently.erase (it); + } + return false; + } + + void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts) + { + if (!ep.port () || ep.address ().is_unspecified () || + i2p::util::GetSecondsSinceEpoch () > ts + SSU2_HOLE_PUNCH_EXPIRATION) return; + auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts); + if (!added && ts > it->second) + it->second = ts; // renew timestamp of existing endpoint + } + void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr from) { if (offset) @@ -343,16 +369,25 @@ namespace transport size_t moreBytes = socket.available (ec); if (!ec && moreBytes) { - std::vector packets; - packets.push_back (packet); - while (moreBytes && packets.size () < 32) + auto packets = m_PacketsArrayPool.AcquireMt (); + packets->AddPacket (packet); + while (moreBytes && packets->numPackets < SSU2_MAX_NUM_PACKETS_PER_BATCH) { packet = m_PacketsPool.AcquireMt (); packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); if (!ec) { i2p::transport::transports.UpdateReceivedBytes (packet->len); - packets.push_back (packet); + if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE) + { + if (!packets->AddPacket (packet)) + { + LogPrint (eLogError, "SSU2: Received packets array is full"); + m_PacketsPool.ReleaseMt (packet); + } + } + else // drop too short packets + m_PacketsPool.ReleaseMt (packet); moreBytes = socket.available(ec); if (ec) break; } @@ -407,15 +442,23 @@ namespace transport } } - void SSU2Server::HandleReceivedPackets (std::vector packets) + void SSU2Server::HandleReceivedPackets (Packets * packets) { + if (!packets) return; if (m_IsThroughProxy) - for (auto& packet: packets) + for (size_t i = 0; i < packets->numPackets; i++) + { + auto& packet = (*packets)[i]; ProcessNextPacketFromProxy (packet->buf, packet->len); + } else - for (auto& packet: packets) + for (size_t i = 0; i < packets->numPackets; i++) + { + auto& packet = (*packets)[i]; ProcessNextPacket (packet->buf, packet->len, packet->from); - m_PacketsPool.ReleaseMt (packets); + } + m_PacketsPool.ReleaseMt (packets->data (), packets->numPackets); + m_PacketsArrayPool.ReleaseMt (packets); if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) m_LastSession->FlushData (); } @@ -425,7 +468,8 @@ namespace transport if (session) { m_Sessions.emplace (session->GetConnID (), session); - AddSessionByRouterHash (session); + if (session->GetState () != eSSU2SessionStatePeerTest) + AddSessionByRouterHash (session); } } @@ -434,19 +478,28 @@ namespace transport auto it = m_Sessions.find (connID); if (it != m_Sessions.end ()) { - auto ident = it->second->GetRemoteIdentity (); - if (ident) - { - auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ()); - if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second) - m_SessionsByRouterHash.erase (it1); - } + if (it->second->GetState () != eSSU2SessionStatePeerTest) + { + auto ident = it->second->GetRemoteIdentity (); + if (ident) + { + std::lock_guard l(m_SessionsByRouterHashMutex); + auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ()); + if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second.lock ()) + m_SessionsByRouterHash.erase (it1); + } + } if (m_LastSession == it->second) m_LastSession = nullptr; m_Sessions.erase (it); } } + void SSU2Server::RequestRemoveSession (uint64_t connID) + { + GetService ().post ([connID, this]() { RemoveSession (connID); }); + } + void SSU2Server::AddSessionByRouterHash (std::shared_ptr session) { if (session) @@ -454,18 +507,26 @@ namespace transport auto ident = session->GetRemoteIdentity (); if (ident) { - auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); - if (!ret.second && ret.first->second != session) + std::shared_ptr oldSession; { + std::lock_guard l(m_SessionsByRouterHashMutex); + auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); + if (!ret.second) + { + oldSession = ret.first->second.lock (); + // update session + ret.first->second = session; + } + } + if (oldSession && oldSession != session) + { // session already exists LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); // move unsent msgs to new session - ret.first->second->MoveSendQueue (session); + oldSession->MoveSendQueue (session); // terminate existing - GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession)); - // update session - ret.first->second = session; - } + GetService ().post (std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession)); + } } } } @@ -473,21 +534,30 @@ namespace transport bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) { if (!session) return false; - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; } - std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) const + std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) { + std::lock_guard l(m_SessionsByRouterHashMutex); auto it = m_SessionsByRouterHash.find (ident); if (it != m_SessionsByRouterHash.end ()) - return it->second; + { + if (!it->second.expired ()) + { + auto s = it->second.lock (); + if (s && s->GetState () != eSSU2SessionStateTerminated) + return s; + } + m_SessionsByRouterHash.erase (it); + } return nullptr; } std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); auto it = m_PendingOutgoingSessions.find (ep); if (it != m_PendingOutgoingSessions.end ()) return it->second; @@ -496,7 +566,7 @@ namespace transport void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); m_PendingOutgoingSessions.erase (ep); } @@ -541,14 +611,51 @@ namespace transport auto it = m_Relays.find (tag); if (it != m_Relays.end ()) { - if (it->second->IsEstablished ()) - return it->second; - else - m_Relays.erase (it); + if (!it->second.expired ()) + { + auto s = it->second.lock (); + if (s && s->IsEstablished ()) + return s; + } + m_Relays.erase (it); } return nullptr; } + bool SSU2Server::AddPeerTest (uint32_t nonce, std::shared_ptr aliceSession, uint64_t ts) + { + return m_PeerTests.emplace (nonce, std::pair{ aliceSession, ts }).second; + } + + std::shared_ptr SSU2Server::GetPeerTest (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + { + auto s = it->second.first.lock (); + m_PeerTests.erase (it); + return s; + } + return nullptr; + } + + bool SSU2Server::AddRequestedPeerTest (uint32_t nonce, std::shared_ptr session, uint64_t ts) + { + return m_RequestedPeerTests.emplace (nonce, std::pair{ session, ts }).second; + } + + std::shared_ptr SSU2Server::GetRequestedPeerTest (uint32_t nonce) + { + auto it = m_RequestedPeerTests.find (nonce); + if (it != m_RequestedPeerTests.end ()) + { + auto s = it->second.first.lock (); + m_RequestedPeerTests.erase (it); + return s; + } + return nullptr; + } + void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { if (len < 24) return; @@ -618,7 +725,7 @@ namespace transport if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent && it1->second->ProcessSessionCreated (buf, len)) { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint } else @@ -715,15 +822,12 @@ namespace transport if (router && address) { // check if no session - auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); - if (it != m_SessionsByRouterHash.end ()) + auto existingSession = FindSession (router->GetIdentHash ()); + if (existingSession) { // session with router found, trying to send peer test if requested - if (peerTest && it->second->IsEstablished ()) - { - auto session = it->second; - GetService ().post ([session]() { session->SendPeerTest (); }); - } + if (peerTest && existingSession->IsEstablished ()) + GetService ().post ([existingSession]() { existingSession->SendPeerTest (); }); return false; } // check is no pending session @@ -780,15 +884,15 @@ namespace transport { if (it.iTag && ts < it.iExp) { - auto it1 = m_SessionsByRouterHash.find (it.iH); - if (it1 != m_SessionsByRouterHash.end ()) + auto s = FindSession (it.iH); + if (s) { - auto addr = it1->second->GetAddress (); + auto addr = s->GetAddress (); if (addr && addr->IsIntroducer ()) { - it1->second->Introduce (session, it.iTag); + s->Introduce (session, it.iTag); return; - } + } } else indices.push_back(i); @@ -894,17 +998,16 @@ namespace transport if (!router) return false; auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address (); if (!addr) return false; - auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); - if (it != m_SessionsByRouterHash.end ()) + auto session = FindSession (router->GetIdentHash ()); + if (session) { - auto remoteAddr = it->second->GetAddress (); + auto remoteAddr = session->GetAddress (); if (!remoteAddr || !remoteAddr->IsPeerTesting () || - (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; - auto s = it->second; - if (s->IsEstablished ()) - GetService ().post ([s]() { s->SendPeerTest (); }); + (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; + if (session->IsEstablished ()) + GetService ().post ([session]() { session->SendPeerTest (); }); else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); + session->SetOnEstablished ([session]() { session->SendPeerTest (); }); return true; } else @@ -925,17 +1028,20 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) + { - if (it->second->IsTerminationTimeoutExpired (ts)) + std::lock_guard l(m_PendingOutgoingSessionsMutex); + for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) { - //it->second->Terminate (); - std::unique_lock l(m_PendingOutgoingSessionsMutex); - it = m_PendingOutgoingSessions.erase (it); + if (it->second->IsTerminationTimeoutExpired (ts)) + { + //it->second->Terminate (); + it = m_PendingOutgoingSessions.erase (it); + } + else + it++; } - else - it++; - } + } for (auto it: m_Sessions) { @@ -953,14 +1059,6 @@ namespace transport it.second->CleanUp (ts); } - for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) - { - if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) - it = m_SessionsByRouterHash.erase (it); - else - it++; - } - ScheduleTermination (); } } @@ -979,12 +1077,23 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Relays.begin (); it != m_Relays.begin ();) { - if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) + if (it->second.expired ()) it = m_Relays.erase (it); else it++; } + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT || it->second.first.expired ()) + { + LogPrint (eLogInfo, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds or session invalid. Deleted"); + it = m_PeerTests.erase (it); + } + else + it++; + } + for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) { if (ts > it->second.second) @@ -1001,7 +1110,35 @@ namespace transport it++; } + for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); ) + { + if (ts > it->second + SSU2_HOLE_PUNCH_EXPIRATION) + it = m_ConnectedRecently.erase (it); + else + it++; + } + + for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) + it = m_RequestedPeerTests.erase (it); + else + it++; + } + + { + std::lock_guard l(m_SessionsByRouterHashMutex); + for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) + { + if (it->second.expired ()) + it = m_SessionsByRouterHash.erase (it); + else + it++; + } + } + m_PacketsPool.CleanUpMt (); + m_PacketsArrayPool.CleanUpMt (); m_SentPacketsPool.CleanUp (); m_IncompleteMessagesPool.CleanUp (); m_FragmentsPool.CleanUp (); @@ -1085,97 +1222,100 @@ namespace transport } std::vector > SSU2Server::FindIntroducers (int maxNumIntroducers, - bool v4, const std::unordered_set& excluded) const + bool v4, const std::unordered_set& excluded) { std::vector > ret; - if (maxNumIntroducers <= 0) return ret; - auto newer = [](const std::shared_ptr& s1, const std::shared_ptr& s2) -> bool - { - auto t1 = s1->GetCreationTime (), t2 = s2->GetCreationTime (); - return (t1 != t2) ? (t1 > t2) : (s1->GetConnID () > s2->GetConnID ()); - }; - std::set, decltype (newer)> introducers(newer); + if (maxNumIntroducers <= 0 || m_Sessions.empty ()) return ret; + + std::vector > eligible; + eligible.reserve (m_Sessions.size ()/2); + auto ts = i2p::util::GetSecondsSinceEpoch (); for (const auto& s : m_Sessions) { if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) && + ts < s.second->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION/2 && !excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) && ((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) || (!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6)))) - introducers.insert (s.second); + eligible.push_back (s.second); } - int i = 0; - for (auto it: introducers) - { - ret.push_back (it); - i++; - if (i >= maxNumIntroducers) break; - } + + if (eligible.size () <= (size_t)maxNumIntroducers) + return eligible; + else + std::sample (eligible.begin(), eligible.end(), std::back_inserter(ret), maxNumIntroducers, m_Rng); return ret; } void SSU2Server::UpdateIntroducers (bool v4) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::list newList, impliedList; + std::list > newList, impliedList; auto& introducers = v4 ? m_Introducers : m_IntroducersV6; std::unordered_set excluded; - for (const auto& it : introducers) + for (const auto& [ident, tag] : introducers) { - std::shared_ptr session; - auto it1 = m_SessionsByRouterHash.find (it); - if (it1 != m_SessionsByRouterHash.end ()) - { - session = it1->second; - excluded.insert (it); - } - if (session && session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer? - ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) + std::shared_ptr session = FindSession (ident); + if (session) + excluded.insert (ident); + if (session) { - session->SendKeepAlive (); - if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) - newList.push_back (it); - else + if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer? + ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) { - impliedList.push_back (it); // keep in introducers list, but not publish - session = nullptr; - } + session->SendKeepAlive (); + if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) + { + newList.push_back ({ident, session->GetRelayTag ()}); + if (tag != session->GetRelayTag ()) + { + LogPrint (eLogDebug, "SSU2: Introducer session to ", session->GetIdentHashBase64() , " was replaced. iTag ", tag, "->", session->GetRelayTag ()); + i2p::context.UpdateSSU2Introducer (ident, v4, session->GetRelayTag (), + session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION); + } + } + else + { + impliedList.push_back ({ident, session->GetRelayTag ()}); // keep in introducers list, but not publish + session = nullptr; + } + } + else + session = nullptr; } - else - session = nullptr; if (!session) - i2p::context.RemoveSSU2Introducer (it, v4); + i2p::context.RemoveSSU2Introducer (ident, v4); } + int numOldSessions = 0; if (newList.size () < SSU2_MAX_NUM_INTRODUCERS) { auto sessions = FindIntroducers (SSU2_MAX_NUM_INTRODUCERS - newList.size (), v4, excluded); - if (sessions.empty () && !introducers.empty ()) + if (sessions.empty () && !impliedList.empty ()) { - // bump creation time for previous introducers if no new sessions found LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing"); - impliedList.clear (); - for (auto& it : introducers) + for (const auto& it : impliedList) { - auto it1 = m_SessionsByRouterHash.find (it); - if (it1 != m_SessionsByRouterHash.end ()) + auto session = FindSession (it.first); + if (session) { - auto session = it1->second; - if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ()) - { - session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION); - if (std::find (newList.begin (), newList.end (), it) == newList.end ()) - sessions.push_back (session); - } + if (std::find_if (newList.begin (), newList.end (), + [&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ()) + { + sessions.push_back (session); + numOldSessions++; + } } } + impliedList.clear (); } for (const auto& it : sessions) { - uint32_t tag = it->GetRelayTag (); + uint32_t tag = it->GetRelayTag (); uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; - if (!tag || ts + SSU2_TO_INTRODUCER_SESSION_DURATION/2 > exp) - continue; // don't pick too old session for introducer + if (!tag && ts >= exp) + continue; // don't publish expired introducer i2p::data::RouterInfo::Introducer introducer; introducer.iTag = tag; introducer.iH = it->GetRemoteIdentity ()->GetIdentHash (); @@ -1185,16 +1325,28 @@ namespace transport { LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ", i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ())); - newList.push_back (it->GetRemoteIdentity ()->GetIdentHash ()); + newList.push_back ({ it->GetRemoteIdentity ()->GetIdentHash (), tag }); + it->SendKeepAlive (); if (newList.size () >= SSU2_MAX_NUM_INTRODUCERS) break; } } } introducers = newList; - if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS) + if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS || numOldSessions) { - for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS; i++) + // we need to create more sessions with relay tag + + // exclude all existing sessions + excluded.clear (); + { + std::lock_guard l(m_SessionsByRouterHashMutex); + for (const auto& [ident, s] : m_SessionsByRouterHash) + excluded.insert (ident); + } + + // sesssion 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); if (introducer) diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index fd071e8d..2b97bd25 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "util.h" @@ -39,6 +40,8 @@ namespace transport const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds + const int SSU2_HOLE_PUNCH_EXPIRATION = 150; // in seconds + const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 32; class SSU2Server: private i2p::util::RunnableServiceWithWork { @@ -49,6 +52,20 @@ namespace transport boost::asio::ip::udp::endpoint from; }; + struct Packets: public std::array + { + size_t numPackets = 0; + bool AddPacket (Packet *p) + { + if (p && numPackets < size ()) + { + data()[numPackets] = p; numPackets++; + return true; + } + return false; + } + }; + class ReceiveService: public i2p::util::RunnableService { public: @@ -72,6 +89,8 @@ namespace transport bool UsesProxy () const { return m_IsThroughProxy; }; bool IsSupported (const boost::asio::ip::address& addr) const; uint16_t GetPort (bool v4) const; + bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep); + void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts); std::mt19937& GetRng () { return m_Rng; } bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; @@ -79,10 +98,11 @@ namespace transport void AddSession (std::shared_ptr session); void RemoveSession (uint64_t connID); + void RequestRemoveSession (uint64_t connID); void AddSessionByRouterHash (std::shared_ptr session); bool AddPendingOutgoingSession (std::shared_ptr session); void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep); - std::shared_ptr FindSession (const i2p::data::IdentHash& ident) const; + std::shared_ptr FindSession (const i2p::data::IdentHash& ident); std::shared_ptr FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const; std::shared_ptr GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded); @@ -91,6 +111,12 @@ namespace transport void RemoveRelay (uint32_t tag); std::shared_ptr FindRelaySession (uint32_t tag); + bool AddPeerTest (uint32_t nonce, std::shared_ptr aliceSession, uint64_t ts); + std::shared_ptr GetPeerTest (uint32_t nonce); + + bool AddRequestedPeerTest (uint32_t nonce, std::shared_ptr session, uint64_t ts); + std::shared_ptr GetRequestedPeerTest (uint32_t nonce); + void Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); void Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, @@ -119,7 +145,7 @@ namespace transport void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred, Packet * packet, boost::asio::ip::udp::socket& socket); void HandleReceivedPacket (Packet * packet); - void HandleReceivedPackets (std::vector packets); + void HandleReceivedPackets (Packets * packets); void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void ScheduleTermination (); @@ -133,7 +159,7 @@ namespace transport void ConnectThroughIntroducer (std::shared_ptr session); std::vector > FindIntroducers (int maxNumIntroducers, - bool v4, const std::unordered_set& excluded) const; + bool v4, const std::unordered_set& excluded); void UpdateIntroducers (bool v4); void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); @@ -156,13 +182,16 @@ namespace transport boost::asio::ip::udp::socket m_SocketV4, m_SocketV6; boost::asio::ip::address m_AddressV4, m_AddressV6; std::unordered_map > m_Sessions; - std::unordered_map > m_SessionsByRouterHash; + std::unordered_map > m_SessionsByRouterHash; + mutable std::mutex m_SessionsByRouterHashMutex; std::map > m_PendingOutgoingSessions; mutable std::mutex m_PendingOutgoingSessionsMutex; std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) - std::map > m_Relays; // we are introducer, relay tag -> session - std::list m_Introducers, m_IntroducersV6; // introducers we are connected to + std::unordered_map > m_Relays; // we are introducer, relay tag -> session + std::unordered_map, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob + std::list > m_Introducers, m_IntroducersV6; // introducers we are connected to i2p::util::MemoryPoolMt m_PacketsPool; + i2p::util::MemoryPoolMt m_PacketsArrayPool; i2p::util::MemoryPool m_SentPacketsPool; i2p::util::MemoryPool m_IncompleteMessagesPool; i2p::util::MemoryPool m_FragmentsPool; @@ -174,7 +203,9 @@ namespace transport int64_t m_PendingTimeOffset; // during peer test std::shared_ptr m_PendingTimeOffsetFrom; std::mt19937 m_Rng; - + std::map m_ConnectedRecently; // endpoint -> last activity time in seconds + std::unordered_map, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) + // proxy bool m_IsThroughProxy; uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE]; diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 92fe2d46..6213c614 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -18,6 +18,12 @@ namespace i2p { namespace transport { + static inline void CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } + void SSU2IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) { if (msg->len + fragmentSize > msg->maxLen) @@ -79,7 +85,7 @@ namespace transport } SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter, - std::shared_ptr addr): + std::shared_ptr addr, bool noise): TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), @@ -93,11 +99,13 @@ namespace transport m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size m_LastResendTime (0), m_LastResendAttemptTime (0) { - m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); + if (noise) + m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); if (in_RemoteRouter && m_Address) { // outgoing - InitNoiseXKState1 (*m_NoiseState, m_Address->s); + if (noise) + InitNoiseXKState1 (*m_NoiseState, m_Address->s); m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port); m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false); if (in_RemoteRouter->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4; @@ -108,7 +116,8 @@ namespace transport else { // incoming - InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); + if (noise) + InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); } } @@ -161,40 +170,47 @@ namespace transport if (!session || !relayTag) return false; // find local address to introduce auto localAddress = session->FindLocalAddress (); - if (!localAddress) return false; + if (!localAddress || localAddress->host.is_unspecified () || !localAddress->port) + { + // can't introduce invalid endpoint + LogPrint (eLogWarning, "SSU2: Can't find local address to introduce"); + return false; + } // create nonce uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); // payload - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 0; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayRequest; payload[3] = 0; // flag htobe32buf (payload + 4, nonce); htobe32buf (payload + 8, relayTag); - htobe32buf (payload + 12, ts); + htobe32buf (payload + 12, ts/1000); payload[16] = 2; // ver size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); if (!asz) return false; payload[17] = asz; - payloadSize += asz + 18; + packet->payloadSize = asz + 18; SignedData s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash s.Insert (payload + 4, 14 + asz); // nonce, relay tag, timestamp, ver, asz and Alice's endpoint - s.Sign (i2p::context.GetPrivateKeys (), payload + payloadSize); - payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); - htobe16buf (payload + 1, payloadSize - 3); // size - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + s.Sign (i2p::context.GetPrivateKeys (), payload + packet->payloadSize); + packet->payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); + htobe16buf (payload + 1, packet->payloadSize - 3); // size + packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); // send - m_RelaySessions.emplace (nonce, std::make_pair (session, ts)); + m_RelaySessions.emplace (nonce, std::make_pair (session, ts/1000)); session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); session->m_DestConnID = ~session->m_SourceConnID; m_Server.AddSession (session); - SendData (payload, payloadSize); - + int32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); + return true; } @@ -208,15 +224,22 @@ namespace transport { if (m_State == eSSU2SessionStateIntroduced) { - // create new connID - uint64_t oldConnID = GetConnID (); - RAND_bytes ((uint8_t *)&m_DestConnID, 8); - RAND_bytes ((uint8_t *)&m_SourceConnID, 8); - // connect + // we are Alice + // keep ConnIDs used for introduction, because Charlie waits for SessionRequest from us m_State = eSSU2SessionStateTokenReceived; - m_Server.AddPendingOutgoingSession (shared_from_this ()); - m_Server.RemoveSession (oldConnID); - Connect (); + // move session to pending outgoing + if (m_Server.AddPendingOutgoingSession (shared_from_this ())) + { + m_Server.RemoveSession (GetConnID ()); + // connect + LogPrint (eLogDebug, "SSU2: Connecting after introduction to ", GetIdentHashBase64()); + Connect (); + } + else + { + LogPrint (eLogError, "SSU2: Session ", GetConnID (), " is already pending"); + m_Server.RequestRemoveSession (GetConnID ()); + } } } @@ -227,11 +250,9 @@ namespace transport RAND_bytes ((uint8_t *)&nonce, 4); auto ts = i2p::util::GetMillisecondsSinceEpoch (); // session for message 5 - auto session = std::make_shared (m_Server); - session->SetState (eSSU2SessionStatePeerTest); - m_PeerTests.emplace (nonce, std::make_pair (session, ts/1000)); - session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_DestConnID = ~session->m_SourceConnID; + auto session = std::make_shared (m_Server, + htobe64 (((uint64_t)nonce << 32) | nonce), 0); + m_Server.AddRequestedPeerTest (nonce, session, ts/1000); m_Server.AddSession (session); // peer test block auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); @@ -252,7 +273,7 @@ namespace transport { uint8_t payload[20]; size_t payloadSize = CreatePaddingBlock (payload, 20, 8); - SendData (payload, payloadSize); + SendData (payload, payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); } } @@ -265,6 +286,7 @@ namespace transport m_OnEstablished = nullptr; if (m_RelayTag) m_Server.RemoveRelay (m_RelayTag); + m_Server.AddConnectedRecently (m_RemoteEndpoint, GetLastActivityTimestamp ()); m_SentHandshakePacket.reset (nullptr); m_SessionConfirmedFragment.reset (nullptr); m_PathChallenge.reset (nullptr); @@ -275,20 +297,15 @@ namespace transport m_SentPackets.clear (); m_IncompleteMessages.clear (); m_RelaySessions.clear (); - m_PeerTests.clear (); m_ReceivedI2NPMsgIDs.clear (); m_Server.RemoveSession (m_SourceConnID); transports.PeerDisconnected (shared_from_this ()); auto remoteIdentity = GetRemoteIdentity (); if (remoteIdentity) - { LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), " (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated"); - } else - { LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), " terminated"); - } } } @@ -587,11 +604,7 @@ namespace transport if (!resentPackets.empty ()) { m_LastResendTime = ts; -#if (__cplusplus >= 201703L) // C++ 17 or higher m_SentPackets.merge (resentPackets); -#else - m_SentPackets.insert (resentPackets.begin (), resentPackets.end ()); -#endif m_WindowSize >>= 1; // /2 if (m_WindowSize < SSU2_MIN_WINDOW_SIZE) m_WindowSize = SSU2_MIN_WINDOW_SIZE; return resentPackets.size (); @@ -1157,7 +1170,7 @@ namespace transport if (profile) // older router? profile->Duplicated (); // mark router as duplicated in profile else - LogPrint (eLogError, "SSU2: Host mismatch between published address ", m_Address->host, + 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; } @@ -1419,83 +1432,17 @@ namespace transport return false; } HandlePayload (payload, len - 48); + m_IsDataReceived = false; // connect to Charlie ConnectAfterIntroduction (); return true; } - void SSU2Session::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey) - { - Header header; - uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; - // fill packet - header.h.connID = m_DestConnID; // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2PeerTest; - header.h.flags[0] = 2; // ver - header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID - header.h.flags[2] = 0; // flag - memcpy (h, header.buf, 16); - memcpy (h + 16, &m_SourceConnID, 8); // source id - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - size_t payloadSize = 7; - if (msg == 6 || msg == 7) - payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, - msg, eSSU2PeerTestCodeAccept, nullptr, signedData, signedDataLen); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // encrypt - uint8_t n[12]; - CreateNonce (be32toh (header.h.packetNum), n); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); - payloadSize += 16; - header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); - memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); - // send - m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); - } - bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len) { - // we are Alice or Charlie - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); - header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); - if (header.h.type != eSSU2PeerTest) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); - return false; - } - if (len < 48) - { - LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); - return false; - } - 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_DestConnID = headerX[0]; - // decrypt and handle payload - uint8_t * payload = buf + 32; - CreateNonce (be32toh (header.h.packetNum), nonce); - uint8_t h[32]; - memcpy (h, header.buf, 16); - memcpy (h + 16, &headerX, 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, - i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) - { - LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); - return false; - } - HandlePayload (payload, len - 48); - return true; + LogPrint (eLogWarning, "SSU2: Unexpected peer test message for this session type"); + return false; } uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags) @@ -1561,6 +1508,7 @@ namespace transport return; } UpdateNumReceivedBytes (len); + if (header.h.flags[0] & SSU2_FLAG_IMMEDIATE_ACK_REQUESTED) m_IsDataReceived = true; if (!packetNum || UpdateReceivePacketNum (packetNum)) HandlePayload (payload, payloadSize); } @@ -1636,14 +1584,17 @@ namespace transport case eSSU2BlkRelayRequest: LogPrint (eLogDebug, "SSU2: RelayRequest"); HandleRelayRequest (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkRelayResponse: LogPrint (eLogDebug, "SSU2: RelayResponse"); HandleRelayResponse (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkRelayIntro: LogPrint (eLogDebug, "SSU2: RelayIntro"); HandleRelayIntro (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkPeerTest: LogPrint (eLogDebug, "SSU2: PeerTest msg=", (int)buf[offset], " code=", (int)buf[offset+1]); @@ -1754,28 +1705,34 @@ namespace transport void SSU2Session::HandleRouterInfo (const uint8_t * buf, size_t len) { - auto ri = ExtractRouterInfo (buf, len); - if (ri) + if (len < 2) return; + // not from SessionConfirmed, we must add it instantly to use in next block + std::shared_ptr newRi; + if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) // compressed? { - // not from SessionConfirmed, we must add it instantly to use in next block - auto newRi = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // TODO: add ri - if (newRi) + auto ri = ExtractRouterInfo (buf, len); + if (ri) + newRi = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); + } + else // use buffer directly. TODO: handle frag + newRi = i2p::data::netdb.AddRouterInfo (buf + 2, len - 2); + + if (newRi) + { + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) { - auto remoteIdentity = GetRemoteIdentity (); - if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) + // peer's RouterInfo update + SetRemoteIdentity (newRi->GetIdentity ()); + auto address = m_RemoteEndpoint.address ().is_v6 () ? newRi->GetSSU2V6Address () : newRi->GetSSU2V4Address (); + if (address) { - // peer's RouterInfo update - SetRemoteIdentity (newRi->GetIdentity ()); - auto address = m_RemoteEndpoint.address ().is_v6 () ? newRi->GetSSU2V6Address () : newRi->GetSSU2V4Address (); - if (address) - { - m_Address = address; - if (IsOutgoing () && m_RelayTag && !address->IsIntroducer ()) - m_RelayTag = 0; // not longer introducer - } + m_Address = address; + if (IsOutgoing () && m_RelayTag && !address->IsIntroducer ()) + m_RelayTag = 0; // not longer introducer } } - } + } } void SSU2Session::HandleAck (const uint8_t * buf, size_t len) @@ -2007,27 +1964,32 @@ namespace transport SendData (payload, payloadSize); return; } + auto mts = i2p::util::GetMillisecondsSinceEpoch (); session->m_RelaySessions.emplace (bufbe32toh (buf + 1), // nonce - std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()) ); + std::make_pair (shared_from_this (), mts/1000) ); // send relay intro to Charlie auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - payloadSize += CreateRelayIntroBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, buf + 1, len -1); - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - session->SendData (payload, payloadSize); + 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); + packet->sendTime = mts; + // Charlie always responds with RelayResponse + session->m_SentPackets.emplace (packetNum, packet); } void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) { // we are Charlie + auto mts = i2p::util::GetMillisecondsSinceEpoch (); SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; uint64_t token = 0; bool isV4 = false; @@ -2047,7 +2009,9 @@ namespace transport boost::asio::ip::udp::endpoint ep; if (ExtractEndpoint (buf + 47, asz, ep)) { - auto addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + std::shared_ptr addr; + if (!ep.address ().is_unspecified () && ep.port ()) + addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); if (addr) { if (m_Server.IsSupported (ep.address ())) @@ -2055,6 +2019,7 @@ namespace transport token = m_Server.GetIncomingToken (ep); isV4 = ep.address ().is_v4 (); SendHolePunch (bufbe32toh (buf + 33), ep, addr->i, token); + m_Server.AddConnectedRecently (ep, mts/1000); } else { @@ -2099,11 +2064,15 @@ namespace transport code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; } // send relay response to Bob - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreateRelayResponseBlock (packet->payload, m_MaxPayloadSize, code, bufbe32toh (buf + 33), token, isV4); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + /*uint32_t packetNum = */SendData (packet->payload, packet->payloadSize); + // sometimes Bob doesn't ack this RelayResponse + // TODO: uncomment line below once the problem is resolved + //packet->sendTime = mts; + //m_SentPackets.emplace (packetNum, packet); } void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) @@ -2131,13 +2100,18 @@ namespace transport if (it->second.first && it->second.first->IsEstablished ()) { // we are Bob, message from Charlie - uint8_t payload[SSU2_MAX_PACKET_SIZE]; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayResponse; htobe16buf (payload + 1, len); memcpy (payload + 3, buf, len); // forward to Alice as is - size_t payloadSize = len + 3; - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - it->second.first->SendData (payload, payloadSize); + 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); + // sometimes Alice doesn't ack this RelayResponse + // TODO: uncomment line below once the problem is resolved + //packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); + //it->second.first->m_SentPackets.emplace (packetNum, packet); } else { @@ -2177,18 +2151,19 @@ namespace transport } else { - LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1]); + LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); it->second.first->Done (); } } m_RelaySessions.erase (it); } else - LogPrint (eLogWarning, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); + LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); } void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len) { + // msgs 1-4 if (len < 3) return; uint8_t msg = buf[0]; size_t offset = 3; // points to signed data @@ -2204,7 +2179,7 @@ namespace transport GetRemoteIdentity ()->GetIdentHash ()); if (session) // session with Charlie { - session->m_PeerTests.emplace (nonce, std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ())); + m_Server.AddPeerTest (nonce, shared_from_this (), ts/1000); auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Alice's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); @@ -2231,11 +2206,14 @@ namespace transport else { // Charlie not found, send error back to Alice - uint8_t payload[SSU2_MAX_PACKET_SIZE], zeroHash[32] = {0}; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 4, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t zeroHash[32] = {0}; + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 4, eSSU2PeerTestCodeBobNoCharlieAvailable, zeroHash, buf + offset, len - offset); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); } break; } @@ -2269,19 +2247,22 @@ namespace transport { boost::asio::ip::udp::endpoint ep; std::shared_ptr addr; - if (ExtractEndpoint (buf + offset + 10, asz, ep)) + if (ExtractEndpoint (buf + offset + 10, asz, ep) && !ep.address ().is_unspecified () && ep.port ()) addr = r->GetSSU2Address (ep.address ().is_v4 ()); if (addr && m_Server.IsSupported (ep.address ()) && i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ())) { - // send msg 5 to Alice - auto session = std::make_shared (m_Server, r, addr); - session->SetState (eSSU2SessionStatePeerTest); - session->m_RemoteEndpoint = ep; // might be different - session->m_DestConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_SourceConnID = ~session->m_DestConnID; - m_Server.AddSession (session); - session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr->i); + if (!m_Server.IsConnectedRecently (ep)) // no alive hole punch + { + // send msg 5 to Alice + auto session = std::make_shared (m_Server, + 0, htobe64 (((uint64_t)nonce << 32) | nonce)); + session->m_RemoteEndpoint = ep; // might be different + m_Server.AddSession (session); + session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr); + } + else + code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; } else code = eSSU2PeerTestCodeCharlieUnsupportedAddress; @@ -2298,54 +2279,59 @@ namespace transport else code = eSSU2PeerTestCodeCharlieAliceIsUnknown; // send msg 3 back to Bob - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 3, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 3, code, nullptr, newSignedData.data (), newSignedData.size ()); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); break; } case 3: // Bob from Charlie { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end () && it->second.first) - { - uint8_t payload[SSU2_MAX_PACKET_SIZE]; + auto aliceSession = m_Server.GetPeerTest (nonce); + if (aliceSession && aliceSession->IsEstablished ()) + { + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Charlie's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) - it->second.first->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (payloadSize + len + 16 > m_MaxPayloadSize) + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) + aliceSession->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + if (packet->payloadSize + len + 16 > m_MaxPayloadSize) { // doesn't fit one message, send RouterInfo in separate message - it->second.first->SendData (payload, payloadSize); - payloadSize = 0; + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + packet = m_Server.GetSentPacketsPool ().AcquireShared (); } // PeerTest to Alice - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4, + packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize, 4, (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - it->second.first->SendData (payload, payloadSize); - m_PeerTests.erase (it); - } + if (packet->payloadSize < m_MaxPayloadSize) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 3 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 3 nonce ", nonce); break; } case 4: // Alice from Bob { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) + auto session = m_Server.GetRequestedPeerTest (nonce); + if (session) { if (buf[1] == eSSU2PeerTestCodeAccept) { if (GetRouterStatus () == eRouterStatusUnknown) SetTestingState (true); auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie - if (r && it->second.first) + if (r) { uint8_t asz = buf[offset + 9]; SignedData s; @@ -2355,26 +2341,33 @@ namespace transport s.Insert (buf + offset, asz + 10); // ver, nonce, ts, asz, Alice's endpoint if (s.Verify (r->GetIdentity (), buf + offset + asz + 10)) { - it->second.first->SetRemoteIdentity (r->GetIdentity ()); + session->SetRemoteIdentity (r->GetIdentity ()); auto addr = r->GetSSU2Address (m_Address->IsV4 ()); if (addr) { - it->second.first->m_Address = addr; - if (it->second.first->m_State == eSSU2SessionStatePeerTestReceived) + if (session->GetMsgNumReceived () >= 5) { - // msg 5 already received. send msg 6 - SetRouterStatus (eRouterStatusOK); - it->second.first->m_State = eSSU2SessionStatePeerTest; - it->second.first->SendPeerTest (6, buf + offset, len - offset, addr->i); + // msg 5 already received + if (session->GetMsgNumReceived () == 5) + { + if (!session->IsConnectedRecently ()) + SetRouterStatus (eRouterStatusOK); + // send msg 6 + session->SendPeerTest (6, buf + offset, len - offset, addr); + } + else + LogPrint (eLogWarning, "SSU2: PeerTest 4 received, but msg ", session->GetMsgNumReceived (), " already received"); } else { + session->m_Address = addr; if (GetTestingState ()) { SetTestingState (false); if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ()) { SetRouterStatus (eRouterStatusFirewalled); + session->SetStatusChanged (); if (m_Address->IsV4 ()) m_Server.RescheduleIntroducersUpdateTimer (); else @@ -2388,63 +2381,34 @@ namespace transport else { LogPrint (eLogWarning, "SSU2: Peer test 4 address not found"); - it->second.first->Done (); + session->Done (); } } else { LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed"); - it->second.first->Done (); + session->Done (); } } else { LogPrint (eLogWarning, "SSU2: Peer test 4 router not found"); - if (it->second.first) - it->second.first->Done (); + session->Done (); } } else { LogPrint (eLogInfo, "SSU2: Peer test 4 error code ", (int)buf[1], " from ", i2p::data::GetIdentHashAbbreviation (buf[1] < 64 ? GetRemoteIdentity ()->GetIdentHash () : i2p::data::IdentHash (buf + 3))); - if (GetTestingState ()) + if (GetTestingState () && GetRouterStatus () != eRouterStatusFirewalled) SetRouterStatus (eRouterStatusUnknown); - it->second.first->Done (); + session->Done (); } - m_PeerTests.erase (it); } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 4 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 4 nonce ", nonce); break; } - case 5: // Alice from Charlie 1 - if (htobe64 (((uint64_t)nonce << 32) | nonce) == m_SourceConnID) - { - if (m_Address) - { - SetRouterStatus (eRouterStatusOK); - SendPeerTest (6, buf + offset, len - offset, m_Address->i); - } - else - // we received msg 5 before msg 4 - m_State = eSSU2SessionStatePeerTestReceived; - } - else - LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", m_SourceConnID); - break; - case 6: // Charlie from Alice - if (m_Address) - SendPeerTest (7, buf + offset, len - offset, m_Address->i); - else - LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); - m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce)); - break; - case 7: // Alice from Charlie 2 - if (m_Address->IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2 - m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce)); - break; default: LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); } @@ -2933,22 +2897,18 @@ namespace transport i2p::data::GzipInflator inflator; uint8_t uncompressed[i2p::data::MAX_RI_BUFFER_SIZE]; size_t uncompressedSize = inflator.Inflate (buf + 2, size - 2, uncompressed, i2p::data::MAX_RI_BUFFER_SIZE); - if (uncompressedSize && uncompressedSize < i2p::data::MAX_RI_BUFFER_SIZE) + if (uncompressedSize && uncompressedSize <= i2p::data::MAX_RI_BUFFER_SIZE) ri = std::make_shared(uncompressed, uncompressedSize); else LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize); } - else + else if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 2) ri = std::make_shared(buf + 2, size - 2); + else + LogPrint (eLogInfo, "SSU2: RouterInfo is too long ", size); return ri; } - void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); - } - bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) { if (packetNum <= m_ReceivePacketNum) return false; // duplicate @@ -3108,16 +3068,6 @@ namespace transport else ++it; } - for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) - { - if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) - { - LogPrint (eLogWarning, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds, deleted"); - it = m_PeerTests.erase (it); - } - else - ++it; - } if (m_PathChallenge) RequestTermination (eSSU2TerminationReasonNormalClose); } @@ -3137,5 +3087,215 @@ namespace transport Resend (i2p::util::GetMillisecondsSinceEpoch ()); // than right time to resend } + SSU2PeerTestSession::SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID): + SSU2Session (server, nullptr, nullptr, false), + m_MsgNumReceived (0), m_NumResends (0),m_IsConnectedRecently (false), m_IsStatusChanged (false), + m_PeerTestResendTimer (server.GetService ()) + { + if (!sourceConnID) sourceConnID = ~destConnID; + if (!destConnID) destConnID = ~sourceConnID; + SetSourceConnID (sourceConnID); + SetDestConnID (destConnID); + SetState (eSSU2SessionStatePeerTest); + SetTerminationTimeout (SSU2_PEER_TEST_EXPIRATION_TIMEOUT); + } + + bool SSU2PeerTestSession::ProcessPeerTest (uint8_t * buf, size_t len) + { + // we are Alice or Charlie, msgs 5,6,7 + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); + header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); + if (header.h.type != eSSU2PeerTest) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); + return false; + } + if (len < 48) + { + LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); + return false; + } + uint8_t nonce[12] = {0}; + uint64_t headerX[2]; // sourceConnID, token + i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + SetDestConnID (headerX[0]); + // decrypt and handle payload + uint8_t * payload = buf + 32; + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t h[32]; + memcpy (h, header.buf, 16); + memcpy (h + 16, &headerX, 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); + return false; + } + HandlePayload (payload, len - 48); + SetIsDataReceived (false); + return true; + } + + void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len) + { + // msgs 5-7 + if (len < 8) return; + uint8_t msg = buf[0]; + if (msg <= m_MsgNumReceived) + { + LogPrint (eLogDebug, "SSU2: PeerTest msg num ", msg, " received after ", m_MsgNumReceived, ". Ignored"); + return; + } + size_t offset = 3; // points to signed data after msg + code + flag + uint32_t nonce = bufbe32toh (buf + offset + 1); // 1 - ver + switch (msg) // msg + { + case 5: // Alice from Charlie 1 + { + if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ()) + { + m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ()); + if (GetAddress ()) + { + if (!m_IsConnectedRecently) + SetRouterStatus (eRouterStatusOK); + else if (m_IsStatusChanged && GetRouterStatus () == eRouterStatusFirewalled) + SetRouterStatus (eRouterStatusUnknown); + SendPeerTest (6, buf + offset, len - offset); + } + } + else + LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", GetSourceConnID ()); + break; + } + case 6: // Charlie from Alice + { + m_PeerTestResendTimer.cancel (); // no more msg 5 resends + if (GetAddress ()) + SendPeerTest (7, buf + offset, len - offset); + else + LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); + GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); + GetServer ().RequestRemoveSession (GetConnID ()); + break; + } + case 7: // Alice from Charlie 2 + { + m_PeerTestResendTimer.cancel (); // no more msg 6 resends + auto addr = GetAddress (); + if (addr && addr->IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2 + GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); + GetServer ().RequestRemoveSession (GetConnID ()); + break; + } + default: + LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", msg); + return; + } + m_MsgNumReceived = msg; + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg) + { + auto addr = GetAddress (); + if (!addr) return; + Header header; + uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; + // fill packet + header.h.connID = GetDestConnID (); // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2PeerTest; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (h, header.buf, 16); + htobuf64 (h + 16, GetSourceConnID ()); // source id + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + size_t payloadSize = 7; + if (msg == 6 || msg == 7) + payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, GetRemoteEndpoint ()); + payloadSize += CreatePeerTestBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, + msg, eSSU2PeerTestCodeAccept, nullptr, m_SignedData.data (), m_SignedData.size ()); + payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize); + // encrypt + uint8_t n[12]; + CreateNonce (be32toh (header.h.packetNum), n); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true); + payloadSize += 16; + 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); + // send + GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen) + { +#if __cplusplus >= 202002L // C++20 + m_SignedData.assign (signedData, signedData + signedDataLen); +#else + m_SignedData.resize (signedDataLen); + memcpy (m_SignedData.data (), signedData, signedDataLen); +#endif + SendPeerTest (msg); + // schedule resend for msgs 5 or 6 + if (msg == 5 || msg == 6) + ScheduleResend (); + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, + std::shared_ptr addr) + { + if (!addr) return; + SetAddress (addr); + SendPeerTest (msg, signedData, signedDataLen); + } + + void SSU2PeerTestSession::Connect () + { + LogPrint (eLogError, "SSU2: Can't connect peer test session"); + } + + bool SSU2PeerTestSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) + { + LogPrint (eLogError, "SSU2: Can't handle incoming message in peer test session"); + return false; + } + + void SSU2PeerTestSession::ScheduleResend () + { + if (m_NumResends < SSU2_PEER_TEST_MAX_NUM_RESENDS) + { + m_PeerTestResendTimer.expires_from_now (boost::posix_time::milliseconds( + SSU2_PEER_TEST_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE)); + std::weak_ptr s(std::static_pointer_cast(shared_from_this ())); + m_PeerTestResendTimer.async_wait ([s](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto s1 = s.lock (); + if (s1) + { + int msg = 0; + if (s1->m_MsgNumReceived < 6) + msg = (s1->m_MsgNumReceived == 5) ? 6 : 5; + if (msg) // 5 or 6 + { + s1->SendPeerTest (msg); + s1->ScheduleResend (); + } + } + } + }); + m_NumResends++; + } + } } } diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index cd6793d3..49bd3be6 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -113,7 +113,6 @@ namespace transport eSSU2SessionStateFailed, eSSU2SessionStateIntroduced, eSSU2SessionStatePeerTest, - eSSU2SessionStatePeerTestReceived, // 5 before 4 eSSU2SessionStateTokenRequestReceived }; @@ -206,36 +205,40 @@ namespace transport class SSU2Server; class SSU2Session: public TransportSession, public std::enable_shared_from_this { - union Header - { - uint64_t ll[2]; - uint8_t buf[16]; - struct + protected: + + union Header { - uint64_t connID; - uint32_t packetNum; - uint8_t type; - uint8_t flags[3]; - } h; - }; + uint64_t ll[2]; + uint8_t buf[16]; + struct + { + uint64_t connID; + uint32_t packetNum; + uint8_t type; + uint8_t flags[3]; + } h; + }; - struct HandshakePacket - { - Header header; - uint8_t headerX[48]; // part1 for SessionConfirmed - uint8_t payload[SSU2_MAX_PACKET_SIZE*2]; - size_t payloadSize = 0; - uint64_t sendTime = 0; // in milliseconds - bool isSecondFragment = false; // for SessionConfirmed - }; + private: + + struct HandshakePacket + { + Header header; + uint8_t headerX[48]; // part1 for SessionConfirmed + uint8_t payload[SSU2_MAX_PACKET_SIZE*2]; + size_t payloadSize = 0; + uint64_t sendTime = 0; // in milliseconds + bool isSecondFragment = false; // for SessionConfirmed + }; - typedef std::function OnEstablished; + typedef std::function OnEstablished; public: SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter = nullptr, - std::shared_ptr addr = nullptr); - ~SSU2Session (); + std::shared_ptr addr = nullptr, bool noise = true); + virtual ~SSU2Session (); void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; }; const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; }; @@ -245,7 +248,7 @@ namespace transport void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; }; OnEstablished GetOnEstablished () const { return m_OnEstablished; }; - void Connect (); + virtual void Connect (); bool Introduce (std::shared_ptr session, uint32_t relayTag); void WaitForIntroduction (); void SendPeerTest (); // Alice, Data message @@ -265,14 +268,34 @@ namespace transport SSU2SessionState GetState () const { return m_State; }; void SetState (SSU2SessionState state) { m_State = state; }; - bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len); + virtual bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len); bool ProcessSessionCreated (uint8_t * buf, size_t len); bool ProcessSessionConfirmed (uint8_t * buf, size_t len); bool ProcessRetry (uint8_t * buf, size_t len); bool ProcessHolePunch (uint8_t * buf, size_t len); - bool ProcessPeerTest (uint8_t * buf, size_t len); + virtual bool ProcessPeerTest (uint8_t * buf, size_t len); void ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + protected: + + SSU2Server& GetServer () { return m_Server; } + RouterStatus GetRouterStatus () const; + void SetRouterStatus (RouterStatus status) const; + size_t GetMaxPayloadSize () const { return m_MaxPayloadSize; } + void SetIsDataReceived (bool dataReceived) { m_IsDataReceived = dataReceived; }; + + uint64_t GetSourceConnID () const { return m_SourceConnID; } + void SetSourceConnID (uint64_t sourceConnID) { m_SourceConnID = sourceConnID; } + uint64_t GetDestConnID () const { return m_DestConnID; } + void SetDestConnID (uint64_t destConnID) { m_DestConnID = destConnID; } + + void SetAddress (std::shared_ptr addr) { m_Address = addr; } + void HandlePayload (const uint8_t * buf, size_t len); + + size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); + size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + private: void Terminate (); @@ -298,11 +321,9 @@ namespace transport void SendQuickAck (); void SendTermination (); void SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey, uint64_t token); - void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message void SendPathResponse (const uint8_t * data, size_t len); void SendPathChallenge (); - void HandlePayload (const uint8_t * buf, size_t len); void HandleDateTime (const uint8_t * buf, size_t len); void HandleRouterInfo (const uint8_t * buf, size_t len); void HandleAck (const uint8_t * buf, size_t len); @@ -312,35 +333,29 @@ namespace transport size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); std::shared_ptr FindLocalAddress () const; void AdjustMaxPayloadSize (); - RouterStatus GetRouterStatus () const; - void SetRouterStatus (RouterStatus status) const; bool GetTestingState () const; void SetTestingState(bool testing) const; std::shared_ptr ExtractRouterInfo (const uint8_t * buf, size_t size); - void CreateNonce (uint64_t seqn, uint8_t * nonce); bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate void HandleFirstFragment (const uint8_t * buf, size_t len); void HandleFollowOnFragment (const uint8_t * buf, size_t len); void HandleRelayRequest (const uint8_t * buf, size_t len); void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0); void HandleRelayResponse (const uint8_t * buf, size_t len); - void HandlePeerTest (const uint8_t * buf, size_t len); + virtual void HandlePeerTest (const uint8_t * buf, size_t len); void HandleI2NPMsg (std::shared_ptr&& msg); - size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r); size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr riBuffer); size_t CreateAckBlock (uint8_t * buf, size_t len); - size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg); size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg); size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID); size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen); size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4); - size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice size_t CreateTerminationBlock (uint8_t * buf, size_t len); - + private: SSU2Server& m_Server; @@ -358,8 +373,7 @@ namespace transport std::set m_OutOfSequencePackets; // packet nums > receive packet num std::map > m_SentPackets; // packetNum -> packet std::unordered_map > m_IncompleteMessages; // msgID -> I2NP - std::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice - std::map, uint64_t > > m_PeerTests; // same as for relay sessions + std::unordered_map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice std::list > m_SendQueue; i2p::I2NPMessagesHandler m_Handler; bool m_IsDataReceived; @@ -377,6 +391,43 @@ namespace transport uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds }; + + const int SSU2_PEER_TEST_RESEND_INTERVAL = 3000; // in milliseconds + const int SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE = 2000; // in milliseconds + const int SSU2_PEER_TEST_MAX_NUM_RESENDS = 3; + + class SSU2PeerTestSession: public SSU2Session // for PeerTest msgs 5,6,7 + { + public: + + SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID); + + uint8_t GetMsgNumReceived () const { return m_MsgNumReceived; } + bool IsConnectedRecently () const { return m_IsConnectedRecently; } + void SetStatusChanged () { m_IsStatusChanged = true; } + + void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, + std::shared_ptr addr); + bool ProcessPeerTest (uint8_t * buf, size_t len) override; + void Connect () override; // outgoing + bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // incoming + + private: + + void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen); // PeerTest message + void SendPeerTest (uint8_t msg); // send or resend m_SignedData + void HandlePeerTest (const uint8_t * buf, size_t len) override; + + void ScheduleResend (); + + private: + + uint8_t m_MsgNumReceived, m_NumResends; + bool m_IsConnectedRecently, m_IsStatusChanged; + std::vector m_SignedData; // for resends + boost::asio::deadline_timer m_PeerTestResendTimer; + }; + inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) { uint64_t data = 0; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index d6f0441c..c30c5d39 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -68,18 +68,19 @@ namespace stream Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), - m_SendStreamID (0), m_SequenceNumber (0), + 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_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (true), - m_IsTimeOutResend (false), m_LocalDestination (local), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (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_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), + 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_PrevRTT (INITIAL_RTT), m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_DropWindowDelayTime (0), m_LastSendTime (0), + m_Jitter (0), m_MinPacingTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { @@ -95,18 +96,18 @@ namespace stream } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): - m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), + 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_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (true), - m_IsTimeOutResend (false), m_LocalDestination (local), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (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_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowIncCounter (0), + 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_PrevRTT (INITIAL_RTT), m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_DropWindowDelayTime (0), m_LastSendTime (0), + m_PrevRTTSample (INITIAL_RTT), m_Jitter (0), m_MinPacingTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { @@ -183,10 +184,29 @@ namespace stream ProcessAck (packet); int32_t receivedSeqn = packet->GetSeqn (); - if (!receivedSeqn && !packet->GetFlags ()) + if (!receivedSeqn && m_LastReceivedSequenceNumber >= 0) { - // plain ack - LogPrint (eLogDebug, "Streaming: Plain ACK received"); + uint16_t flags = packet->GetFlags (); + if (flags) + // plain ack with options + ProcessOptions (flags, packet); + else + // plain ack + { + LogPrint (eLogDebug, "Streaming: Plain ACK received"); + if (m_IsImmediateAckRequested) + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (m_IsFirstRttSample) + { + m_RTT = ts - m_LastSendTime; + m_IsFirstRttSample = false; + } + else + m_RTT = (m_RTT + (ts - m_LastSendTime)) / 2; + m_IsImmediateAckRequested = false; + } + } m_LocalDestination.DeletePacket (packet); return; } @@ -325,12 +345,16 @@ namespace stream LogPrint (eLogInfo, "Streaming: Invalid option size ", optionSize, " Discarded"); return false; } + if (!flags) return true; + bool immediateAckRequested = false; if (flags & PACKET_FLAG_DELAY_REQUESTED) { - if (!m_IsAckSendScheduled) + uint16_t delayRequested = bufbe16toh (optionData); + if (!delayRequested) // 0 requests an immediate ack + immediateAckRequested = true; + else if (!m_IsAckSendScheduled) { - uint16_t delayRequested = bufbe16toh (optionData); - if (delayRequested > 0 && delayRequested < m_RTT) + if (delayRequested < m_RTT) { m_IsAckSendScheduled = true; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); @@ -339,8 +363,15 @@ namespace stream } if (delayRequested >= DELAY_CHOKING) { - m_WindowSize = 1; - m_WindowIncCounter = 0; + if (!m_IsWinDropped) + { + m_WindowDropTargetSize = MIN_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsWinDropped = true; // don't drop window twice + m_DropWindowDelaySequenceNumber = m_SequenceNumber; + UpdatePacingTime (); + } } } optionData += 2; @@ -424,6 +455,8 @@ namespace stream return false; } } + if (immediateAckRequested) + SendQuickAck (); return true; } @@ -503,58 +536,47 @@ namespace stream m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; if (m_WindowSize < MAX_WINDOW_SIZE && !m_IsFirstACK) - m_WindowIncCounter++; + if (m_RTT < m_LocalDestination.GetRandom () % INITIAL_RTT) // dirty + m_WindowIncCounter++; } else break; } if (rttSample != INT_MAX) { - if (m_IsFirstRttSample) + if (m_IsFirstRttSample && !m_IsFirstACK) { m_RTT = rttSample; m_SlowRTT = rttSample; + m_SlowRTT2 = rttSample; m_PrevRTTSample = rttSample; - if (m_RoutingSession) - m_RoutingSession->SetSharedRoutingPath ( - std::make_shared ( - i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0})); + m_Jitter = rttSample / 10; // 10% + m_Jitter += 5; // for low-latency connections m_IsFirstRttSample = false; } else - m_RTT = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * rttSample; - // calculate jitter - int jitter = 0; - if (rttSample > m_PrevRTTSample) - jitter = rttSample - m_PrevRTTSample; - else if (rttSample < m_PrevRTTSample) - jitter = m_PrevRTTSample - rttSample; - else - jitter = std::round (rttSample / 10); // 10% - jitter += 5; // for low-latency connections - m_Jitter = std::round (RTT_EWMA_ALPHA * jitter + (1.0 - RTT_EWMA_ALPHA) * m_Jitter); - m_PrevRTTSample = rttSample; - // - // delay-based CC - if ((m_PrevRTT > m_SlowRTT + m_Jitter) && (m_RTT > m_SlowRTT + m_Jitter) && !m_IsWinDropped) // Drop window if RTT grows too fast, late detection + m_RTT = (m_PrevRTTSample + rttSample) / 2; + if (!m_IsWinDropped) { - if (m_LastWindowDropSize) - m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize) / 2; + m_SlowRTT = SLOWRTT_EWMA_ALPHA * m_RTT + (1.0 - SLOWRTT_EWMA_ALPHA) * m_SlowRTT; + m_SlowRTT2 = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT2; + // calculate jitter + double jitter = 0; + if (rttSample > m_PrevRTTSample) + jitter = rttSample - m_PrevRTTSample; + else if (rttSample < m_PrevRTTSample) + jitter = m_PrevRTTSample - rttSample; else - m_LastWindowDropSize = m_WindowSize; - m_WindowSize = m_WindowSize / 2; // /2 - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - m_WindowIncCounter = 0; - m_DropWindowDelayTime = ts + m_SlowRTT; - m_IsFirstACK = true; - m_IsWinDropped = true; // don't drop window twice + jitter = rttSample / 10; // 10% + jitter += 5; // for low-latency connections + m_Jitter = (0.05 * jitter) + (1.0 - 0.05) * m_Jitter; } + // + // 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 + ProcessWindowDrop (); UpdatePacingTime (); - if (rttSample < m_RTT) // need for delay-based CC - m_SlowRTT = RTT_EWMA_ALPHA * rttSample + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT; - else - m_SlowRTT = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT; - m_PrevRTT = m_RTT; + 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 @@ -562,26 +584,52 @@ namespace stream if (wasInitial) ScheduleResend (); } - if ( ts > m_DropWindowDelayTime) + if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) + { + m_IsFirstRttSample = true; m_IsWinDropped = false; + } + if (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) { ScheduleResend (); } - if ((m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss - || int(m_SentPackets.size ()) > m_WindowSize) // or we drop window + 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 } if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) { m_ResendTimer.cancel (); m_SendTimer.cancel (); } + if (acknowledged && m_IsFirstACK) + { + if (m_RoutingSession) + m_RoutingSession->SetSharedRoutingPath ( + std::make_shared ( + i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0})); + m_IsFirstACK = false; + } if (acknowledged) { m_NumResendAttempts = 0; - m_IsFirstACK = false; + m_IsTimeOutResend = false; SendBuffer (); } if (m_Status == eStreamStatusClosed) @@ -781,13 +829,22 @@ namespace stream // for limit inbound speed auto ts = i2p::util::GetMillisecondsSinceEpoch (); int numPackets = 0; + bool lostPackets = false; int64_t passedTime = m_PacketACKInterval * INITIAL_WINDOW_SIZE; // in microseconds // while m_LastACKSendTime == 0 if (m_LastACKSendTime) passedTime = (ts - m_LastACKSendTime)*1000; // in microseconds numPackets = (passedTime + m_PacketACKIntervalRem) / m_PacketACKInterval; m_PacketACKIntervalRem = (passedTime + m_PacketACKIntervalRem) - (numPackets * m_PacketACKInterval); if (m_LastConfirmedReceivedSequenceNumber + numPackets < m_LastReceivedSequenceNumber) + { lastReceivedSeqn = m_LastConfirmedReceivedSequenceNumber + numPackets; + if (!m_IsAckSendScheduled) + { + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } + } if (numPackets == 0) return; // for limit inbound speed if (!m_SavedPackets.empty ()) @@ -795,8 +852,26 @@ namespace stream for (auto it: m_SavedPackets) { auto seqn = it->GetSeqn (); - if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) break; // for limit inbound speed - if ((int)seqn > lastReceivedSeqn) lastReceivedSeqn = seqn; + // for limit inbound speed + if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) + { + if (!m_IsAckSendScheduled) + { + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } + if (lostPackets) + break; + else + return; + } + // for limit inbound speed + if ((int)seqn > lastReceivedSeqn) + { + lastReceivedSeqn = seqn; + lostPackets = true; // for limit inbound speed + } } } if (lastReceivedSeqn < 0) @@ -858,13 +933,22 @@ namespace stream } packet[size] = 0; size++; // resend delay - htobuf16 (packet + size, choking ? PACKET_FLAG_DELAY_REQUESTED : 0); // no flags set or delay + bool requestImmediateAck = false; + if (!choking) + requestImmediateAck = m_LastSendTime && ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL && + ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL + m_LocalDestination.GetRandom () % REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE; + htobe16buf (packet + size, (choking || requestImmediateAck) ? PACKET_FLAG_DELAY_REQUESTED : 0); // no flags set or delay requested size += 2; // flags - if (choking) + if (choking || requestImmediateAck) { - htobuf16 (packet + size, 2); // 2 bytes delay interval - htobuf16 (packet + size + 2, DELAY_CHOKING); // set choking interval + htobe16buf (packet + size, 2); // 2 bytes delay interval + htobe16buf (packet + size + 2, choking ? DELAY_CHOKING : 0); // set choking or immediated ack interval size += 2; + if (requestImmediateAck) // ack request sent + { + m_LastSendTime = ts; + m_IsImmediateAckRequested = true; + } } else htobuf16 (packet + size, 0); // no options @@ -1135,45 +1219,37 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { - if (m_WindowIncCounter && m_WindowSize < MAX_WINDOW_SIZE) - { - if (m_LastWindowDropSize && (m_LastWindowDropSize > m_WindowSize)) - { - m_WindowSize += 2.001-(2/((m_LastWindowDropSize+(1/m_WindowSize))/m_WindowSize)); // some magic here - m_WindowIncCounter --; - } - else - { - m_WindowSize += 1; - m_WindowIncCounter --; - } - if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; - UpdatePacingTime (); - } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (m_LastSendTime && ts*1000 > m_LastSendTime*1000 + m_PacingTime) { m_NumPacketsToSend = ((ts*1000 - m_LastSendTime*1000) + m_PacingTimeRem) / m_PacingTime; m_PacingTimeRem = ((ts*1000 - m_LastSendTime*1000) + m_PacingTimeRem) - (m_NumPacketsToSend * m_PacingTime); m_IsSendTime = true; - if (m_IsNAcked || m_IsResendNeeded) // resend packets + if (m_WindowIncCounter && m_WindowSize < MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty ()) + { + 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 + 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 --; + UpdatePacingTime (); + } + } + } + if (m_IsNAcked) + ResendPacket (); + else if (m_IsResendNeeded) // resend packets ResendPacket (); // delay-based CC else if (!m_IsWinDropped && int(m_SentPackets.size ()) == m_WindowSize) // we sending packets too fast, early detection - { - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (m_LastWindowDropSize) - m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize) / 2; - else - m_LastWindowDropSize = m_WindowSize; - m_WindowSize = m_WindowSize / 2; // /2 - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - m_WindowIncCounter = 0; - m_DropWindowDelayTime = ts + m_SlowRTT; - m_IsFirstACK = true; - m_IsWinDropped = true; // don't drop window twice - UpdatePacingTime (); - } + ProcessWindowDrop (); else if (m_WindowSize > int(m_SentPackets.size ())) // send packets SendBuffer (); } @@ -1265,31 +1341,19 @@ namespace stream if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) { // loss-based CC - if (!m_IsWinDropped) - { - if (m_LastWindowDropSize) - m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize) / 2; - else - m_LastWindowDropSize = m_WindowSize; - m_WindowSize = m_WindowSize / 2; // /2 - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - m_WindowIncCounter = 0; - m_IsWinDropped = true; // don't drop window twice - m_DropWindowDelayTime = ts + m_SlowRTT; - m_IsFirstACK = true; - UpdatePacingTime (); - } + if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED) + ProcessWindowDrop (); } else if (m_IsTimeOutResend) { m_IsTimeOutResend = false; m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change - m_WindowSize = INITIAL_WINDOW_SIZE; + m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; m_LastWindowDropSize = 0; m_WindowIncCounter = 0; m_IsWinDropped = true; m_IsFirstRttSample = true; - m_DropWindowDelayTime = 0; + m_DropWindowDelaySequenceNumber = 0; m_IsFirstACK = true; UpdatePacingTime (); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); @@ -1359,6 +1423,7 @@ namespace stream void Stream::UpdateCurrentRemoteLease (bool expired) { + bool isLeaseChanged = true; if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); @@ -1416,10 +1481,15 @@ namespace stream } if (!updated) { - uint32_t i = rand () % leases.size (); + uint32_t i = m_LocalDestination.GetRandom () % leases.size (); if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) + { // make sure we don't select previous - i = (i + 1) % leases.size (); // if so, pick next + if (leases.size () > 1) + i = (i + 1) % leases.size (); // if so, pick next + else + isLeaseChanged = false; + } m_CurrentRemoteLease = leases[i]; } } @@ -1436,16 +1506,23 @@ namespace stream LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; } - // drop window to initial upon RemoteLease change - m_RTO = INITIAL_RTO; - m_WindowSize = INITIAL_WINDOW_SIZE; - m_LastWindowDropSize = 0; - m_WindowIncCounter = 0; - m_IsWinDropped = true; - m_IsFirstRttSample = true; - m_DropWindowDelayTime = 0; - m_IsFirstACK = true; - UpdatePacingTime (); + if (isLeaseChanged) + { + // drop window to initial upon RemoteLease change + m_RTO = INITIAL_RTO; + if (m_WindowSize > INITIAL_WINDOW_SIZE) + { + m_WindowDropTargetSize = std::max (m_WindowSize/2, (float)INITIAL_WINDOW_SIZE); + m_IsWinDropped = true; + } + else + m_WindowSize = INITIAL_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsFirstRttSample = true; + m_IsFirstACK = true; + UpdatePacingTime (); + } } void Stream::ResetRoutingPath () @@ -1464,10 +1541,29 @@ namespace stream 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; + 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; + m_WindowIncCounter = 0; // disable window growth + m_DropWindowDelaySequenceNumber = m_SequenceNumber; + m_IsFirstACK = true; // ignore first RTT sample + m_IsWinDropped = true; // don't drop window twice + UpdatePacingTime (); + } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), - m_PendingIncomingTimer (m_Owner->GetService ()) + m_PendingIncomingTimer (m_Owner->GetService ()), + m_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ()) { } @@ -1656,10 +1752,12 @@ namespace stream m_IncomingStreams.erase (stream->GetSendStreamID ()); if (m_LastStream == stream) m_LastStream = nullptr; } - if (m_Streams.empty ()) + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (m_Streams.empty () || ts > m_LastCleanupTime + STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL) { m_PacketsPool.CleanUp (); m_I2NPMsgsPool.CleanUp (); + m_LastCleanupTime = ts; } } @@ -1795,5 +1893,15 @@ namespace stream return msg; } + uint32_t StreamingDestination::GetRandom () + { + if (m_Owner) + { + auto pool = m_Owner->GetTunnelPool (); + if (pool) + return pool->GetRng ()(); + } + return rand (); + } } } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index a0229483..9ac84990 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -54,9 +54,11 @@ namespace stream const size_t COMPRESSION_THRESHOLD_SIZE = 66; const int MAX_NUM_RESEND_ATTEMPTS = 10; const int INITIAL_WINDOW_SIZE = 10; - const int MIN_WINDOW_SIZE = 1; - const int MAX_WINDOW_SIZE = 1024; - const double RTT_EWMA_ALPHA = 0.125; + const int MIN_WINDOW_SIZE = 2; + const int MAX_WINDOW_SIZE = 512; + 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 const int MIN_RTO = 20; // in milliseconds const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds @@ -68,7 +70,11 @@ namespace stream const int MAX_RECEIVE_TIMEOUT = 20; // in seconds const uint16_t DELAY_CHOKING = 60000; // in milliseconds const uint64_t SEND_INTERVAL = 1000; // in microseconds - + const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds + const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds + const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1 + const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds + struct Packet { size_t len, offset; @@ -241,11 +247,13 @@ namespace stream void HandleAckSendTimer (const boost::system::error_code& ecode); void UpdatePacingTime (); + void ProcessWindowDrop (); private: boost::asio::io_service& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; + uint32_t m_DropWindowDelaySequenceNumber; uint32_t m_TunnelsChangeSequenceNumber; int32_t m_LastReceivedSequenceNumber; int32_t m_PreviousReceivedSequenceNumber; @@ -259,6 +267,7 @@ namespace stream bool m_IsSendTime; bool m_IsWinDropped; bool m_IsTimeOutResend; + bool m_IsImmediateAckRequested; StreamingDestination& m_LocalDestination; std::shared_ptr m_RemoteIdentity; std::shared_ptr m_TransientVerifier; // in case of offline key @@ -275,10 +284,12 @@ namespace stream uint16_t m_Port; SendBufferQueue m_SendBuffer; - double m_RTT, m_SlowRTT; - float m_WindowSize, m_LastWindowDropSize; - int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_PrevRTT, m_Jitter; - uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, m_DropWindowDelayTime, m_LastSendTime; // microseconds + double m_RTT, m_SlowRTT, m_SlowRTT2; + float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize; + int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample; + double m_Jitter; + uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds + m_LastSendTime; // miliseconds uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed int m_NumResendAttempts, m_NumPacketsToSend; size_t m_MTU; @@ -316,6 +327,7 @@ namespace stream Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } + uint32_t GetRandom (); private: @@ -339,7 +351,8 @@ namespace stream i2p::util::MemoryPool m_PacketsPool; i2p::util::MemoryPool > m_I2NPMsgsPool; - + uint64_t m_LastCleanupTime; // in seconds + public: i2p::data::GzipInflator m_Inflator; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index cf30b428..549efb63 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -599,7 +599,7 @@ namespace transport } LogPrint (eLogInfo, "Transports: No compatible addresses available"); - if (peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) + if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them peer->Done (); std::unique_lock l(m_PeersMutex); diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 55c4d38c..1b63b7a7 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -411,10 +411,12 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, - int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance) + std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, + int numOutboundHops, int numInboundTunnels, int numOutboundTunnels, + int inboundVariance, int outboundVariance, bool isHighBandwidth) { - auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance); + auto pool = std::make_shared (numInboundHops, numOutboundHops, + numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -705,7 +707,7 @@ namespace tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); // reachable by us + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); CreateTunnel ( @@ -765,7 +767,7 @@ namespace tunnel int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen); int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum); int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum); - m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0); + m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0, false); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; @@ -777,7 +779,7 @@ namespace tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : // should be reachable by us because we send build request directly - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); if (!router) { LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); return; diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index d3de272d..6b014af2 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -232,8 +232,9 @@ namespace tunnel void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); - std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, - int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance); + std::shared_ptr CreateTunnelPool (int numInboundHops, + int numOuboundHops, int numInboundTunnels, int numOutboundTunnels, + int inboundVariance, int outboundVariance, bool isHighBandwidth); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 0a855350..5af42373 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -41,11 +41,11 @@ namespace tunnel } TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance): + int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance), - m_IsActive (true), m_CustomPeerSelector(nullptr), + m_IsActive (true), m_IsHighBandwidth (isHighBandwidth), m_CustomPeerSelector(nullptr), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) { if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) @@ -549,20 +549,22 @@ namespace tunnel std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, bool reverse, bool endpoint) const { - bool tryHighBandwidth = !IsExploratory (); + bool tryClient = !IsExploratory () && !i2p::context.IsLimitedConnectivity (); std::shared_ptr hop; for (int i = 0; i < TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS; i++) { - hop = tryHighBandwidth ? - i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) : - i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint); + hop = tryClient ? + (m_IsHighBandwidth ? + i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) : + i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, true)): + i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); if (hop) { if (!hop->GetProfile ()->IsBad ()) break; } - else if (tryHighBandwidth) - tryHighBandwidth = false; + else if (tryClient) + tryClient = false; else return nullptr; } @@ -585,7 +587,7 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - auto r = i2p::transport::transports.GetRandomPeer (!IsExploratory ()); + auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 { diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index c19114e2..0ebfd1ac 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -62,7 +62,7 @@ namespace tunnel public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance); + int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -146,7 +146,7 @@ namespace tunnel std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; - bool m_IsActive; + bool m_IsActive, m_IsHighBandwidth; uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; diff --git a/libi2pd/util.h b/libi2pd/util.h index e2037212..e39a9259 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.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 * @@ -131,6 +131,14 @@ namespace util this->Release (t); } + void ReleaseMt (T * * arr, size_t num) + { + if (!arr || !num) return; + std::lock_guard l(m_Mutex); + for (size_t i = 0; i < num; i++) + this->Release (arr[i]); + } + templateclass C, typename... R> void ReleaseMt(const C& c) { @@ -138,7 +146,7 @@ namespace util for (auto& it: c) this->Release (it); } - + template std::shared_ptr AcquireSharedMt (TArgs&&... args) { diff --git a/libi2pd/version.h b/libi2pd/version.h index f3e487c2..40d07845 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -18,8 +18,8 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 53 -#define I2PD_VERSION_MICRO 1 +#define I2PD_VERSION_MINOR 54 +#define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #ifdef GITVER #define I2PD_VERSION XSTRINGIZE(GITVER) @@ -33,7 +33,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 63 +#define I2P_VERSION_MICRO 64 #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 8f2117a7..14599cf7 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -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 * @@ -15,7 +15,6 @@ #include #include #include -#include #include "Base.h" #include "util.h" #include "Identity.h" @@ -27,6 +26,14 @@ #include "AddressBook.h" #include "Config.h" +#if STD_FILESYSTEM +#include +namespace fs_lib = std::filesystem; +#else +#include +namespace fs_lib = boost::filesystem; +#endif + namespace i2p { namespace client @@ -266,11 +273,11 @@ namespace client void AddressBookFilesystemStorage::ResetEtags () { LogPrint (eLogError, "Addressbook: Resetting eTags"); - for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it) + for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) { - if (!boost::filesystem::is_regular_file (it->status ())) + if (!fs_lib::is_regular_file (it->status ())) continue; - boost::filesystem::remove (it->path ()); + fs_lib::remove (it->path ()); } } @@ -434,7 +441,7 @@ namespace client auto ident = std::make_shared(); if (ident->FromBase64 (jump)) { - m_Storage->AddAddress (ident); + if (m_Storage) m_Storage->AddAddress (ident); m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); } @@ -445,18 +452,19 @@ namespace client void AddressBook::InsertFullAddress (std::shared_ptr address) { - m_Storage->AddAddress (address); + if (m_Storage) m_Storage->AddAddress (address); } std::shared_ptr AddressBook::GetFullAddress (const std::string& address) { auto addr = GetAddress (address); if (!addr || !addr->IsIdentHash ()) return nullptr; - return m_Storage->GetAddress (addr->identHash); + return m_Storage ? m_Storage->GetAddress (addr->identHash) : nullptr; } void AddressBook::LoadHosts () { + if (!m_Storage) return; if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; @@ -527,15 +535,18 @@ namespace client ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); - m_Storage->AddAddress (ident); - m_Storage->RemoveAddress (it->second->identHash); + if (m_Storage) + { + m_Storage->AddAddress (ident); + m_Storage->RemoveAddress (it->second->identHash); + } LogPrint (eLogInfo, "Addressbook: Updated host: ", name); } } else { m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); - m_Storage->AddAddress (ident); + if (m_Storage) m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: Added new host: ", name); } @@ -547,7 +558,7 @@ namespace client if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; - m_Storage->Save (m_Addresses); + if (m_Storage) m_Storage->Save (m_Addresses); } return !incomplete; } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 9b2c7e7e..fc4f19a7 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.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 * @@ -99,7 +99,8 @@ namespace client std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); - + bool IsEnabled () const { return m_IsEnabled; } + private: void StartSubscriptions (); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 4f91c564..cf72d204 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -474,8 +474,9 @@ namespace client options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED); options[I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, DEFAULT_MAX_INBOUND_SPEED); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); + options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; @@ -519,6 +520,8 @@ namespace client options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ()) options[I2CP_PARAM_LEASESET_PRIV_KEY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_STREAMING_PROFILE, value)) + options[I2CP_PARAM_STREAMING_PROFILE] = value; } void ClientContext::ReadTunnels () @@ -663,7 +666,9 @@ namespace client // http proxy std::string outproxy = section.second.get("outproxy", ""); bool addresshelper = section.second.get("addresshelper", true); - auto tun = std::make_shared(name, address, port, outproxy, addresshelper, localDestination); + bool senduseragent = section.second.get("senduseragent", false); + auto tun = std::make_shared(name, address, port, + outproxy, addresshelper, senduseragent, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } @@ -879,6 +884,7 @@ namespace client uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + bool httpSendUserAgent; i2p::config::GetOption("httpproxy.senduseragent", httpSendUserAgent); if (httpAddresshelper) i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); @@ -898,7 +904,8 @@ namespace client } try { - m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); + m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, + httpOutProxyURL, httpAddresshelper, httpSendUserAgent, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 01678f4a..dba65815 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -59,7 +60,8 @@ namespace proxy { "\r\n" ; - bool str_rmatch(std::string & str, const char *suffix) { + static bool str_rmatch(std::string & str, const char *suffix) + { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ @@ -77,16 +79,16 @@ namespace proxy { void Terminate(); void AsyncSockRead(); static bool ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm); - static bool VerifyAddressHelper (const std::string& jump); - static void SanitizeHTTPRequest(i2p::http::HTTPReq& req); + static bool VerifyAddressHelper (std::string_view jump); + void SanitizeHTTPRequest(i2p::http::HTTPReq& req); 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(std::string& host); - void SendProxyError(std::string& content); - void SendRedirect(std::string& address); + void HostNotFound(const std::string& host); + void SendProxyError(const std::string& content); + void SendRedirect(const std::string& address); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); @@ -108,7 +110,7 @@ namespace proxy { std::shared_ptr m_proxysock; boost::asio::ip::tcp::resolver m_proxy_resolver; std::string m_OutproxyUrl, m_Response; - bool m_Addresshelper; + bool m_Addresshelper, m_SendUserAgent; i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; int m_req_len; @@ -124,7 +126,8 @@ namespace proxy { m_proxysock(std::make_shared(parent->GetService())), m_proxy_resolver(parent->GetService()), m_OutproxyUrl(parent->GetOutproxyURL()), - m_Addresshelper(parent->GetHelperSupport()) {} + m_Addresshelper(parent->GetHelperSupport()), + m_SendUserAgent (parent->GetSendUserAgent ()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; @@ -175,7 +178,8 @@ namespace proxy { SendProxyError(content); } - void HTTPReqHandler::HostNotFound(std::string& host) { + void HTTPReqHandler::HostNotFound(const std::string& host) + { std::stringstream ss; ss << "

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

\r\n" << "

" << tr("Remote host not found in router's addressbook") << "

\r\n" @@ -192,7 +196,7 @@ namespace proxy { SendProxyError(content); } - void HTTPReqHandler::SendProxyError(std::string& content) + void HTTPReqHandler::SendProxyError(const std::string& content) { i2p::http::HTTPRes res; res.code = 500; @@ -208,7 +212,7 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::SendRedirect(std::string& address) + void HTTPReqHandler::SendRedirect(const std::string& address) { i2p::http::HTTPRes res; res.code = 302; @@ -272,7 +276,7 @@ namespace proxy { return true; } - bool HTTPReqHandler::VerifyAddressHelper (const std::string& jump) + bool HTTPReqHandler::VerifyAddressHelper (std::string_view jump) { auto pos = jump.find(".b32.i2p"); if (pos != std::string::npos) @@ -312,7 +316,8 @@ namespace proxy { req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ - req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + if (!m_SendUserAgent) + req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /** * i2pd PR #1816: @@ -373,7 +378,7 @@ namespace proxy { std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { - if (!m_Addresshelper) + if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); @@ -441,7 +446,7 @@ namespace proxy { bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { - std::string uri(m_ClientRequest.uri); + const std::string& uri = m_ClientRequest.uri; auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { @@ -548,9 +553,9 @@ namespace proxy { std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? m_ClientRequest.uri = m_ClientRequestURL.to_string(); - /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections */ - if(m_ClientRequest.method != "CONNECT") - m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0"); + /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 13, for non-HTTPS connections */ + if(m_ClientRequest.method != "CONNECT" && !m_SendUserAgent) + m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0"); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); @@ -748,9 +753,10 @@ namespace proxy { Done (shared_from_this()); } - HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, + const std::string & outproxy, bool addresshelper, bool senduseragent, std::shared_ptr localDestination): TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), - m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper) + m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent) { } diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index d819a53c..507a87e2 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.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 * @@ -15,13 +15,15 @@ namespace proxy { { public: - HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination); + HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, + bool addresshelper, bool senduseragent, std::shared_ptr localDestination); HTTPProxy(const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination = nullptr) : - HTTPProxy(name, address, port, "", true, localDestination) {} ; + HTTPProxy(name, address, port, "", true, false, localDestination) {} ; ~HTTPProxy() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } - bool GetHelperSupport() { return m_Addresshelper; } + bool GetHelperSupport() const { return m_Addresshelper; } + bool GetSendUserAgent () const { return m_SendUserAgent; } protected: @@ -33,7 +35,7 @@ namespace proxy { std::string m_Name; std::string m_OutproxyUrl; - bool m_Addresshelper; + bool m_Addresshelper, m_SendUserAgent; }; } // http } // i2p diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 12d2d0c9..fc6d1b40 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -226,7 +226,8 @@ namespace client leases = remote->GetNonExpiredLeases (true); // with threshold if (!leases.empty ()) { - remoteLease = leases[rand () % leases.size ()]; + auto pool = GetTunnelPool (); + remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()]; auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway); outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr, leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 3174bdb3..0961690b 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -444,9 +444,7 @@ namespace proxy break; case CMD_UDP: if (m_socksv == SOCKS5) break; -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif default: LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); diff --git a/tests/Makefile b/tests/Makefile index 7c44e467..798fab42 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ SYS := $(shell $(CXX) -dumpmachine) -CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++17 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files INCFLAGS += -I../libi2pd LIBI2PD = ../libi2pd.a @@ -18,7 +18,7 @@ ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstrin endif LDLIBS = \ - -lboost_filesystem$(BOOST_SUFFIX) \ + -lboost_system$(BOOST_SUFFIX) \ -lboost_program_options$(BOOST_SUFFIX) \ -lssl \ -lcrypto \