diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ed3973c5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# editorconfig.org + +root = true + +[*] +# Unix style files +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile,Makefile.*] +indent_style = tab +indent_size = 4 + +[*.cmd] +indent_style = space +indent_size = 2 +end_of_line = crlf + +[*.{h,cpp}] +indent_style = tab +indent_size = 4 + +[*.rc] +indent_style = space +indent_size = 4 + +[*.{md,markdown}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index 50672d26..afddb7e9 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -14,6 +14,7 @@ jobs: - uses: actions/checkout@v2 - name: install packages run: | + find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete brew update brew install boost miniupnpc openssl@1.1 - name: build application diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 899a7eec..1f3142e4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -57,6 +57,7 @@ jobs: tags: | purplei2p/i2pd:latest-${{ matrix.archname }} ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} + provenance: false push: runs-on: ubuntu-latest @@ -90,51 +91,37 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Create and push latest manifest image to Docker Hub + if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: - base-image: purplei2p/i2pd:latest - extra-images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 + inputs: purplei2p/i2pd:latest + images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 push: true - name: Create and push latest manifest image to GHCR + if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: - base-image: ghcr.io/purplei2p/i2pd:latest - extra-images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 + inputs: ghcr.io/purplei2p/i2pd:latest + images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 push: true - name: Store release version to env if: ${{ startsWith(github.ref, 'refs/tags/') }} run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - - name: Create and push release manifest image to Docker Hub + - name: Create and push release manifest to Docker Hub if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: - base-image: purplei2p/i2pd:latest-release - extra-images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 + inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 push: true - - name: Create and push release manifest image to GHCR + - name: Create and push release manifest to GHCR if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: - base-image: ghcr.io/purplei2p/i2pd:latest-release - extra-images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push versioned manifest image to Docker Hub - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - base-image: purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - extra-images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push versioned manifest image to GHCR - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - base-image: ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - extra-images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 + inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 push: true diff --git a/ChangeLog b/ChangeLog index 9f3faf11..385a3545 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,88 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.46.1] - 2023-02-20 +### Fixed +- Race condition while getting router's peer profile +- Creation of new router.info +- Displaying LeaseSets in the webconsole +- Crash when processing ACK request + +## [2.46.0] - 2023-02-15 +### Added +- Limit number of acked SSU2 packets to 511 +- Localization to Swedish, Portuguese, Turkish, Polish +- Periodically send Datetime block in NTCP2 and SSU2 +- Don't select random port from reserved +- In memory table for peer profiles +- Store if router was unreachable in it's peer profile +- Show IPv6 addresses in square brackets in webconsole +- Check referer when processing Addresshelper +### Changed +- Algorithm for tunnel creation success rate calculation +- Drop incoming NTCP2 and SSU2 connection if published IP doesn't match actual endpoint +- Exclude actually unreachable router from netdb for 2 hours +- Select first hop from high bandwidth peers for client tunnels +- Drop too long or too short LeaseSet +- Delete router from netdb if became invalid after update +- Terminate existing session if clock skew detected +- Close previous UDP socket if open before reopening +- Minimal version for floodfill is 0.9.51 +- Sort transports by endpoints in webconsole +### Fixed +- Deadlock during processing I2NP block with Garlic in ECIES encrypted message to router +- Race condition with encrypted LeaseSets +- HTTP query detection +- Connection attempts to IPs from invalid ranges +- Publish "0.0.0.0" in RouterInfo +- Crash upon receiving PeerTest 7 +- Tunnels for closed SAM session socket +- Missing NTCP2 address in RouterInfo if enabled back + +## [2.45.1] - 2023-01-11 +### Added +- Full Cone NAT status error +### Changed +- Drop duplicated I2NP messages in SSU2 +- Set rejection code 30 if tunnel with id already exists +- Network status is always OK if peer test msg 5 received +### Fixed +- UPnP crash if SSU2 or NTCP2 is disabled +- Crash on termination for some platforms + +## [2.45.0] - 2023-01-03 +### Added +- Test for Symmetric NAT with peer test msgs 6 and 7 +- Webconsole "No Descriptors" router error state +- 1 and 15 seconds bandwidth calculation for i2pcontrol +- Show non-zero send queue size for transports in web console +- Compressible padding for I2P addresses +- Localization to Czech +- Don't accept incoming session from invalid/reserved addresses for NTCP2 and SSU2 +- Limit simultaneous tunnel build requests by 4 per pool +### Changed +- Removed SSU support +- Reduced bandwidth calculation interval from 60 to 15 seconds +- Increased default max transit tunnels number from 2500 to 5000 or 10000 for floodfill +- Transit tunnels limit is doubled if floodfill mode is enabled +- NTCP2 and SSU2 timestamps are rounded to seconds +- Drop RouterInfos and LeaseSets with timestamp from future +- Don't delete unreachable routers if tunnel creation success rate is too low +- Refuse duplicated incoming pending NTCP2 session from same IP +- Don't send SSU2 termination again if termination received block received +- Handle standard network error for SSU2 without throwing an exception +- Don't select overloaded peer for next tunnel +- Remove "X-Requested-With" in HTTP Proxy for non-AJAX requests +### Fixed +- File descriptors leak +- Random crash on AddressBook update +- Crash if incorrect LeaseSet size +- Spamming to log if no descriptors +- ::1 address in RouterInfo +- SSU2 network error handling (especially for Windows) +- Race condition with pending outgoing SSU2 sessions +- RTT self-reduction for long-live streams + ## [2.44.0] - 2022-11-20 ### Added - SSL connection for server I2P tunnels @@ -11,7 +93,7 @@ - SSU2 send and verify path challenge - Configurable ssu2.mtu4 and ssu2.mtu6 ### Changed -- SSU2 is enbaled and SSU is disabled by default +- SSU2 is enabled and SSU is disabled by default - Separate network status and error - Random selection between NTCP2 and SSU2 priority - Added notbob.i2p to jump services diff --git a/Makefile b/Makefile index d792360a..520a56ba 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,10 @@ else LD_DEBUG = -s endif +ifneq (, $(DESTDIR)) + PREFIX = $(DESTDIR) +endif + ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) @@ -114,9 +118,9 @@ obj/%.o: %.cpp | mk_obj_dir $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) +$(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif $(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) diff --git a/Makefile.linux b/Makefile.linux index 28334082..d01f2b73 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -20,7 +20,11 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 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} "[1,7-9]"),1) # gcc >= 7 +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 NEEDED_CXXFLAGS += -std=c++17 LDLIBS = -latomic else # not supported diff --git a/README.md b/README.md index 6f2d23ec..d0481ed9 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,12 @@ Build instructions: **Supported systems:** -* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) - * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) - * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. +* GNU/Linux (Debian, Ubuntu, etc) - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) +* CentOS, Fedora, Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) +* Alpine, ArchLinux, openSUSE, Gentoo, etc. * Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) -* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) -* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) +* Mac OS - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) +* Docker image - [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) * Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) * FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) * Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 0badf802..affc301a 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + //setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); i2p::log::SetThrowFunction ([](const std::string& s) @@ -61,7 +61,7 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + //setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); #ifdef WIN32_APP if (!i2p::win32::StartWin32App ()) return false; diff --git a/build/.gitignore b/build/.gitignore index 872332c5..7689cc88 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,5 +1,7 @@ # Various generated files /CMakeFiles/ +/Testing/ +/tests/ /i2pd /libi2pd.a /libi2pdclient.a @@ -8,6 +10,7 @@ /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake +/CTestTestfile.cmake /install_manifest.txt /arch.c # windows build script diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 11036f16..f6f52001 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -20,12 +20,17 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_GIT_VERSION "Use git commit info as version" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) +option(BUILD_TESTING "Build tests" OFF) + +IF(BUILD_TESTING) + enable_testing() +ENDIF() # paths set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") -#Handle paths nicely +# Handle paths nicely include(GNUInstallDirs) # architecture @@ -171,14 +176,13 @@ if(WITH_THREADSANITIZER) endif() endif() - -# Enable usage of STD's Atomic instead of Boost's on PowerPC -# For more information refer to https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 -if(ARCHITECTURE MATCHES "ppc") +# 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. +if(APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc") add_definitions(-DBOOST_SP_USE_STD_ATOMIC) endif() - # libraries set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -275,9 +279,13 @@ if(WITH_BINARY) set(DL_LIB ${CMAKE_DL_LIBS}) endif() - target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") endif() + +if(BUILD_TESTING) + add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests) +endif() diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake index b8296a1c..d5ec6a0a 100644 --- a/build/cmake_modules/CheckAtomic.cmake +++ b/build/cmake_modules/CheckAtomic.cmake @@ -1,18 +1,23 @@ # atomic builtins are required for threading support. INCLUDE(CheckCXXSourceCompiles) +INCLUDE(CheckLibraryExists) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "-std=c++11") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; +std::atomic y; +std::atomic z; int main() { - return x; + ++z; + ++y; + return ++x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) @@ -27,6 +32,7 @@ function(check_working_cxx_atomics64 varname) std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); + (void)i; return 0; } " ${varname}) @@ -34,15 +40,16 @@ int main() { endfunction(check_working_cxx_atomics64) -# This isn't necessary on MSVC, so avoid command-line switch annoyance -# by only running on GCC-like hosts. -if (LLVM_COMPILER_IS_GCC_COMPATIBLE) +# Check for (non-64-bit) atomic operations. +if(MSVC) + set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) +else() # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) - if( HAVE_LIBATOMIC ) + if(HAVE_LIBATOMIC) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) @@ -58,20 +65,20 @@ endif() if(MSVC) set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) else() + # First check if atomics work without the library. check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) -endif() - -# If not, check if the library exists, and atomics work with it. -if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) - check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) - if(HAVE_CXX_LIBATOMICS64) - list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") - check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) - if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) - message(FATAL_ERROR "Host compiler must support std::atomic!") + # If not, check if the library exists, and atomics work with it. + if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) + if(HAVE_CXX_LIBATOMICS64) + list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) + if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) + message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!") + endif() + else() + message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.") endif() - else() - message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() @@ -80,7 +87,6 @@ endif() ## assumes C++11 works. CHECK_CXX_SOURCE_COMPILES(" #ifdef _MSC_VER -#include /* Workaround for PR19898. */ #include #endif int main() { diff --git a/build/cmake_modules/FindCheck.cmake b/build/cmake_modules/FindCheck.cmake new file mode 100644 index 00000000..8ad818f4 --- /dev/null +++ b/build/cmake_modules/FindCheck.cmake @@ -0,0 +1,55 @@ +# - Try to find the CHECK libraries +# Once done this will define +# +# CHECK_FOUND - system has check +# CHECK_INCLUDE_DIRS - the check include directory +# CHECK_LIBRARIES - check library +# +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2009 Bjoern Ricks +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +INCLUDE( FindPkgConfig ) + +IF ( Check_FIND_REQUIRED ) + SET( _pkgconfig_REQUIRED "REQUIRED" ) +ELSE( Check_FIND_REQUIRED ) + SET( _pkgconfig_REQUIRED "" ) +ENDIF ( Check_FIND_REQUIRED ) + +IF ( CHECK_MIN_VERSION ) + PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check>=${CHECK_MIN_VERSION} ) +ELSE ( CHECK_MIN_VERSION ) + PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check ) +ENDIF ( CHECK_MIN_VERSION ) + +# Look for CHECK include dir and libraries +IF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) + + FIND_PATH( CHECK_INCLUDE_DIRS check.h ) + + FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) + + IF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) + SET( CHECK_FOUND 1 ) + IF ( NOT Check_FIND_QUIETLY ) + MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) + ENDIF ( NOT Check_FIND_QUIETLY ) + ELSE ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) + IF ( Check_FIND_REQUIRED ) + MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) + ELSE ( Check_FIND_REQUIRED ) + IF ( NOT Check_FIND_QUIETLY ) + MESSAGE( STATUS "Could NOT find CHECK" ) + ENDIF ( NOT Check_FIND_QUIETLY ) + ENDIF ( Check_FIND_REQUIRED ) + ENDIF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) +ENDIF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) + +# Hide advanced variables from CMake GUIs +MARK_AS_ADVANCED( CHECK_INCLUDE_DIRS CHECK_LIBRARIES ) + diff --git a/build/cmake_modules/TargetArch.cmake b/build/cmake_modules/TargetArch.cmake index ac7275b1..d59925c8 100644 --- a/build/cmake_modules/TargetArch.cmake +++ b/build/cmake_modules/TargetArch.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2022, The PurpleI2P Project +# Copyright (c) 2017-2023, The PurpleI2P Project # This file is part of Purple i2pd project and licensed under BSD3 # See full license text in LICENSE file at top of project tree @@ -83,13 +83,13 @@ function(target_architecture output_var) # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using - # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we - # disable it by default + # the OS X SDK version 10.7 or later - you'll need 10.4/10.5/10.6 for that, so we + # disable it by default. Also, ppc64 is not supported in 10.6. # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. - # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. + # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise; 10.6 also supports ppc. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) @@ -133,11 +133,11 @@ function(target_architecture output_var) enable_language(C) # Detect the architecture in a rather creative way... - # This compiles a small C program which is a series of ifdefs that selects a - # particular #error preprocessor directive whose message string contains the - # target architecture. The program will always fail to compile (both because - # file is not a valid C program, and obviously because of the presence of the - # #error preprocessor directives... but by exploiting the preprocessor in this + # This compiles a small C program which is a series of ifdefs that selects + # a particular #error preprocessor directive whose message string contains + # the target architecture. The program will always fail to compile (both because + # file is not a valid C program, and obviously because of the presence of + # the #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 129c5ff3..ead21f10 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -60,8 +60,8 @@ RUN apk update \ RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ # 3. Copy preconfigured config file and entrypoint -COPY i2pd-docker.conf "$I2PD_HOME/i2pd.conf" -RUN chown i2pd:nobody "$I2PD_HOME/i2pd.conf" +COPY i2pd-docker.conf "$DATA_DIR/i2pd.conf" +RUN chown i2pd:nobody "$DATA_DIR/i2pd.conf" COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index c9ded966..22010680 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -1,13 +1,13 @@ # i2pd -# Copyright (C) 2021-2022 PurpleI2P team +# Copyright (C) 2021-2023 PurpleI2P team # This file is distributed under the same license as the i2pd package. -# R4SAS , 2021-2022. +# R4SAS , 2021-2023. # msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2022-07-26 21:22\n" +"POT-Creation-Date: 2023-01-19 04:18\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -18,712 +18,748 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:108 -msgid "day" -msgid_plural "days" +#: daemon/HTTPServer.cpp:106 +#, c-format +msgid "%d day" +msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:112 -msgid "hour" -msgid_plural "hours" +#: daemon/HTTPServer.cpp:110 +#, c-format +msgid "%d hour" +msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:116 -msgid "minute" -msgid_plural "minutes" +#: daemon/HTTPServer.cpp:114 +#, c-format +msgid "%d minute" +msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:119 -msgid "second" -msgid_plural "seconds" +#: daemon/HTTPServer.cpp:117 +#, c-format +msgid "%d second" +msgid_plural "%d seconds" msgstr[0] "" msgstr[1] "" -#. tr: Kibibit -#: daemon/HTTPServer.cpp:127 daemon/HTTPServer.cpp:155 -msgid "KiB" +#. tr: Kibibyte +#: daemon/HTTPServer.cpp:125 daemon/HTTPServer.cpp:153 +#, c-format +msgid "%.2f KiB" msgstr "" -#. tr: Mebibit +#. tr: Mebibyte +#: daemon/HTTPServer.cpp:127 +#, c-format +msgid "%.2f MiB" +msgstr "" + +#. tr: Gibibyte #: daemon/HTTPServer.cpp:129 -msgid "MiB" +#, c-format +msgid "%.2f GiB" msgstr "" -#. tr: Gibibit -#: daemon/HTTPServer.cpp:131 -msgid "GiB" -msgstr "" - -#: daemon/HTTPServer.cpp:148 +#: daemon/HTTPServer.cpp:146 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:149 +#: daemon/HTTPServer.cpp:147 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:150 +#: daemon/HTTPServer.cpp:148 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:151 +#: daemon/HTTPServer.cpp:149 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:152 +#: daemon/HTTPServer.cpp:150 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:154 +#: daemon/HTTPServer.cpp:152 msgid "exploratory" msgstr "" #. tr: Webconsole page title -#: daemon/HTTPServer.cpp:185 +#: daemon/HTTPServer.cpp:183 msgid "Purple I2P Webconsole" msgstr "" -#: daemon/HTTPServer.cpp:190 +#: daemon/HTTPServer.cpp:188 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:193 +#: daemon/HTTPServer.cpp:191 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:700 +#: daemon/HTTPServer.cpp:192 daemon/HTTPServer.cpp:712 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:382 -#: daemon/HTTPServer.cpp:394 +#: daemon/HTTPServer.cpp:193 daemon/HTTPServer.cpp:387 +#: daemon/HTTPServer.cpp:399 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:197 daemon/HTTPServer.cpp:352 -#: daemon/HTTPServer.cpp:438 daemon/HTTPServer.cpp:444 -#: daemon/HTTPServer.cpp:597 daemon/HTTPServer.cpp:640 -#: daemon/HTTPServer.cpp:644 +#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:357 +#: daemon/HTTPServer.cpp:443 daemon/HTTPServer.cpp:449 +#: daemon/HTTPServer.cpp:609 daemon/HTTPServer.cpp:652 +#: daemon/HTTPServer.cpp:656 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:650 +#: daemon/HTTPServer.cpp:197 daemon/HTTPServer.cpp:662 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:359 -#: daemon/HTTPServer.cpp:770 daemon/HTTPServer.cpp:786 +#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:364 +#: daemon/HTTPServer.cpp:781 daemon/HTTPServer.cpp:797 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:839 +#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:855 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:204 +#: daemon/HTTPServer.cpp:202 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:908 -#: daemon/HTTPServer.cpp:918 +#: daemon/HTTPServer.cpp:204 daemon/HTTPServer.cpp:884 +#: daemon/HTTPServer.cpp:894 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:222 daemon/HTTPServer.cpp:1302 -#: daemon/HTTPServer.cpp:1305 daemon/HTTPServer.cpp:1308 -#: daemon/HTTPServer.cpp:1322 daemon/HTTPServer.cpp:1367 -#: daemon/HTTPServer.cpp:1370 daemon/HTTPServer.cpp:1373 +#: daemon/HTTPServer.cpp:220 daemon/HTTPServer.cpp:1278 +#: daemon/HTTPServer.cpp:1281 daemon/HTTPServer.cpp:1284 +#: daemon/HTTPServer.cpp:1298 daemon/HTTPServer.cpp:1343 +#: daemon/HTTPServer.cpp:1346 daemon/HTTPServer.cpp:1349 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:229 +#: daemon/HTTPServer.cpp:227 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:230 +#: daemon/HTTPServer.cpp:228 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:231 +#: daemon/HTTPServer.cpp:229 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:253 -#: daemon/HTTPServer.cpp:325 +#: daemon/HTTPServer.cpp:230 daemon/HTTPServer.cpp:233 +#: daemon/HTTPServer.cpp:329 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:369 -#: daemon/HTTPServer.cpp:370 daemon/HTTPServer.cpp:976 -#: daemon/HTTPServer.cpp:985 +#: daemon/HTTPServer.cpp:231 daemon/HTTPServer.cpp:374 +#: daemon/HTTPServer.cpp:375 daemon/HTTPServer.cpp:952 +#: daemon/HTTPServer.cpp:961 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:234 +#: daemon/HTTPServer.cpp:232 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:237 -msgid "Error" -msgstr "" - -#: daemon/HTTPServer.cpp:241 +#: daemon/HTTPServer.cpp:240 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:244 +#: daemon/HTTPServer.cpp:243 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:247 +#: daemon/HTTPServer.cpp:246 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:259 +#: daemon/HTTPServer.cpp:249 +msgid "Full cone NAT" +msgstr "" + +#: daemon/HTTPServer.cpp:252 +msgid "No Descriptors" +msgstr "" + +#: daemon/HTTPServer.cpp:261 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:262 +#: daemon/HTTPServer.cpp:264 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:267 +#: daemon/HTTPServer.cpp:269 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:280 +#: daemon/HTTPServer.cpp:275 daemon/HTTPServer.cpp:282 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:287 +#: daemon/HTTPServer.cpp:289 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:288 +#: daemon/HTTPServer.cpp:290 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:289 +#: daemon/HTTPServer.cpp:291 msgid "Received" msgstr "" -#. tr: Kibibit/s -#: daemon/HTTPServer.cpp:291 daemon/HTTPServer.cpp:294 -#: daemon/HTTPServer.cpp:297 -msgid "KiB/s" +#. tr: Kibibyte/s +#: daemon/HTTPServer.cpp:293 daemon/HTTPServer.cpp:296 +#: daemon/HTTPServer.cpp:299 +#, c-format +msgid "%.2f KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:292 +#: daemon/HTTPServer.cpp:294 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:295 +#: daemon/HTTPServer.cpp:297 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:298 +#: daemon/HTTPServer.cpp:300 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:301 +#: daemon/HTTPServer.cpp:303 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:304 +#: daemon/HTTPServer.cpp:307 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:306 +#: daemon/HTTPServer.cpp:309 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:307 +#: daemon/HTTPServer.cpp:310 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:308 +#: daemon/HTTPServer.cpp:311 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:309 +#: daemon/HTTPServer.cpp:312 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:337 +#. tr: Shown when router doesn't publish itself and have "Firewalled" state +#: daemon/HTTPServer.cpp:341 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:350 +#: daemon/HTTPServer.cpp:355 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:351 +#: daemon/HTTPServer.cpp:356 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:962 +#: daemon/HTTPServer.cpp:363 daemon/HTTPServer.cpp:938 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:368 +#: daemon/HTTPServer.cpp:373 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:369 daemon/HTTPServer.cpp:370 -#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:372 -#: daemon/HTTPServer.cpp:373 daemon/HTTPServer.cpp:374 +#: daemon/HTTPServer.cpp:374 daemon/HTTPServer.cpp:375 +#: daemon/HTTPServer.cpp:376 daemon/HTTPServer.cpp:377 +#: daemon/HTTPServer.cpp:378 daemon/HTTPServer.cpp:379 msgid "Enabled" msgstr "" -#: daemon/HTTPServer.cpp:369 daemon/HTTPServer.cpp:370 -#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:372 -#: daemon/HTTPServer.cpp:373 daemon/HTTPServer.cpp:374 +#: daemon/HTTPServer.cpp:374 daemon/HTTPServer.cpp:375 +#: daemon/HTTPServer.cpp:376 daemon/HTTPServer.cpp:377 +#: daemon/HTTPServer.cpp:378 daemon/HTTPServer.cpp:379 msgid "Disabled" msgstr "" -#: daemon/HTTPServer.cpp:417 +#: daemon/HTTPServer.cpp:422 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:426 +#: daemon/HTTPServer.cpp:431 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:431 +#: daemon/HTTPServer.cpp:436 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:432 +#: daemon/HTTPServer.cpp:437 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:433 +#: daemon/HTTPServer.cpp:438 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" -#: daemon/HTTPServer.cpp:439 +#: daemon/HTTPServer.cpp:444 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:439 +#: daemon/HTTPServer.cpp:444 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:439 +#: daemon/HTTPServer.cpp:444 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:449 daemon/HTTPServer.cpp:655 +#: daemon/HTTPServer.cpp:454 daemon/HTTPServer.cpp:667 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds -#: daemon/HTTPServer.cpp:464 daemon/HTTPServer.cpp:484 -#: daemon/HTTPServer.cpp:669 daemon/HTTPServer.cpp:689 -msgid "ms" +#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:489 +#: daemon/HTTPServer.cpp:681 daemon/HTTPServer.cpp:701 +#, c-format +msgid "%dms" msgstr "" -#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:674 +#: daemon/HTTPServer.cpp:474 daemon/HTTPServer.cpp:686 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:491 +#: daemon/HTTPServer.cpp:496 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:491 +#: daemon/HTTPServer.cpp:497 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:498 daemon/HTTPServer.cpp:501 +#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:515 +#: daemon/HTTPServer.cpp:507 daemon/HTTPServer.cpp:526 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:499 +#: daemon/HTTPServer.cpp:507 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:506 +#: daemon/HTTPServer.cpp:515 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:514 daemon/HTTPServer.cpp:517 +#: daemon/HTTPServer.cpp:523 daemon/HTTPServer.cpp:529 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:515 +#: daemon/HTTPServer.cpp:526 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:524 daemon/HTTPServer.cpp:582 +#: daemon/HTTPServer.cpp:536 daemon/HTTPServer.cpp:594 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:941 +#: daemon/HTTPServer.cpp:547 daemon/HTTPServer.cpp:917 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:558 +#: daemon/HTTPServer.cpp:570 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:587 +#: daemon/HTTPServer.cpp:599 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:590 +#: daemon/HTTPServer.cpp:602 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:616 +#: daemon/HTTPServer.cpp:628 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:619 +#: daemon/HTTPServer.cpp:631 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:620 +#: daemon/HTTPServer.cpp:632 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:625 +#: daemon/HTTPServer.cpp:637 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:628 +#: daemon/HTTPServer.cpp:640 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:629 +#: daemon/HTTPServer.cpp:641 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:630 +#: daemon/HTTPServer.cpp:642 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:640 -msgid "not floodfill" +#: daemon/HTTPServer.cpp:652 +msgid "floodfill mode is disabled" msgstr "" -#: daemon/HTTPServer.cpp:651 +#: daemon/HTTPServer.cpp:663 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:701 +#: daemon/HTTPServer.cpp:713 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:706 +#: daemon/HTTPServer.cpp:714 +msgid "Reload tunnels configuration" +msgstr "" + +#: daemon/HTTPServer.cpp:717 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:708 +#: daemon/HTTPServer.cpp:719 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:712 daemon/HTTPServer.cpp:717 +#: daemon/HTTPServer.cpp:723 daemon/HTTPServer.cpp:728 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:714 daemon/HTTPServer.cpp:719 +#: daemon/HTTPServer.cpp:725 daemon/HTTPServer.cpp:730 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:722 +#: daemon/HTTPServer.cpp:733 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:723 +#: daemon/HTTPServer.cpp:734 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:726 +#: daemon/HTTPServer.cpp:737 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:728 +#: daemon/HTTPServer.cpp:739 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:736 +#: daemon/HTTPServer.cpp:747 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:741 daemon/HTTPServer.cpp:760 +#: daemon/HTTPServer.cpp:752 daemon/HTTPServer.cpp:771 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:748 +#: daemon/HTTPServer.cpp:759 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:786 +#: daemon/HTTPServer.cpp:797 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:902 daemon/HTTPServer.cpp:925 +#: daemon/HTTPServer.cpp:878 daemon/HTTPServer.cpp:901 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:918 +#: daemon/HTTPServer.cpp:894 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:931 +#: daemon/HTTPServer.cpp:907 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:936 +#: daemon/HTTPServer.cpp:912 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:993 +#: daemon/HTTPServer.cpp:969 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:1009 +#: daemon/HTTPServer.cpp:985 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1023 +#: daemon/HTTPServer.cpp:999 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1223 +#: daemon/HTTPServer.cpp:1199 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1242 +#: daemon/HTTPServer.cpp:1218 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1300 daemon/HTTPServer.cpp:1357 -#: daemon/HTTPServer.cpp:1397 +#: daemon/HTTPServer.cpp:1276 daemon/HTTPServer.cpp:1333 +#: daemon/HTTPServer.cpp:1373 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1300 +#: daemon/HTTPServer.cpp:1276 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1302 +#: daemon/HTTPServer.cpp:1278 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1305 +#: daemon/HTTPServer.cpp:1281 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1308 +#: daemon/HTTPServer.cpp:1284 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1310 daemon/HTTPServer.cpp:1375 +#: daemon/HTTPServer.cpp:1286 daemon/HTTPServer.cpp:1351 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1311 daemon/HTTPServer.cpp:1324 -#: daemon/HTTPServer.cpp:1399 -msgid "You will be redirected in 5 seconds" +#: daemon/HTTPServer.cpp:1287 daemon/HTTPServer.cpp:1300 +#: daemon/HTTPServer.cpp:1375 +#, c-format +msgid "You will be redirected in %d seconds" msgstr "" -#: daemon/HTTPServer.cpp:1322 -msgid "Transit tunnels count must not exceed 65535" +#: daemon/HTTPServer.cpp:1298 +#, c-format +msgid "Transit tunnels count must not exceed %d" msgstr "" -#: daemon/HTTPServer.cpp:1323 daemon/HTTPServer.cpp:1398 +#: daemon/HTTPServer.cpp:1299 daemon/HTTPServer.cpp:1374 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1359 +#: daemon/HTTPServer.cpp:1335 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1360 +#: daemon/HTTPServer.cpp:1336 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1360 +#: daemon/HTTPServer.cpp:1336 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1337 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1343 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1370 +#: daemon/HTTPServer.cpp:1346 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1373 +#: daemon/HTTPServer.cpp:1349 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1393 +#: daemon/HTTPServer.cpp:1369 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1397 +#: daemon/HTTPServer.cpp:1373 msgid "Command accepted" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:163 +#: libi2pd_client/HTTPProxy.cpp:166 msgid "Proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:171 +#: libi2pd_client/HTTPProxy.cpp:174 msgid "Proxy info" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:179 +#: libi2pd_client/HTTPProxy.cpp:182 msgid "Proxy error: Host not found" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:180 +#: libi2pd_client/HTTPProxy.cpp:183 msgid "Remote host not found in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:181 +#: libi2pd_client/HTTPProxy.cpp:184 msgid "You may try to find this host on jump services below" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:282 libi2pd_client/HTTPProxy.cpp:297 -#: libi2pd_client/HTTPProxy.cpp:331 libi2pd_client/HTTPProxy.cpp:372 +#: libi2pd_client/HTTPProxy.cpp:309 libi2pd_client/HTTPProxy.cpp:324 +#: libi2pd_client/HTTPProxy.cpp:392 libi2pd_client/HTTPProxy.cpp:435 msgid "Invalid request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:282 +#: libi2pd_client/HTTPProxy.cpp:309 msgid "Proxy unable to parse your request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:297 -msgid "addresshelper is not supported" +#: libi2pd_client/HTTPProxy.cpp:324 +msgid "Addresshelper is not supported" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:349 +#, c-format +msgid "" +"Host %s is already in router's addressbook. Be " +"careful: source of this URL may be harmful! Click here to update record: " +"Continue." +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:351 +msgid "Addresshelper forced update rejected" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:358 +#, c-format +msgid "" +"To add host %s in router's addressbook, click here: Continue." +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:360 +msgid "Addresshelper request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:369 +#, c-format +msgid "" +"Host %s added to router's addressbook from helper. Click here to proceed: Continue." +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:370 +msgid "Addresshelper adding" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:377 +#, c-format +msgid "" +"Host %s is already in router's addressbook. Click " +"here to update record: Continue." +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:379 +msgid "Addresshelper update" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:306 libi2pd_client/HTTPProxy.cpp:315 #: libi2pd_client/HTTPProxy.cpp:392 -msgid "Host" +msgid "Invalid request URI" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:306 -msgid "added to router's addressbook from helper" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:307 -msgid "Click here to proceed:" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:307 libi2pd_client/HTTPProxy.cpp:317 -msgid "Continue" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:308 libi2pd_client/HTTPProxy.cpp:318 -msgid "Addresshelper found" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:315 -msgid "already in router's addressbook" -msgstr "" - -#. tr: The "record" means addressbook's record. That message appears when domain was already added to addressbook, but helper link is opened for it. -#: libi2pd_client/HTTPProxy.cpp:316 -msgid "Click here to update record:" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:331 -msgid "invalid request uri" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:372 +#: libi2pd_client/HTTPProxy.cpp:435 msgid "Can't detect destination host from request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:389 libi2pd_client/HTTPProxy.cpp:393 +#: libi2pd_client/HTTPProxy.cpp:452 libi2pd_client/HTTPProxy.cpp:456 msgid "Outproxy failure" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:389 -msgid "bad outproxy settings" +#: libi2pd_client/HTTPProxy.cpp:452 +msgid "Bad outproxy settings" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:392 -msgid "not inside I2P network, but outproxy is not enabled" +#: libi2pd_client/HTTPProxy.cpp:455 +#, c-format +msgid "Host %s is not inside I2P network, but outproxy is not enabled" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:482 -msgid "unknown outproxy url" +#: libi2pd_client/HTTPProxy.cpp:544 +msgid "Unknown outproxy URL" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:490 -msgid "cannot resolve upstream proxy" +#: libi2pd_client/HTTPProxy.cpp:550 +msgid "Cannot resolve upstream proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:498 -msgid "hostname too long" +#: libi2pd_client/HTTPProxy.cpp:558 +msgid "Hostname is too long" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:525 -msgid "cannot connect to upstream socks proxy" +#: libi2pd_client/HTTPProxy.cpp:585 +msgid "Cannot connect to upstream SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:531 -msgid "Cannot negotiate with socks proxy" +#: libi2pd_client/HTTPProxy.cpp:591 +msgid "Cannot negotiate with SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:573 +#: libi2pd_client/HTTPProxy.cpp:633 msgid "CONNECT error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:573 -msgid "Failed to Connect" +#: libi2pd_client/HTTPProxy.cpp:633 +msgid "Failed to connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:584 libi2pd_client/HTTPProxy.cpp:610 -msgid "socks proxy error" +#: libi2pd_client/HTTPProxy.cpp:644 libi2pd_client/HTTPProxy.cpp:670 +msgid "SOCKS proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:592 -msgid "failed to send request to upstream" +#: libi2pd_client/HTTPProxy.cpp:652 +msgid "Failed to send request to upstream" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:613 -msgid "No Reply From socks proxy" +#: libi2pd_client/HTTPProxy.cpp:673 +msgid "No reply from SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:620 -msgid "cannot connect" +#: libi2pd_client/HTTPProxy.cpp:680 +msgid "Cannot connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:620 -msgid "http out proxy not implemented" +#: libi2pd_client/HTTPProxy.cpp:680 +msgid "HTTP out proxy not implemented" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:621 -msgid "cannot connect to upstream http proxy" +#: libi2pd_client/HTTPProxy.cpp:681 +msgid "Cannot connect to upstream HTTP proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:654 +#: libi2pd_client/HTTPProxy.cpp:714 msgid "Host is down" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:654 +#: libi2pd_client/HTTPProxy.cpp:714 msgid "" "Can't create connection to requested host, it may be down. Please try again " "later." diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md index 57021f60..56d76c5a 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -1,29 +1,29 @@ -`xgettext` command for extracting translation ---- - -``` -xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp -``` - -Regex for transforming gettext translations to our format: ---- - -``` -in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\n(msgstr\[1\]\ \"(.*)\"\n)?(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? -out: #{"$2", {"$3", "$5", "$7", "$9", "$11"}},\n -``` - -``` -in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n -out: {"$1", "$2"},\n -``` - -``` -in: ^#[:.](.*)$\n -out: -``` - -``` -in: \n\n -out: \n -``` +`xgettext` command for extracting translation +--- + +``` +xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +``` + +Regex for transforming gettext translations to our format: +--- + +``` +in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\n(msgstr\[1\]\ \"(.*)\"\n)?(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? +out: #{"$2", {"$3", "$5", "$7", "$9", "$11"}},\n +``` + +``` +in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n +out: {"$1", "$2"},\n +``` + +``` +in: ^#[:.,](.*)$\n +out: +``` + +``` +in: \n\n +out: \n +``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index b281ce8f..84d00347 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -122,12 +122,15 @@ port = 7070 ## Path to web console, default "/" # webroot = / ## Uncomment following lines to enable Web Console authentication +## You should not use Web Console via public networks without additional encryption. +## HTTP authentication is not encryption layer! # auth = true # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), afrikaans, armenian, chinese, french, -## german, italian, russian, spanish, turkmen, ukrainian and uzbek languages +## Currently supported english (default), afrikaans, armenian, chinese, czech, french, +## german, italian, polish, portuguese, russian, spanish, turkish, turkmen, ukrainian +## and uzbek languages # lang = english [httpproxy] @@ -139,6 +142,8 @@ port = 4444 ## Optional keys file for proxy local destination # keys = http-proxy-keys.dat ## Enable address helper for adding .i2p domains with "jump URLs" (default: true) +## You should disable this feature if your i2pd HTTP Proxy is public, +## because anyone could spoof the short domain via addresshelper and forward other users to phishing links # addresshelper = true ## Address of a proxy server inside I2P, which is used to visit regular Internet # outproxy = http://false.i2p @@ -237,8 +242,9 @@ verify = true # subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt [limits] -## Maximum active transit sessions (default:2500) -# transittunnels = 2500 +## Maximum active transit sessions (default: 5000) +## This value is doubled if floodfill mode is enabled! +# transittunnels = 5000 ## Limit number of open file descriptors (0 - use system limit) # openfiles = 0 ## Maximum size of corefile in Kb (0 - use system limit) diff --git a/contrib/i2pd.service b/contrib/i2pd.service index 45fe4cc6..79bf15c8 100644 --- a/contrib/i2pd.service +++ b/contrib/i2pd.service @@ -29,7 +29,7 @@ SendSIGKILL=yes #TimeoutStopSec=10m # If you have problems with hanging i2pd, you can try increase this -LimitNOFILE=4096 +LimitNOFILE=8192 # To enable write of coredump uncomment this #LimitCORE=infinity diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 9183b529..e758d42b 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.44.0 +Version: 2.46.1 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -158,6 +158,18 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Feb 20 2023 r4sas - 2.46.1 +- update to 2.46.1 + +* Wed Feb 15 2023 orignal - 2.46.0 +- update to 2.46.0 + +* Wed Jan 11 2023 orignal - 2.45.1 +- update to 2.45.1 + +* Tue Jan 3 2023 orignal - 2.45.0 +- update to 2.45.0 + * Sun Nov 20 2022 orignal - 2.44.0 - update to 2.44.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 16734f3f..32154a9c 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd -Version: 2.44.0 -Release: 1%{?dist} +Version: 2.46.1 +Release: 2%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -155,6 +155,18 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Feb 20 2023 r4sas - 2.46.1 +- update to 2.46.1 + +* Wed Feb 15 2023 orignal - 2.46.0 +- update to 2.46.0 + +* Wed Jan 11 2023 orignal - 2.45.1 +- update to 2.45.1 + +* Tue Jan 3 2023 orignal - 2.45.0 +- update to 2.45.0 + * Sun Nov 20 2022 orignal - 2.44.0 - update to 2.44.0 diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css index 5d63d1a7..89021539 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -1,293 +1,293 @@ -/* - * Copyright (c) 2021-2022, The PurpleI2P Project - * - * This file is part of Purple i2pd project and licensed under BSD3 - * - * See full license text in LICENSE file at top of project tree - * - ****************************************************************** - * - * This is style sheet for webconsole, with @media selectors for adaptive - * view on desktop and mobile devices, respecting preferred user's color - * scheme used in system/browser. - * - * Minified copy of that style sheet is bundled inside i2pd sources. -*/ - -:root { - --main-bg-color: #fafafa; - --main-text-color: #103456; - --main-link-color: #894c84; - --main-link-hover-color: #fafafa; -} - -@media (prefers-color-scheme: dark) { - :root { - --main-bg-color: #242424; - --main-text-color: #17ab5c; - --main-link-color: #bf64b7; - --main-link-hover-color: #000000; - } -} - -body { - font: 100%/1.5em sans-serif; - margin: 0; - padding: 1.5em; - background: var(--main-bg-color); - color: var(--main-text-color); -} - -a, .slide label { - text-decoration: none; - color: var(--main-link-color); -} - -a:hover, .slide label:hover, button[type=submit]:hover { - color: var(--main-link-hover-color); - background: var(--main-link-color); -} - -a.button { - appearance: button; - text-decoration: none; - padding: 0 5px; - border: 1px solid var(--main-link-color); -} - -.header { - font-size: 2.5em; - text-align: center; - margin: 1em 0; - color: var(--main-link-color); -} - -.wrapper { - margin: 0 auto; - padding: 1em; - max-width: 64em; -} - -.menu { - display: block; - float: left; - overflow: hidden; - padding: 4px; - max-width: 12em; - white-space: nowrap; - text-overflow: ellipsis; -} - -.listitem { - display: block; - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.tableitem { - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.content { - float: left; - font-size: 1em; - margin-left: 2em; - padding: 4px; - max-width: 50em; - overflow: auto; -} - -.tunnel.established { - color: #56B734; -} - -.tunnel.expiring { - color: #D3AE3F; -} - -.tunnel.failed { - color: #D33F3F; -} - -.tunnel.building { - color: #434343; -} - -caption { - font-size: 1.5em; - text-align: center; - color: var(--main-link-color); -} - -table { - display: table; - border-collapse: collapse; - text-align: center; -} - -table.extaddr { - text-align: left; -} - -table.services { - width: 100%; -} - -textarea { - background-color: var(--main-bg-color); - color: var(--main-text-color); - word-break: break-all; -} - -.streamdest { - width: 120px; - max-width: 240px; - overflow: hidden; - text-overflow: ellipsis; -} - -.slide div.slidecontent, .slide [type="checkbox"] { - display: none; -} - -.slide [type="checkbox"]:checked ~ div.slidecontent { - display: block; - margin-top: 0; - padding: 0; -} - -.disabled { - color: #D33F3F; -} - -.enabled { - color: #56B734; -} - -button[type=submit] { - background-color: transparent; - color: var(--main-link-color); - text-decoration: none; - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input, select, select option { - background-color: var(--main-bg-color); - color: var(--main-link-color); - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input:focus, select:focus, select option:focus { - outline: none; -} - -input[type=number]::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -@media screen and (max-width: 1150px) { /* adaptive style */ - .wrapper { - max-width: 58em; - } - - .content { - max-width: 40em; - } -} - -@media screen and (max-width: 980px) { - body { - font: 100%/1.2em sans-serif; - padding: 1.2em 0 0 0; - } - - .menu { - width: 100%; - max-width: unset; - display: block; - float: none; - position: unset; - font-size: 16px; - text-align: center; - } - - .menu a, .commands a { - display: inline-block; - padding: 4px; - } - - .content { - float: none; - margin-left: unset; - margin-top: 16px; - max-width: 100%; - width: 100%; - text-align: center; - } - - a, .slide label { - display: block; - } - - .header { - margin: unset; - font-size: 1.5em; - } - - small { - display: block - } - - a.button { - appearance: button; - text-decoration: none; - margin-top: 10px; - padding: 6px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - width: -webkit-fill-available; - } - - input, select { - width: 35%; - text-align: center; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 18px; - } - - table.extaddr { - margin: auto; - text-align: unset; - } - - textarea { - width: -webkit-fill-available; - height: auto; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 12px; - } - - button[type=submit] { - padding: 5px 15px; - background: transparent; - border: 2px solid var(--main-link-color); - cursor: pointer; - -webkit-border-radius: 5px; - border-radius: 5px; - position: relative; - height: 36px; - display: -webkit-inline-box; - margin-top: 10px; - } -} \ No newline at end of file +/* + * Copyright (c) 2021-2023, The PurpleI2P Project + * + * This file is part of Purple i2pd project and licensed under BSD3 + * + * See full license text in LICENSE file at top of project tree + * + ****************************************************************** + * + * This is style sheet for webconsole, with @media selectors for adaptive + * view on desktop and mobile devices, respecting preferred user's color + * scheme used in system/browser. + * + * Minified copy of that style sheet is bundled inside i2pd sources. +*/ + +:root { + --main-bg-color: #fafafa; + --main-text-color: #103456; + --main-link-color: #894c84; + --main-link-hover-color: #fafafa; +} + +@media (prefers-color-scheme: dark) { + :root { + --main-bg-color: #242424; + --main-text-color: #17ab5c; + --main-link-color: #bf64b7; + --main-link-hover-color: #000000; + } +} + +body { + font: 100%/1.5em sans-serif; + margin: 0; + padding: 1.5em; + background: var(--main-bg-color); + color: var(--main-text-color); +} + +a, .slide label { + text-decoration: none; + color: var(--main-link-color); +} + +a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { + color: var(--main-link-hover-color); + background: var(--main-link-color); +} + +a.button { + appearance: button; + text-decoration: none; + padding: 0 5px; + border: 1px solid var(--main-link-color); +} + +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: var(--main-link-color); +} + +.wrapper { + margin: 0 auto; + padding: 1em; + max-width: 64em; +} + +.menu { + display: block; + float: left; + overflow: hidden; + padding: 4px; + max-width: 12em; + white-space: nowrap; + text-overflow: ellipsis; +} + +.listitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.tableitem { + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.content { + float: left; + font-size: 1em; + margin-left: 2em; + padding: 4px; + max-width: 50em; + overflow: auto; +} + +.tunnel.established { + color: #56B734; +} + +.tunnel.expiring { + color: #D3AE3F; +} + +.tunnel.failed { + color: #D33F3F; +} + +.tunnel.building { + color: #434343; +} + +caption { + font-size: 1.5em; + text-align: center; + color: var(--main-link-color); +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +table.extaddr { + text-align: left; +} + +table.services { + width: 100%; +} + +textarea { + background-color: var(--main-bg-color); + color: var(--main-text-color); + word-break: break-all; +} + +.streamdest { + width: 120px; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide div.slidecontent, .slide [type="checkbox"] { + display: none; +} + +.slide [type="checkbox"]:checked ~ div.slidecontent { + display: block; + margin-top: 0; + padding: 0; +} + +.disabled { + color: #D33F3F; +} + +.enabled { + color: #56B734; +} + +button[type=submit] { + background-color: transparent; + color: var(--main-link-color); + text-decoration: none; + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input, select, select option { + background-color: var(--main-bg-color); + color: var(--main-link-color); + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input:focus, select:focus, select option:focus { + outline: none; +} + +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +@media screen and (max-width: 1150px) { /* adaptive style */ + .wrapper { + max-width: 58em; + } + + .content { + max-width: 40em; + } +} + +@media screen and (max-width: 980px) { + body { + font: 100%/1.2em sans-serif; + padding: 1.2em 0 0 0; + } + + .menu { + width: 100%; + max-width: unset; + display: block; + float: none; + position: unset; + font-size: 16px; + text-align: center; + } + + .menu a, .commands a { + display: inline-block; + padding: 4px; + } + + .content { + float: none; + margin-left: unset; + margin-top: 16px; + max-width: 100%; + width: 100%; + text-align: center; + } + + a, .slide label { + display: block; + } + + .header { + margin: unset; + font-size: 1.5em; + } + + small { + display: block + } + + a.button { + appearance: button; + text-decoration: none; + margin-top: 10px; + padding: 6px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + width: -webkit-fill-available; + } + + input, select { + width: 35%; + text-align: center; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 18px; + } + + table.extaddr { + margin: auto; + text-align: unset; + } + + textarea { + width: -webkit-fill-available; + height: auto; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 12px; + } + + button[type=submit] { + padding: 5px 15px; + background: transparent; + border: 2px solid var(--main-link-color); + cursor: pointer; + -webkit-border-radius: 5px; + border-radius: 5px; + position: relative; + height: 36px; + display: -webkit-inline-box; + margin-top: 10px; + } +} diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 2c120537..a0d7cf1a 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -165,20 +165,21 @@ namespace util i2p::transport::InitTransports (); - bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetAcceptsTunnels (!transit); - uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); - SetMaxNumTransitTunnels (transitTunnels); - bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) { + if (isFloodfill) + { LogPrint(eLogInfo, "Daemon: Router configured as floodfill"); i2p::context.SetFloodfill (true); } else - { i2p::context.SetFloodfill (false); - } + + bool transit; i2p::config::GetOption("notransit", transit); + i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels")) + transitTunnels *= 2; // double default number of transit tunnels for floodfill + SetMaxNumTransitTunnels (transitTunnels); /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 917acdb8..e42efb36 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -87,8 +87,6 @@ namespace http { const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; - const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; - const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time) { @@ -105,18 +103,18 @@ namespace http { int num; if ((num = seconds / 86400) > 0) { - s << num << " " << tr("day", "days", num) << ", "; + s << ntr("%d day", "%d days", num, num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << num << " " << tr("hour", "hours", num) << ", "; + s << ntr("%d hour", "%d hours", num, num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << num << " " << tr("minute", "minutes", num) << ", "; + s << ntr("%d minute", "%d minutes", num, num) << ", "; seconds -= num * 60; } - s << seconds << " " << tr("second", "seconds", seconds); + s << ntr("%d second", "%d seconds", seconds, seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) @@ -124,11 +122,11 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); + s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes); else if (numKBytes < 1024 * 1024) - s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); + s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024); else - s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); + s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) @@ -152,7 +150,8 @@ namespace http { else stateText = tr("unknown"); s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; - s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; + ShowTraffic(s, bytes); + s << "\r\n"; } static void SetLogLevel (const std::string& level) @@ -200,7 +199,7 @@ namespace http { if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) s << " " << tr("Transit Tunnels") << "
\r\n"; s << - " " << tr ("Transports") << "
\r\n" + " " << tr("Transports") << "
\r\n" " " << tr("I2P tunnels") << "
\r\n"; if (i2p::client::context.GetSAMBridge ()) s << " " << tr("SAM sessions") << "
\r\n"; @@ -236,7 +235,6 @@ namespace http { } if (error != eRouterErrorNone) { - s << "
"; switch (error) { case eRouterErrorClockSkew: @@ -248,6 +246,9 @@ namespace http { case eRouterErrorSymmetricNAT: s << " - " << tr("Symmetric NAT"); break; + case eRouterErrorFullConeNAT: + s << " - " << tr("Full cone NAT"); + break; case eRouterErrorNoDescriptors: s << " - " << tr("No Descriptors"); break; @@ -288,21 +289,26 @@ namespace http { if (family.length () > 0) s << ""<< tr("Family") << ": " << family << "
\r\n"; s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; + bool isTotalTCSR; + i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); + if (isTotalTCSR) { + s << "" << tr("Total tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%
\r\n"; + } s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << (double) i2p::transport::transports.GetInBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; + s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetInBandwidth15s () / 1024) << ")
\r\n"; s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << (double) i2p::transport::transports.GetOutBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; + s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << ")
\r\n"; s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; + s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << ")
\r\n"; s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; s << "
"; if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { s << "\r\n\r\n
\r\n"; } - if (includeHiddenContent) + if (includeHiddenContent) { s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) @@ -312,9 +318,10 @@ namespace http { s << ""<< tr("Our external address") << ":" << "
\r\n\r\n"; auto addresses = i2p::context.GetRouterInfo().GetAddresses (); if (addresses) - { + { for (const auto& address : *addresses) { + if (!address) continue; s << "\r\n\r\n"; if (address->published) - s << "\r\n"; + s << "\r\n"; else { - s << "\r\n"; } s << "\r\n"; } - } + } s << "
"; switch (address->transportStyle) { @@ -327,24 +334,25 @@ namespace http { default: s << tr("Unknown"); } - if (address->IsV6 ()) + bool v6 = address->IsV6 (); + if (v6) { if (address->IsV4 ()) s << "v4"; s << "v6"; } s << "" << address->host.to_string() << ":" << address->port << "" << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":") << address->port << "" << tr("supported"); + s << "" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported"); if (address->port) s << " :" << address->port; s << "
\r\n"; } s << "
\r\n
\r\n"; @@ -465,7 +473,7 @@ namespace http { } s << "⇒ " << it->GetTunnelID () << ":me"; if (it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; + s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "\r\n"; } @@ -485,22 +493,26 @@ namespace http { ); } if (it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "\r\n"; } } s << "
\r\n"; - s << "" << tr("Tags") << "
\r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; + s << "" << tr("Tags") << "
\r\n" + << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } - s << "
\r\n\r\n" - << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; + s << "
\r\n" + << "\r\n" + << "
\r\n" + << "\r\n\r\n" + << "\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; } else s << tr("Outgoing") << ": 0
\r\n"; s << "
\r\n"; @@ -515,8 +527,11 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } - s << "
\r\n\r\n" - << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; + s << "
\r\n" + << "\r\n" + << "
\r\n\r\n" + << "\r\n" + << "\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; } else s << tr("Tags sessions") << ": 0
\r\n"; s << "
\r\n"; @@ -539,7 +554,7 @@ namespace http { << tr("Streams") << "\r\n\r\n" << "StreamID" - << "" // Stream closing button column + << " " // Stream closing button column << "Destination" << "Sent" << "Received" @@ -610,7 +625,10 @@ namespace http { if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); else - ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + { + ls.reset (new i2p::data::LeaseSet2 (storeType)); + ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), false); + } if (!ls) return; s << "
IsExpired()) @@ -641,7 +659,7 @@ namespace http { } else if (!i2p::context.IsFloodfill ()) { - s << "" << tr("LeaseSets") << ": " << tr("not floodfill") << ".
\r\n"; + s << "" << tr("LeaseSets") << ": " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".
\r\n"; } else { @@ -670,7 +688,7 @@ namespace http { } s << "⇒ " << it->GetTunnelID () << ":me"; if (it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
\r\n"; } @@ -690,7 +708,7 @@ namespace http { ); } if (it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "\r\n"; } @@ -728,19 +746,20 @@ namespace http { s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; + auto loglevel = i2p::log::Logger().GetLogLevel(); s << "" << tr("Logging level") << "
\r\n"; - s << " none \r\n"; - s << " error \r\n"; - s << " warn \r\n"; - s << " info \r\n"; - s << " debug
\r\n
\r\n"; + s << " none \r\n"; + s << " error \r\n"; + s << " warn \r\n"; + s << " info \r\n"; + s << " debug
\r\n
\r\n"; uint16_t maxTunnels = GetMaxNumTransitTunnels (); s << "" << tr("Transit tunnels limit") << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; - s << " \r\n"; + s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n"; @@ -770,56 +789,70 @@ namespace http { { if (i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "" << tr("Transit Tunnels") << ":
\r\n
\r\n"; + s << "" << tr("Transit Tunnels") << ":
\r\n"; + s << ""; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { - s << "
\r\n"; if (std::dynamic_pointer_cast(it)) - s << it->GetTunnelID () << " ⇒ "; + s << "
\r\n"; } - s << "\r\n"; + s << "
ID" << tr("Amount") << "
" << it->GetTunnelID () << ""; else if (std::dynamic_pointer_cast(it)) - s << " ⇒ " << it->GetTunnelID (); + s << "
" << it->GetTunnelID () << ""; else - s << " ⇒ " << it->GetTunnelID () << " ⇒ "; - s << " " << it->GetNumTransmittedBytes () << "\r\n"; + s << "
" << it->GetTunnelID () << ""; + ShowTraffic(s, it->GetNumTransmittedBytes ()); + s << "
\r\n"; } else { - s << "" << tr("Transit Tunnels") << ": " << tr("no transit tunnels currently built") << ".
\r\n"; + s << "" << tr("Transit Tunnels") << ": " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".
\r\n"; } } template static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) { - std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; - for (const auto& it: sessions ) + auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) + { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; + std::set sortedSessions(comp); + for (const auto& it : sessions) { - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second && it.second->IsEstablished () && endpoint.address ().is_v4 ()) + auto ret = sortedSessions.insert(it.second); + if (!ret.second) + LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); + } + std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; + for (const auto& it: sortedSessions) + { + auto endpoint = it->GetRemoteEndpoint (); + if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) { tmp_s << "
\r\n"; - if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + if (it->IsOutgoing ()) tmp_s << " ⇒ "; + tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - tmp_s << " [itag:" << it.second->GetRelayTag () << "]"; + if (!it->IsOutgoing ()) tmp_s << " ⇒ "; + tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; + if (it->GetRelayTag ()) + tmp_s << " [itag:" << it->GetRelayTag () << "]"; + if (it->GetSendQueueSize () > 0) + tmp_s << " [queue:" << it->GetSendQueueSize () << "]"; tmp_s << "
\r\n" << std::endl; cnt++; } - if (it.second && it.second->IsEstablished () && endpoint.address ().is_v6 ()) + if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) { tmp_s6 << "
\r\n"; - if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + if (it->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); - if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - tmp_s6 << " [itag:" << it.second->GetRelayTag () << "]"; + if (!it->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; + if (it->GetRelayTag ()) + tmp_s6 << " [itag:" << it->GetRelayTag () << "]"; + if (it->GetSendQueueSize () > 0) + tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]"; tmp_s6 << "
\r\n" << std::endl; cnt6++; } @@ -879,7 +912,7 @@ namespace http { s << "
\r\n"; } else - s << "" << tr("SAM sessions") << ": " << tr("no sessions currently running") << ".
\r\n"; + s << "" << tr("SAM sessions") << ": " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".
\r\n"; } void ShowSAMSession (std::stringstream& s, const std::string& id) @@ -988,7 +1021,7 @@ namespace http { for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; @@ -1198,7 +1231,7 @@ namespace http { url.parse_query(params); std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = "5; url=" + webroot + "?page=commands"; + std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands"; std::string token = params["token"]; if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) @@ -1272,20 +1305,20 @@ namespace http { s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected in 5 seconds") << ""; - redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; } else if (cmd == HTTP_COMMAND_LIMITTRANSIT) { uint32_t limit = std::stoul(params["limit"], nullptr); - if (limit > 0 && limit <= 65535) + if (limit > 0 && limit <= TRANSIT_TUNNELS_LIMIT) SetMaxNumTransitTunnels (limit); else { - s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
\r\n
\r\n"; + s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n
\r\n
\r\n"; s << "" << tr("Back to commands list") << "\r\n
\r\n"; - s << "

" << tr("You will be redirected in 5 seconds") << ""; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; res.add_header("Refresh", redirect.c_str()); return; } @@ -1360,7 +1393,7 @@ namespace http { s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

\r\n"; s << "" << tr("Back to commands list") << "
\r\n"; - s << "

" << tr("You will be redirected in 5 seconds") << ""; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; res.add_header("Refresh", redirect.c_str()); } diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 8646253f..f41d925d 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,6 +24,8 @@ namespace http { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds + const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds + const int TRANSIT_TUNNELS_LIMIT = 65535; class HTTPConnection: public std::enable_shared_from_this { diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h index 0acbe8d1..1e5b6f75 100644 --- a/daemon/HTTPServerResources.h +++ b/daemon/HTTPServerResources.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -38,7 +38,7 @@ namespace http "@media (prefers-color-scheme: dark) { :root { --main-bg-color: #242424; --main-text-color: #17ab5c; --main-link-color: #bf64b7; --main-link-hover-color: #000000; } }\r\n" "body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: var(--main-bg-color); color: var(--main-text-color); }\r\n" "a, .slide label { text-decoration: none; color: var(--main-link-color); }\r\n" - "a:hover, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n" + "a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n" "a.button { appearance: button; text-decoration: none; padding: 0 5px; border: 1px solid var(--main-link-color); }\r\n" ".header { font-size: 2.5em; text-align: center; margin: 1em 0; color: var(--main-link-color); }\r\n" ".wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n" diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index b7182055..dbaf864a 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -163,7 +163,7 @@ namespace transport if (!a) return; for (const auto& address : *a) { - if (!address->host.is_v6 () && address->port) + if (address && !address->host.is_v6 () && address->port) TryPortMapping (address); } m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes @@ -215,7 +215,7 @@ namespace transport if (!a) return; for (const auto& address : *a) { - if (!address->host.is_v6 () && address->port) + if (address && !address->host.is_v6 () && address->port) CloseMapping (address); } } diff --git a/debian/changelog b/debian/changelog index accbf46a..13691601 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,38 @@ +i2pd (2.46.1-2) unstable; urgency=critical + + * re-pushed release due to new critical bug + + -- r4sas Mon, 20 Feb 2023 23:40:00 +0000 + +i2pd (2.46.1-1) unstable; urgency=high + + * updated to version 2.46.1/0.9.57 + + -- r4sas Mon, 20 Feb 2023 02:45:00 +0000 + +i2pd (2.46.0-1) unstable; urgency=high + + * updated to version 2.46.0/0.9.57 + + -- orignal Wed, 15 Feb 2023 19:00:00 +0000 + +i2pd (2.45.1-1) unstable; urgency=medium + + * updated to version 2.45.1/0.9.57 + + -- orignal Wed, 11 Jan 2023 19:00:00 +0000 + +i2pd (2.45.0-1) unstable; urgency=high + + * updated to version 2.45.0/0.9.57 + * compat level 12 + * standards version 4.3.0 + * increased nofile limit in service and init.d to 8192 + * added conffiles + * removed #1210 patch + + -- r4sas Tue, 3 Jan 2023 18:00:00 +0000 + i2pd (2.44.0-1) unstable; urgency=medium * updated to version 2.44.0/0.9.56 diff --git a/debian/compat b/debian/compat index ec635144..48082f72 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 +12 diff --git a/debian/control b/debian/control index 318463bc..48f5e680 100644 --- a/debian/control +++ b/debian/control @@ -2,8 +2,8 @@ Source: i2pd Section: net Priority: optional Maintainer: r4sas -Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.8 +Build-Depends: debhelper (>= 12~), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev +Standards-Version: 4.3.0 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd diff --git a/debian/copyright b/debian/copyright index 73df9da0..352e260b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,18 +3,18 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2020 PurpleI2P +Copyright: 2013-2023 PurpleI2P License: BSD-3-clause Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen - 2016-2020 R4SAS + 2016-2023 R4SAS 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause - Copyright (c) 2013-2017, The PurpleI2P Project + Copyright (c) 2013-2023, The PurpleI2P Project . All rights reserved. . diff --git a/debian/i2pd.default b/debian/i2pd.default index bd1d073f..90392ede 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -8,4 +8,4 @@ I2PD_ENABLED="yes" DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this -ulimit -n 4096 +ulimit -n 8192 diff --git a/debian/i2pd.install b/debian/i2pd.install index 6eb6c8c2..93eee7a1 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,7 +1,6 @@ i2pd usr/sbin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ -contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/ contrib/apparmor/usr.sbin.i2pd etc/apparmor.d diff --git a/debian/i2pd.links b/debian/i2pd.links index a149967f..16558791 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,5 +1,4 @@ -etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf +etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf -etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d usr/share/i2pd/certificates var/lib/i2pd/certificates diff --git a/debian/patches/01-fix-1210.patch b/debian/patches/01-fix-1210.patch deleted file mode 100644 index 9ad9bb0f..00000000 --- a/debian/patches/01-fix-1210.patch +++ /dev/null @@ -1,27 +0,0 @@ -Description: fix #1210 - Disables two options, which not presented in old systemd versions -Author: r4sas - -Bug: https://github.com/PurpleI2P/i2pd/issues/1210 -Reviewed-By: r4sas -Last-Update: 2020-05-25 - -Index: i2pd/contrib/i2pd.service -=================================================================== ---- i2pd.orig/contrib/i2pd.service -+++ i2pd/contrib/i2pd.service -@@ -6,10 +6,10 @@ After=network.target - [Service] - User=i2pd - Group=i2pd --RuntimeDirectory=i2pd --RuntimeDirectoryMode=0700 --LogsDirectory=i2pd --LogsDirectoryMode=0700 -+#RuntimeDirectory=i2pd -+#RuntimeDirectoryMode=0700 -+#LogsDirectory=i2pd -+#LogsDirectoryMode=0700 - Type=forking - ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service - ExecReload=/bin/sh -c "kill -HUP $MAINPID" diff --git a/debian/patches/02-upnp.patch b/debian/patches/01-upnp.patch similarity index 100% rename from debian/patches/02-upnp.patch rename to debian/patches/01-upnp.patch diff --git a/debian/patches/series b/debian/patches/series index 2f816712..f97fdf65 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1 @@ -01-fix-1210.patch -02-upnp.patch +01-upnp.patch diff --git a/debian/rules b/debian/rules index 11791d9b..fc769066 100755 --- a/debian/rules +++ b/debian/rules @@ -8,6 +8,6 @@ export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic export DEB_LDFLAGS_MAINT_APPEND = %: - dh $@ --parallel + dh $@ override_dh_auto_install: diff --git a/debian/watch b/debian/watch index 64367c81..2ec6c29b 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,4 @@ -version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ +version=4 +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ https://github.com/PurpleI2P/i2pd/tags \ (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index 5860facf..b582a06a 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -64,10 +64,10 @@ namespace afrikaans // language namespace static std::map> plurals { - {"days", {"dag", "dae"}}, - {"hours", {"uur", "ure"}}, - {"minutes", {"minuut", "minute"}}, - {"seconds", {"seconde", "sekondes"}}, + {"%d days", {"%d dag", "%d dae"}}, + {"%d hours", {"%d uur", "%d ure"}}, + {"%d minutes", {"%d minuut", "%d minute"}}, + {"%d seconds", {"%d seconde", "%d sekondes"}}, {"", {"", ""}}, }; diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp index 586e7579..b99e5032 100644 --- a/i18n/Armenian.cpp +++ b/i18n/Armenian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,15 +31,16 @@ namespace armenian // language namespace static std::map strings { - {"KiB", "ԿիԲ"}, - {"MiB", "ՄիԲ"}, - {"GiB", "ԳիԲ"}, + {"%.2f KiB", "%.2f ԿիԲ"}, + {"%.2f MiB", "%.2f ՄիԲ"}, + {"%.2f GiB", "%.2f ԳիԲ"}, {"building", "կառուցվում է"}, {"failed", "Անհաջող"}, {"expiring", "Լրանում է"}, {"established", "կարգավոյված է"}, {"unknown", "անհայտ"}, {"exploratory", "հետազոտոկան"}, + {"Purple I2P Webconsole", "Վեբ-կոնսոլ Purple I2P"}, {"i2pd webconsole", "Վեբ-կոնսոլ i2pd"}, {"Main page", "Գլխավոր էջ"}, {"Router commands", "Երթուղիչի հրահանգներ"}, @@ -57,10 +58,10 @@ namespace armenian // language namespace {"Unknown", "Անհայտ"}, {"Proxy", "Պրոկսի"}, {"Mesh", "MESH-ցանց"}, - {"Error", "Սխալ"}, {"Clock skew", "Ոչ ճշգրիտ ժամանակ"}, {"Offline", "Օֆլայն"}, {"Symmetric NAT", "Սիմետրիկ NAT"}, + {"Full cone NAT", "Full cone NAT"}, {"Uptime", "Առկայություն"}, {"Network status", "Ցանցի կարգավիճակ"}, {"Network status v6", "Ցանցի կարգավիճակ v6"}, @@ -68,7 +69,7 @@ namespace armenian // language namespace {"Family", "Խմբատեսակ"}, {"Tunnel creation success rate", "Հաջողությամբ կառուցված թունելներ"}, {"Received", "Ստացվել է"}, - {"KiB/s", "ԿիԲ/վ"}, + {"%.2f KiB/s", "%.2f ԿիԲ/վ"}, {"Sent", "Ուղարկվել է"}, {"Transit", "Տարանցում"}, {"Data path", "Տվյալների ուղին"}, @@ -89,12 +90,12 @@ namespace armenian // language namespace {"Address registration line", "Հասցեի գրանցման տող"}, {"Domain", "Տիրույթ"}, {"Generate", "Գեներացնել"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Նշում. արդյունքի տողը կարող է օգտագործվել միայն 2LD տիրույթներ գրանցելու համար (example.i2p): Ենթատիրույթներ գրանցելու համար խնդրում ենք օգտագործել i2pd-tools գործիքակազմը"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Նշում. արդյունքի տողը կարող է օգտագործվել միայն 2LD տիրույթներ գրանցելու համար (example.i2p): Ենթատիրույթներ գրանցելու համար խնդրում ենք օգտագործել i2pd-tools գործիքակազմը:"}, {"Address", "Հասցե"}, {"Type", "Տեսակը"}, {"EncType", "Գաղտնագրի տեսակը"}, {"Inbound tunnels", "Մուտքային թունելներ"}, - {"ms", "մլվ"}, + {"%dms", "%dմլվ"}, {"Outbound tunnels", "Ելքային թունելներ"}, {"Tags", "Թեգեր"}, {"Incoming", "Մուտքային"}, @@ -112,11 +113,10 @@ namespace armenian // language namespace {"Invalid", "Անվավեր"}, {"Store type", "Պահեստավորման տեսակը"}, {"Expires", "Սպառվում է"}, - {"Non Expired Leases", "Չսպառված Lease-եր"}, + {"Non Expired Leases", "Չսպառված Lease-եր"}, {"Gateway", "Դարպաս"}, {"TunnelID", "Թունելի ID"}, {"EndDate", "Ավարտ"}, - {"not floodfill", "ոչ floodfill-ներ"}, {"Queue size", "Հերթի չափսը"}, {"Run peer test", "Գործարկել փորձարկումը"}, {"Decline transit tunnels", "Մերժել տարանցիկ թունելներ"}, @@ -146,10 +146,7 @@ namespace armenian // language namespace {"Destination not found", "Հասցեի վայրը չի գտնվել"}, {"StreamID can't be null", "StreamID-ն չի կարող լինել դատարկ"}, {"Return to destination page", "Վերադառնալ նախորդ էջի հասցե"}, - {"You will be redirected in 5 seconds", "Դուք կտեղափոխվեք 5 վայրկյանից"}, - {"Transit tunnels count must not exceed 65535", "Տարանցիկ թունելների քանակը չպետք է գերազանցի 65535-ը"}, {"Back to commands list", "Վերադառնալ հրահանգների ցուցակ"}, - {"Register at reg.i2p", "Գրանցել reg.i2p-ում"}, {"Description", "Նկարագրություն"}, {"A bit information about service on domain", "Մի փոքր տեղեկատվություն տիրոիյթում գտնվող ծառայության մասին"}, {"Submit", "Ուղարկվել"}, @@ -165,43 +162,35 @@ namespace armenian // language namespace {"You may try to find this host on jump services below", "Ստորև Դուք կարող եք գտնել այս հոսթը jump ծառայությունների միջոցով"}, {"Invalid request", "Սխալ հարցում"}, {"Proxy unable to parse your request", "Պրոկսին չի կարող հասկանալ Ձեր հարցումը"}, - {"addresshelper is not supported", "addresshelper-ը համատեղելի չէ"}, - {"Host", "Հոսթ"}, - {"added to router's addressbook from helper", "Ավելացված է երթուղիչի հասցեագրքում helper-ի միջոցով"}, - {"Click here to proceed:", "Շարունակելու համար սեղմեք այստեղ"}, - {"Continue", "Շարունակել"}, - {"Addresshelper found", "addresshelper-ը գնտված է"}, - {"already in router's addressbook", "արդեն առկա է երթուղիչի հասցեագրքում"}, - {"Click here to update record:", "Սեղմեկ այստեղ որպեսզի թարվացնեք գրառումը"}, - {"invalid request uri", "Սխալ ձևավորված URI հարցում"}, + {"Invalid request URI", "Սխալ ձևավորված URI հարցում"}, {"Can't detect destination host from request", "Չհաջողվեց հայնտաբերեկ վայրի հասցեն նշված հարցմամբ"}, {"Outproxy failure", "Սխալ արտաքին պրոքսի"}, - {"bad outproxy settings", "Սխալ արտաքին պրոկսի կարգավորումներ"}, - {"not inside I2P network, but outproxy is not enabled", "Հարցումը I2P ցանցից դուրս է, բայց արտաքին պրոքսին միացված չէ"}, - {"unknown outproxy url", "արտաքին պրոքսիի անհայտ URL"}, - {"cannot resolve upstream proxy", "Չհաջողվեց որոշել վերադաս պրոկսին"}, - {"hostname too long", "Հոսթի անունը չափազանց երկար է"}, - {"cannot connect to upstream socks proxy", "չհաջողվեց միանալ վերադաս socks պրոկսիին"}, - {"Cannot negotiate with socks proxy", "Չհաջողվեց պայմանավորվել վերադաս socks պրոկսիի հետ"}, + {"Bad outproxy settings", "Սխալ արտաքին պրոկսի կարգավորումներ"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Հոսթ %s Հարցումը I2P ցանցից դուրս է, բայց արտաքին պրոքսին միացված չէ"}, + {"Unknown outproxy URL", "Արտաքին պրոքսիի անհայտ URL"}, + {"Cannot resolve upstream proxy", "Չհաջողվեց որոշել վերադաս պրոկսին"}, + {"Hostname is too long", "Հոսթի անունը չափազանց երկար է"}, + {"Cannot connect to upstream SOCKS proxy", "Չհաջողվեց միանալ վերադաս SOCKS պրոկսի սերվերին"}, + {"Cannot negotiate with SOCKS proxy", "Չհաջողվեց պայմանավորվել վերադաս SOCKS պրոկսիի հետ"}, {"CONNECT error", "Սխալ CONNECT հարցում"}, - {"Failed to Connect", "Միանալ չhաջողվեց"}, - {"socks proxy error", "Սխալ SOCKS պրոկսի"}, - {"failed to send request to upstream", "Չհաջողվեց հարցումն ուղարկել վերադաս պրոկսիին"}, - {"No Reply From socks proxy", "Բացակայում է պատասխանը SOCKS պրոկսի սերվերի կողմից"}, - {"cannot connect", "Հնարավոր չե միանալ"}, - {"http out proxy not implemented", "Արտաքին http պրոկսին դեռ իրականացված չէ"}, - {"cannot connect to upstream http proxy", "Չհաջողվեց միանալ վերադաս http պրոկսի սերվերին"}, + {"Failed to connect", "Միանալ չhաջողվեց"}, + {"SOCKS proxy error", "Սխալ SOCKS պրոկսի"}, + {"Failed to send request to upstream", "Չհաջողվեց հարցումն ուղարկել վերադաս պրոկսիին"}, + {"No reply from SOCKS proxy", "Բացակայում է պատասխանը SOCKS պրոկսի սերվերի կողմից"}, + {"Cannot connect", "Հնարավոր չե միանալ"}, + {"HTTP out proxy not implemented", "Արտաքին HTTP պրոկսին դեռ իրականացված չէ"}, + {"Cannot connect to upstream HTTP proxy", "Չհաջողվեց միանալ վերադաս HTTP պրոկսի սերվերին"}, {"Host is down", "Հոսթն անհասանելի է"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Հոսթի հետ կապը հաստատել չհաջողվեց, հնարավոր է այն անջատված է, փորձեք միանալ քիչ ուշ"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Հոսթի հետ կապը հաստատել չհաջողվեց, հնարավոր է այն անջատված է, փորձեք միանալ քիչ ուշ:"}, {"", ""}, }; static std::map> plurals { - {"days", {"օր", "օր"}}, - {"hours", {"ժամ", "ժամ"}}, - {"minutes", {"րոպե", "րոպե"}}, - {"seconds", {"վարկյան", "վարկյան"}}, + {"%d days", {"%d օր", "%d օր"}}, + {"%d hours", {"%d ժամ", "%d ժամ"}}, + {"%d minutes", {"%d րոպե", "%d րոպե"}}, + {"%d seconds", {"%d վարկյան", "%d վարկյան"}}, {"", {"", ""}}, }; diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp index 2f6c14e2..cd38fa0f 100644 --- a/i18n/Chinese.cpp +++ b/i18n/Chinese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,7 +13,6 @@ #include "I18N.h" // Simplified Chinese localization file -// This is an example translation file without strings in it. namespace i2p { @@ -32,9 +31,9 @@ namespace chinese // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "正在构建"}, {"failed", "连接失败"}, {"expiring", "即将过期"}, @@ -46,7 +45,7 @@ namespace chinese // language namespace {"Main page", "主页"}, {"Router commands", "路由命令"}, {"Local Destinations", "本地目标"}, - {"LeaseSets", "租契集"}, + {"LeaseSets", "租约集"}, {"Tunnels", "隧道"}, {"Transit Tunnels", "中转隧道"}, {"Transports", "传输"}, @@ -58,11 +57,12 @@ namespace chinese // language namespace {"Firewalled", "受到防火墙限制"}, {"Unknown", "未知"}, {"Proxy", "代理"}, - {"Mesh", "Mesh组网"}, - {"Error", "错误"}, + {"Mesh", "自组网"}, {"Clock skew", "时钟偏移"}, {"Offline", "离线"}, {"Symmetric NAT", "对称 NAT"}, + {"Full cone NAT", "全锥型NAT"}, + {"No Descriptors", "无描述符"}, {"Uptime", "运行时间"}, {"Network status", "IPv4 网络状态"}, {"Network status v6", "IPv6 网络状态"}, @@ -70,7 +70,7 @@ namespace chinese // language namespace {"Family", "家族"}, {"Tunnel creation success rate", "隧道创建成功率"}, {"Received", "已接收"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "已发送"}, {"Transit", "中转"}, {"Data path", "数据文件路径"}, @@ -91,12 +91,12 @@ namespace chinese // language namespace {"Address registration line", "地址域名注册"}, {"Domain", "域名"}, {"Generate", "生成"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "注意: 结果字符串只能用于注册次级域名(例如:example.i2p)。若需注册子域名,请使用 i2pd-tools。"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "注意: 结果字符串只能用于注册二级域名(例如:example.i2p)。若需注册三级域名,请使用 i2pd-tools。"}, {"Address", "地址"}, {"Type", "类型"}, {"EncType", "加密类型"}, {"Inbound tunnels", "入站隧道"}, - {"ms", "毫秒"}, + {"%dms", "%dms"}, {"Outbound tunnels", "出站隧道"}, {"Tags", "标签"}, {"Incoming", "传入"}, @@ -118,9 +118,10 @@ namespace chinese // language namespace {"Gateway", "网关"}, {"TunnelID", "隧道 ID"}, {"EndDate", "结束日期"}, - {"not floodfill", "非洪泛"}, + {"floodfill mode is disabled", "洪泛已禁用"}, {"Queue size", "队列大小"}, {"Run peer test", "运行节点测试"}, + {"Reload tunnels configuration", "重新载入隧道配置"}, {"Decline transit tunnels", "拒绝中转隧道"}, {"Accept transit tunnels", "允许中转隧道"}, {"Cancel graceful shutdown", "取消平滑关闭"}, @@ -128,7 +129,7 @@ namespace chinese // language namespace {"Force shutdown", "强制停止"}, {"Reload external CSS styles", "重载外部 CSS 样式"}, {"Note: any action done here are not persistent and not changes your config files.", "注意: 此处完成的任何操作都不是永久的,不会更改您的配置文件。"}, - {"Logging level", "日志记录级别"}, + {"Logging level", "日志级别"}, {"Transit tunnels limit", "中转隧道限制"}, {"Change", "修改"}, {"Change language", "更改语言"}, @@ -148,8 +149,8 @@ namespace chinese // language namespace {"Destination not found", "找不到目标"}, {"StreamID can't be null", "StreamID 不能为空"}, {"Return to destination page", "返回目标页面"}, - {"You will be redirected in 5 seconds", "您将在5秒内被重定向"}, - {"Transit tunnels count must not exceed 65535", "中转隧道数量不能超过 65535"}, + {"You will be redirected in %d seconds", "您将在%d秒内被重定向"}, + {"Transit tunnels count must not exceed %d", "中转隧道数量限制为 %d"}, {"Back to commands list", "返回命令列表"}, {"Register at reg.i2p", "在 reg.i2p 注册域名"}, {"Description", "描述"}, @@ -164,35 +165,36 @@ namespace chinese // language namespace {"Proxy info", "代理信息"}, {"Proxy error: Host not found", "代理错误:未找到主机"}, {"Remote host not found in router's addressbook", "在路由地址簿中未找到远程主机"}, - {"You may try to find this host on jump services below", "您可以尝试在下方的跳转服务中找到该主机"}, + {"You may try to find this host on jump services below", "您可以尝试在下方的跳转服务中找到此主机"}, {"Invalid request", "无效请求"}, {"Proxy unable to parse your request", "代理无法解析您的请求"}, - {"addresshelper is not supported", "不支持地址助手"}, - {"Host", "主机"}, - {"added to router's addressbook from helper", "将此地址从地址助手添加到路由地址簿"}, - {"Click here to proceed:", "点击此处继续:"}, - {"Continue", "继续"}, - {"Addresshelper found", "已找到地址助手"}, - {"already in router's addressbook", "已在路由地址簿中"}, - {"Click here to update record:", "点击此处更新地址簿记录"}, - {"invalid request uri", "无效的 URL 请求"}, + {"Addresshelper is not supported", "不支持地址助手"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "主机 %s 已在路由地址簿中请注意:此地址的来源可能是有害的!点击此处更新记录:继续"}, + {"Addresshelper forced update rejected", "地址助手强制更新被拒绝"}, + {"To add host %s in router's addressbook, click here: Continue.", "若要在路由器地址簿中添加主机 %s 请点击这里: 继续"}, + {"Addresshelper request", "请求地址助手"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "主机 %s 已通过地址助手添加到路由地址簿中。点击此处继续:继续"}, + {"Addresshelper adding", "正在添加地址助手"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "主机 %s 已在路由地址簿中。点击此处更新记录:继续"}, + {"Addresshelper update", "更新地址助手"}, + {"Invalid request URI", "无效的 URI 请求"}, {"Can't detect destination host from request", "无法从请求中检测到目标主机"}, {"Outproxy failure", "出口代理故障"}, - {"bad outproxy settings", "错误的出口代理设置"}, - {"not inside I2P network, but outproxy is not enabled", "该地址不在 I2P 网络内,但未启用出口代理"}, - {"unknown outproxy url", "未知的出口代理地址"}, - {"cannot resolve upstream proxy", "无法解析上游代理"}, - {"hostname too long", "主机名过长"}, - {"cannot connect to upstream socks proxy", "无法连接到上游 socks 代理"}, - {"Cannot negotiate with socks proxy", "无法与 socks 代理协商"}, + {"Bad outproxy settings", "错误的出口代理设置"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "主机 %s 不在 I2P 网络内,但出口代理未启用"}, + {"Unknown outproxy URL", "未知的出口代理地址"}, + {"Cannot resolve upstream proxy", "无法解析上游代理"}, + {"Hostname is too long", "主机名过长"}, + {"Cannot connect to upstream SOCKS proxy", "无法连接到上游 SOCKS 代理"}, + {"Cannot negotiate with SOCKS proxy", "无法与 SOCKS 代理协商"}, {"CONNECT error", "连接错误"}, - {"Failed to Connect", "连接失败"}, - {"socks proxy error", "socks 代理错误"}, - {"failed to send request to upstream", "向上游发送请求失败"}, - {"No Reply From socks proxy", "没有来自 socks 代理的回复"}, - {"cannot connect", "无法连接"}, - {"http out proxy not implemented", "http 出口代理未实现"}, - {"cannot connect to upstream http proxy", "无法连接到上游 http 代理"}, + {"Failed to connect", "连接失败"}, + {"SOCKS proxy error", "SOCKS 代理错误"}, + {"Failed to send request to upstream", "向上游发送请求失败"}, + {"No reply from SOCKS proxy", "没有来自 SOCKS 代理的回复"}, + {"Cannot connect", "无法连接"}, + {"HTTP out proxy not implemented", "HTTP 出口代理未实现"}, + {"Cannot connect to upstream HTTP proxy", "无法连接到上游 HTTP 代理"}, {"Host is down", "主机已关闭"}, {"Can't create connection to requested host, it may be down. Please try again later.", "无法创建到目标主机的连接。主机可能已下线,请稍后再试。"}, {"", ""}, @@ -200,10 +202,10 @@ namespace chinese // language namespace static std::map> plurals { - {"days", {"日"}}, - {"hours", {"时"}}, - {"minutes", {"分"}}, - {"seconds", {"秒"}}, + {"%d days", {"%d 天"}}, + {"%d hours", {"%d 小时"}}, + {"%d minutes", {"%d 分钟"}}, + {"%d seconds", {"%d 秒"}}, {"", {""}}, }; diff --git a/i18n/Czech.cpp b/i18n/Czech.cpp new file mode 100644 index 00000000..09a56d50 --- /dev/null +++ b/i18n/Czech.cpp @@ -0,0 +1,204 @@ +/* +* Copyright (c) 2022-2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Czech localization file + +namespace i2p +{ +namespace i18n +{ +namespace czech // language namespace +{ + // language name in lowercase + static std::string language = "czech"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "vytváří se"}, + {"failed", "selhalo"}, + {"expiring", "končící"}, + {"established", "vytvořeno"}, + {"unknown", "neznámý"}, + {"exploratory", "průzkumné"}, + {"Purple I2P Webconsole", "Purple I2P Webkonsole"}, + {"i2pd webconsole", "i2pd webkonsole"}, + {"Main page", "Hlavní stránka"}, + {"Router commands", "Router příkazy"}, + {"Local Destinations", "Lokální destinace"}, + {"LeaseSets", "LeaseSety"}, + {"Tunnels", "Tunely"}, + {"Transit Tunnels", "Transitní tunely"}, + {"Transports", "Transporty"}, + {"I2P tunnels", "I2P tunely"}, + {"SAM sessions", "SAM relace"}, + {"ERROR", "CHYBA"}, + {"OK", "OK"}, + {"Testing", "Testuji"}, + {"Firewalled", "Za Firewallem"}, + {"Unknown", "Neznámý"}, + {"Proxy", "Proxy"}, + {"Mesh", "Síť"}, + {"Clock skew", "Časová nesrovnalost"}, + {"Offline", "Offline"}, + {"Symmetric NAT", "Symetrický NAT"}, + {"Uptime", "Doba provozu"}, + {"Network status", "Status sítě"}, + {"Network status v6", "Status sítě v6"}, + {"Stopping in", "Zastavuji za"}, + {"Family", "Rodina"}, + {"Tunnel creation success rate", "Úspěšnost vytváření tunelů"}, + {"Received", "Přijato"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Odesláno"}, + {"Transit", "Tranzit"}, + {"Data path", "Cesta k data souborům"}, + {"Hidden content. Press on text to see.", "Skrytý kontent. Pro zobrazení, klikni na text."}, + {"Router Ident", "Routerová Identita"}, + {"Router Family", "Rodina routerů"}, + {"Router Caps", "Omezení Routerů"}, + {"Version", "Verze"}, + {"Our external address", "Naše externí adresa"}, + {"supported", "podporováno"}, + {"Routers", "Routery"}, + {"Floodfills", "Floodfilly"}, + {"Client Tunnels", "Klientské tunely"}, + {"Services", "Služby"}, + {"Enabled", "Zapnuto"}, + {"Disabled", "Vypnuto"}, + {"Encrypted B33 address", "Šifrovaná adresa B33"}, + {"Address registration line", "Registrační řádek adresy"}, + {"Domain", "Doména"}, + {"Generate", "Vygenerovat"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Poznámka: výsledný řetězec může být použit pouze pro registraci 2LD domén (example.i2p). Pro registraci subdomén použijte prosím i2pd-tools."}, + {"Address", "Adresa"}, + {"Type", "Typ"}, + {"EncType", "EncType"}, + {"Inbound tunnels", "Příchozí tunely"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Odchozí tunely"}, + {"Tags", "Štítky"}, + {"Incoming", "Příchozí"}, + {"Outgoing", "Odchozí"}, + {"Destination", "Destinace"}, + {"Amount", "Množství"}, + {"Incoming Tags", "Příchozí štítky"}, + {"Tags sessions", "Relace štítků"}, + {"Status", "Status"}, + {"Local Destination", "Lokální Destinace"}, + {"Streams", "Toky"}, + {"Close stream", "Uzavřít tok"}, + {"I2CP session not found", "I2CP relace nenalezena"}, + {"I2CP is not enabled", "I2CP není zapnuto"}, + {"Invalid", "Neplatný"}, + {"Store type", "Druh uložení"}, + {"Expires", "Vyprší"}, + {"Non Expired Leases", "Nevypršené Leasy"}, + {"Gateway", "Brána"}, + {"TunnelID", "ID tunelu"}, + {"EndDate", "Datum ukončení"}, + {"Queue size", "Velikost fronty"}, + {"Run peer test", "Spustit peer test"}, + {"Decline transit tunnels", "Odmítnout tranzitní tunely"}, + {"Accept transit tunnels", "Přijmout tranzitní tunely"}, + {"Cancel graceful shutdown", "Zrušit hladké vypnutí"}, + {"Start graceful shutdown", "Zahájit hladké vypnutí"}, + {"Force shutdown", "Vynutit vypnutí"}, + {"Reload external CSS styles", "Znovu načíst externí CSS"}, + {"Note: any action done here are not persistent and not changes your config files.", "Poznámka: žádná vykonaná akce zde není trvalá a nemění konfigurační soubory."}, + {"Logging level", "Úroveň logování"}, + {"Transit tunnels limit", "Limit tranzitních tunelů"}, + {"Change", "Změnit"}, + {"Change language", "Změnit jazyk"}, + {"no transit tunnels currently built", "Žádný tranzitní tunel není momentálně vytvořen"}, + {"SAM disabled", "SAM vypnutý"}, + {"no sessions currently running", "Momentálně nejsou spuštěné žádné relace"}, + {"SAM session not found", "SAM relace nenalezena"}, + {"SAM Session", "SAM Relace"}, + {"Server Tunnels", "Server Tunely"}, + {"Client Forwards", "Přesměrování Klienta"}, + {"Server Forwards", "Přesměrování Serveru"}, + {"Unknown page", "Neznámá stránka"}, + {"Invalid token", "Neplatný token"}, + {"SUCCESS", "ÚSPĚCH"}, + {"Stream closed", "Tok uzavřen"}, + {"Stream not found or already was closed", "Tok nenalezen nebo byl již uzavřen"}, + {"Destination not found", "Destinace nenalezena"}, + {"StreamID can't be null", "StreamID nemůže být null"}, + {"Return to destination page", "Zpět na stránku destinací"}, + {"Back to commands list", "Zpět na list příkazů"}, + {"Register at reg.i2p", "Zaregistrovat na reg.i2p"}, + {"Description", "Popis"}, + {"A bit information about service on domain", "Trochu informací o službě na doméně"}, + {"Submit", "Odeslat"}, + {"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"}, + {"Domain must end with .i2p", "Doména musí končit s .i2p"}, + {"Such destination is not found", "Takováto destinace nebyla nalezena"}, + {"Unknown command", "Neznámý příkaz"}, + {"Command accepted", "Příkaz přijat"}, + {"Proxy error", "Chyba proxy serveru"}, + {"Proxy info", "Proxy informace"}, + {"Proxy error: Host not found", "Chyba proxy serveru: Hostitel nenalezen"}, + {"Remote host not found in router's addressbook", "Vzdálený hostitel nebyl nalezen v adresáři routeru"}, + {"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"}, + {"Invalid request", "Neplatný požadavek"}, + {"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"}, + {"Invalid request URI", "Neplatný URI požadavek"}, + {"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"}, + {"Outproxy failure", "Outproxy selhání"}, + {"Bad outproxy settings", "Špatné outproxy nastavení"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Hostitel %s není uvnitř I2P sítě a outproxy není nastavena"}, + {"Unknown outproxy URL", "Neznámá outproxy URL"}, + {"Cannot resolve upstream proxy", "Nelze rozluštit upstream proxy server"}, + {"Hostname is too long", "Název hostitele je příliš dlouhý"}, + {"Cannot connect to upstream SOCKS proxy", "Nelze se připojit k upstream SOCKS proxy serveru"}, + {"Cannot negotiate with SOCKS proxy", "Nelze vyjednávat se SOCKS proxy serverem"}, + {"CONNECT error", "Chyba PŘIPOJENÍ"}, + {"Failed to connect", "Připojení se nezdařilo"}, + {"SOCKS proxy error", "Chyba SOCKS proxy serveru"}, + {"Failed to send request to upstream", "Odeslání žádosti upstream serveru se nezdařilo"}, + {"No reply from SOCKS proxy", "Žádná odpověď od SOCKS proxy serveru"}, + {"Cannot connect", "Nelze se připojit"}, + {"HTTP out proxy not implemented", "HTTP out proxy není implementován"}, + {"Cannot connect to upstream HTTP proxy", "Nelze se připojit k upstream HTTP proxy serveru"}, + {"Host is down", "Hostitel je nedostupný"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Připojení k požadovanému hostiteli nelze vytvořit, může být nedostupný. Zkuste to, prosím, znovu později."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}}, + {"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}}, + {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, + {"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}}, + {"", {"", "", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/French.cpp b/i18n/French.cpp index 1a49ddfc..0ce044e1 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,9 +31,9 @@ namespace french // language namespace static std::map strings { - {"KiB", "Kio"}, - {"MiB", "Mio"}, - {"GiB", "Gio"}, + {"%.2f KiB", "%.2f Kio"}, + {"%.2f MiB", "%.2f Mio"}, + {"%.2f GiB", "%.2f Gio"}, {"building", "En construction"}, {"failed", "échoué"}, {"expiring", "expiré"}, @@ -58,10 +58,11 @@ namespace french // language namespace {"Unknown", "Inconnu"}, {"Proxy", "Proxy"}, {"Mesh", "Maillé"}, - {"Error", "Erreur"}, {"Clock skew", "Horloge décalée"}, {"Offline", "Hors ligne"}, {"Symmetric NAT", "NAT symétrique"}, + {"Full cone NAT", "NAT à cône complet"}, + {"No Descriptors", "Aucuns Descripteurs"}, {"Uptime", "Temps de fonctionnement"}, {"Network status", "État du réseau"}, {"Network status v6", "État du réseau v6"}, @@ -69,7 +70,7 @@ namespace french // language namespace {"Family", "Famille"}, {"Tunnel creation success rate", "Taux de succès de création de tunnels"}, {"Received", "Reçu"}, - {"KiB/s", "kio/s"}, + {"%.2f KiB/s", "%.2f Kio/s"}, {"Sent", "Envoyé"}, {"Transit", "Transité"}, {"Data path", "Emplacement des données"}, @@ -81,6 +82,7 @@ namespace french // language namespace {"Our external address", "Notre adresse externe"}, {"supported", "supporté"}, {"Routers", "Routeurs"}, + {"Floodfills", "Remplisseurs"}, {"Client Tunnels", "Tunnels clients"}, {"Services", "Services"}, {"Enabled", "Activé"}, @@ -92,8 +94,9 @@ namespace french // language namespace {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Note: La chaîne résultante peut seulement être utilisée pour enregistrer les domaines 2LD (exemple.i2p). Pour enregistrer des sous-domaines, veuillez utiliser i2pd-tools."}, {"Address", "Adresse"}, {"Type", "Type"}, + {"EncType", "EncType"}, {"Inbound tunnels", "Tunnels entrants"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Tunnels sortants"}, {"Tags", "Balises"}, {"Incoming", "Entrant"}, @@ -115,8 +118,10 @@ namespace french // language namespace {"Gateway", "Passerelle"}, {"TunnelID", "ID du tunnel"}, {"EndDate", "Date de fin"}, + {"floodfill mode is disabled", "le mode de remplissage est désactivé"}, {"Queue size", "Longueur de la file"}, {"Run peer test", "Lancer test des pairs"}, + {"Reload tunnels configuration", "Recharger la configuration des tunnels"}, {"Decline transit tunnels", "Refuser les tunnels transitoires"}, {"Accept transit tunnels", "Accepter les tunnels transitoires"}, {"Cancel graceful shutdown", "Annuler l'arrêt gracieux"}, @@ -134,6 +139,8 @@ namespace french // language namespace {"SAM session not found", "session SAM introuvable"}, {"SAM Session", "Session SAM"}, {"Server Tunnels", "Tunnels serveurs"}, + {"Client Forwards", "Transmission du client"}, + {"Server Forwards", "Transmission du serveur"}, {"Unknown page", "Page inconnue"}, {"Invalid token", "Jeton invalide"}, {"SUCCESS", "SUCCÈS"}, @@ -142,8 +149,8 @@ namespace french // language namespace {"Destination not found", "Destination introuvable"}, {"StreamID can't be null", "StreamID ne peut pas être vide"}, {"Return to destination page", "Retourner à la page de destination"}, - {"You will be redirected in 5 seconds", "Vous allez être redirigé dans cinq secondes"}, - {"Transit tunnels count must not exceed 65535", "Le nombre de tunnels transitoires ne doit pas dépasser 65535"}, + {"You will be redirected in %d seconds", "Vous serez redirigé dans %d secondes"}, + {"Transit tunnels count must not exceed %d", "Le nombre de tunnels de transit ne doit pas excéder %d"}, {"Back to commands list", "Retour à la liste des commandes"}, {"Register at reg.i2p", "Inscription à reg.i2p"}, {"Description", "Description"}, @@ -161,32 +168,33 @@ namespace french // language namespace {"You may try to find this host on jump services below", "Vous pouvez essayer de trouver cet hôte sur des services de redirection ci-dessous"}, {"Invalid request", "Requête invalide"}, {"Proxy unable to parse your request", "Proxy incapable de comprendre votre requête"}, - {"addresshelper is not supported", "Assistant d'adresse non supporté"}, - {"Host", "Hôte"}, - {"added to router's addressbook from helper", "Ajouté au carnet d'adresse du routeur par l'assistant"}, - {"Click here to proceed:", "Cliquez ici pour continuer:"}, - {"Continue", "Continuer"}, - {"Addresshelper found", "Assistant d'adresse trouvé"}, - {"already in router's addressbook", "déjà dans le carnet d'adresses du routeur"}, - {"Click here to update record:", "Cliquez ici pour mettre à jour le carnet d'adresse:"}, - {"invalid request uri", "uri de la requête invalide"}, + {"Addresshelper is not supported", "Assistant d'adresse non supporté"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'hôte %s est déjà dans le carnet d'adresses du routeur. Attention : la source de cette URL peut être nuisible ! Cliquez ici pour mettre à jour l'enregistrement : Continuer."}, + {"Addresshelper forced update rejected", "Mise à jour forcée des assistants d'adresses rejetée"}, + {"To add host %s in router's addressbook, click here: Continue.", "Pour ajouter l'hôte %s au carnet d'adresses du routeur, cliquez ici : Continuer."}, + {"Addresshelper request", "Demande à l'assistant d'adresse"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'hôte %s a été ajouté au carnet d'adresses du routeur depuis l'assistant. Cliquez ici pour continuer : Continuer."}, + {"Addresshelper adding", "Ajout de l'assistant d'adresse"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'hôte %s est déjà dans le carnet d'adresses du routeur. Cliquez ici pour mettre à jour le dossier : Continuer."}, + {"Addresshelper update", "Mise à jour de l'assistant d'adresse"}, + {"Invalid request URI", "URI de la requête invalide"}, {"Can't detect destination host from request", "Impossible de détecter l'hôte de destination à partir de la requête"}, {"Outproxy failure", "Échec de proxy de sortie"}, - {"bad outproxy settings", "Mauvaise configuration du proxy de sortie"}, - {"not inside I2P network, but outproxy is not enabled", "pas dans le réseau I2P, mais le proxy de sortie n'est pas activé"}, - {"unknown outproxy url", "URL du proxy de sortie inconnu"}, - {"cannot resolve upstream proxy", "impossible de résoudre l'adresse du proxy en amont"}, - {"hostname too long", "nom d'hôte trop long"}, - {"cannot connect to upstream socks proxy", "impossible de se connecter au proxy socks en amont"}, - {"Cannot negotiate with socks proxy", "Impossible de négocier avec le proxy socks"}, + {"Bad outproxy settings", "Mauvaise configuration du proxy de sortie"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Hôte %s pas dans le réseau I2P, mais le proxy de sortie n'est pas activé"}, + {"Unknown outproxy URL", "URL du proxy de sortie inconnu"}, + {"Cannot resolve upstream proxy", "Impossible de résoudre l'adresse du proxy en amont"}, + {"Hostname is too long", "Nom d'hôte trop long"}, + {"Cannot connect to upstream SOCKS proxy", "Impossible de se connecter au proxy SOCKS en amont"}, + {"Cannot negotiate with SOCKS proxy", "Impossible de négocier avec le proxy SOCKS"}, {"CONNECT error", "Erreur de connexion"}, - {"Failed to Connect", "Échec de connexion"}, - {"socks proxy error", "Erreur de proxy socks"}, - {"failed to send request to upstream", "Erreur lors de l'envoie de la requête en amont"}, - {"No Reply From socks proxy", "Pas de réponse du proxy socks"}, - {"cannot connect", "impossible de connecter"}, - {"http out proxy not implemented", "Proxy de sortie HTTP non implémenté"}, - {"cannot connect to upstream http proxy", "impossible de se connecter au proxy HTTP en amont"}, + {"Failed to connect", "Échec de connexion"}, + {"SOCKS proxy error", "Erreur de proxy SOCKS"}, + {"Failed to send request to upstream", "Erreur lors de l'envoie de la requête en amont"}, + {"No reply from SOCKS proxy", "Pas de réponse du proxy SOCKS"}, + {"Cannot connect", "Impossible de connecter"}, + {"HTTP out proxy not implemented", "Proxy de sortie HTTP non implémenté"}, + {"Cannot connect to upstream HTTP proxy", "Impossible de se connecter au proxy HTTP en amont"}, {"Host is down", "Hôte hors service"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Impossible d'établir une connexion avec l'hôte, il est peut-être hors service. Veuillez réessayer plus tard."}, {"", ""}, @@ -194,10 +202,10 @@ namespace french // language namespace static std::map> plurals { - {"days", {"jour", "jours"}}, - {"hours", {"heure", "heures"}}, - {"minutes", {"minute", "minutes"}}, - {"seconds", {"seconde", "secondes"}}, + {"%d days", {"%d jour", "%d jours"}}, + {"%d hours", {"%d heure", "%d heures"}}, + {"%d minutes", {"%d minute", "%d minutes"}}, + {"%d seconds", {"%d seconde", "%d secondes"}}, {"", {"", ""}}, }; diff --git a/i18n/German.cpp b/i18n/German.cpp index 489a93a7..0db3d6be 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,9 +31,9 @@ namespace german // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "In Bau"}, {"failed", "fehlgeschlagen"}, {"expiring", "läuft ab"}, @@ -58,7 +58,6 @@ namespace german // language namespace {"Unknown", "Unbekannt"}, {"Proxy", "Proxy"}, {"Mesh", "Mesh"}, - {"Error", "Fehler"}, {"Clock skew", "Zeitabweichung"}, {"Offline", "Offline"}, {"Symmetric NAT", "Symmetrisches NAT"}, @@ -69,7 +68,7 @@ namespace german // language namespace {"Family", "Familie"}, {"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"}, {"Received", "Eingegangen"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Gesendet"}, {"Transit", "Transit"}, {"Data path", "Datenpfad"}, @@ -95,7 +94,7 @@ namespace german // language namespace {"Type", "Typ"}, {"EncType", "Verschlüsselungstyp"}, {"Inbound tunnels", "Eingehende Tunnel"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Ausgehende Tunnel"}, {"Tags", "Tags"}, {"Incoming", "Eingehend"}, @@ -117,7 +116,6 @@ namespace german // language namespace {"Gateway", "Gateway"}, {"TunnelID", "TunnelID"}, {"EndDate", "Enddatum"}, - {"not floodfill", "kein Floodfill"}, {"Queue size", "Größe der Warteschlange"}, {"Run peer test", "Peer-Test durchführen"}, {"Decline transit tunnels", "Transittunnel ablehnen"}, @@ -147,8 +145,6 @@ namespace german // language namespace {"Destination not found", "Ziel nicht gefunden"}, {"StreamID can't be null", "StreamID kann nicht null sein"}, {"Return to destination page", "Zurück zur Ziel-Seite"}, - {"You will be redirected in 5 seconds", "Du wirst in 5 Sekunden weitergeleitet"}, - {"Transit tunnels count must not exceed 65535", "Es darf maximal 65535 Transittunnel geben"}, {"Back to commands list", "Zurück zur Befehlsliste"}, {"Register at reg.i2p", "Auf reg.i2p registrieren"}, {"Description", "Beschreibung"}, @@ -166,32 +162,24 @@ namespace german // language namespace {"You may try to find this host on jump services below", "Vielleicht kannst du diesen Host auf einem der nachfolgenden Jump-Services finden"}, {"Invalid request", "Ungültige Anfrage"}, {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht verarbeiten"}, - {"addresshelper is not supported", "Addresshelfer wird nicht unterstützt"}, - {"Host", "Host"}, - {"added to router's addressbook from helper", "vom Helfer zum Router-Adressbuch hinzugefügt"}, - {"Click here to proceed:", "Klicke hier um fortzufahren:"}, - {"Continue", "Fortsetzen"}, - {"Addresshelper found", "Adresshelfer gefunden"}, - {"already in router's addressbook", "bereits im Adressbuch des Routers"}, - {"Click here to update record:", "Klicke hier, um den Eintrag zu aktualisieren:"}, - {"invalid request uri", "ungültige Anfrage-URI"}, + {"Invalid request URI", "Ungültige Anfrage-URI"}, {"Can't detect destination host from request", "Kann den Ziel-Host von der Anfrage nicht erkennen"}, {"Outproxy failure", "Outproxy-Fehler"}, - {"bad outproxy settings", "ungültige Outproxy-Einstellungen"}, - {"not inside I2P network, but outproxy is not enabled", "außerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, - {"unknown outproxy url", "unbekannte Outproxy-URL"}, - {"cannot resolve upstream proxy", "kann den Upstream-Proxy nicht auflösen"}, - {"hostname too long", "Hostname zu lang"}, - {"cannot connect to upstream socks proxy", "Kann keine Verbindung zum Upstream-Socks-Proxy herstellen"}, - {"Cannot negotiate with socks proxy", "Kann nicht mit Socks-Proxy verhandeln"}, + {"Bad outproxy settings", "Ungültige Outproxy-Einstellungen"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s außerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, + {"Unknown outproxy URL", "Unbekannte Outproxy-URL"}, + {"Cannot resolve upstream proxy", "Kann den Upstream-Proxy nicht auflösen"}, + {"Hostname is too long", "Hostname zu lang"}, + {"Cannot connect to upstream SOCKS proxy", "Kann keine Verbindung zum Upstream-SOCKS-Proxy herstellen"}, + {"Cannot negotiate with SOCKS proxy", "Kann nicht mit SOCKS-Proxy verhandeln"}, {"CONNECT error", "CONNECT-Fehler"}, - {"Failed to Connect", "Verbindung konnte nicht hergestellt werden"}, - {"socks proxy error", "Socks-Proxy-Fehler"}, - {"failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, - {"No Reply From socks proxy", "Keine Antwort vom Socks-Proxy"}, - {"cannot connect", "kann nicht verbinden"}, - {"http out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, - {"cannot connect to upstream http proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, + {"Failed to connect", "Verbindung konnte nicht hergestellt werden"}, + {"SOCKS proxy error", "SOCKS-Proxy-Fehler"}, + {"Failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, + {"No reply from SOCKS proxy", "Keine Antwort vom SOCKS-Proxy"}, + {"Cannot connect", "Kann nicht verbinden"}, + {"HTTP out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, + {"Cannot connect to upstream HTTP proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, {"Host is down", "Host ist offline"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Konnte keine Verbindung zum angefragten Host aufbauen, vielleicht ist er offline. Versuche es später noch mal."}, {"", ""}, @@ -199,10 +187,10 @@ namespace german // language namespace static std::map> plurals { - {"days", {"Tag", "Tage"}}, - {"hours", {"Stunde", "Stunden"}}, - {"minutes", {"Minute", "Minuten"}}, - {"seconds", {"Sekunde", "Sekunden"}}, + {"%d days", {"%d Tag", "%d Tage"}}, + {"%d hours", {"%d Stunde", "%d Stunden"}}, + {"%d minutes", {"%d Minute", "%d Minuten"}}, + {"%d seconds", {"%d Sekunde", "%d Sekunden"}}, {"", {"", ""}}, }; diff --git a/i18n/I18N.cpp b/i18n/I18N.cpp index fe04bcb6..cf4873eb 100644 --- a/i18n/I18N.cpp +++ b/i18n/I18N.cpp @@ -1,11 +1,12 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ +#include #include "ClientContext.h" #include "I18N_langs.h" #include "I18N.h" @@ -18,9 +19,15 @@ namespace i18n { const auto it = i2p::i18n::languages.find(lang); if (it == i2p::i18n::languages.end()) // fallback + { i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); + setlocale(LC_NUMERIC, "english"); + } else + { i2p::client::context.SetLanguage (it->second.LocaleFunc()); + setlocale(LC_NUMERIC, lang.c_str()); // set decimal point based on language + } } std::string translate (const std::string& arg) diff --git a/i18n/I18N.h b/i18n/I18N.h index 27e043b9..395c18eb 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -67,17 +67,71 @@ namespace i18n const std::map> m_Plurals; std::function m_Formula; }; - + void SetLanguage(const std::string &lang); std::string translate (const std::string& arg); std::string translate (const std::string& arg, const std::string& arg2, const int& n); } // i18n } // i2p -template -std::string tr (TArgs&&... args) +/** + * @brief Get translation of string + * @param arg String with message + */ +template +std::string tr (TValue&& arg) { - return i2p::i18n::translate(std::forward(args)...); + return i2p::i18n::translate(std::forward(arg)); +} + +/** + * @brief Get translation of string and format it + * @param arg String with message + * @param args Array of arguments for string formatting +*/ +template +std::string tr (TValue&& arg, TArgs&&... args) +{ + std::string tr_str = i2p::i18n::translate(std::forward(arg)); + + size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); + size = size + 1; + std::string str(size, 0); + std::snprintf(&str.front(), size, tr_str.c_str(), std::forward(args)...); + + return str; +} + +/** + * @brief Get translation of string with plural forms + * @param arg String with message in singular form + * @param arg2 String with message in plural form + * @param n Integer, used for selection of form + */ +template +std::string ntr (TValue&& arg, TValue2&& arg2, int& n) +{ + return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); +} + +/** + * @brief Get translation of string with plural forms and format it + * @param arg String with message in singular form + * @param arg2 String with message in plural form + * @param n Integer, used for selection of form + * @param args Array of arguments for string formatting + */ +template +std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args) +{ + std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); + + size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); + size = size + 1; + std::string str(size, 0); + std::snprintf(&str.front(), size, tr_str.c_str(), std::forward(args)...); + + return str; } #endif // __I18N_H__ diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 42c7ba4e..6426e2ce 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,18 +23,23 @@ namespace i18n }; // Add localization here with language name as namespace - namespace afrikaans { std::shared_ptr GetLocale (); } - namespace armenian { std::shared_ptr GetLocale (); } - namespace chinese { std::shared_ptr GetLocale (); } - namespace english { std::shared_ptr GetLocale (); } - namespace french { std::shared_ptr GetLocale (); } - namespace german { std::shared_ptr GetLocale (); } - namespace italian { std::shared_ptr GetLocale (); } - namespace russian { std::shared_ptr GetLocale (); } - namespace spanish { std::shared_ptr GetLocale (); } - namespace turkmen { std::shared_ptr GetLocale (); } - namespace ukrainian { std::shared_ptr GetLocale (); } - namespace uzbek { std::shared_ptr GetLocale (); } + namespace afrikaans { std::shared_ptr GetLocale (); } + namespace armenian { std::shared_ptr GetLocale (); } + namespace chinese { std::shared_ptr GetLocale (); } + namespace czech { std::shared_ptr GetLocale (); } + namespace english { std::shared_ptr GetLocale (); } + namespace french { std::shared_ptr GetLocale (); } + namespace german { std::shared_ptr GetLocale (); } + namespace italian { std::shared_ptr GetLocale (); } + namespace polish { std::shared_ptr GetLocale (); } + namespace portuguese { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } + namespace spanish { std::shared_ptr GetLocale (); } + namespace swedish { std::shared_ptr GetLocale (); } + namespace turkish { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } + namespace ukrainian { std::shared_ptr GetLocale (); } + namespace uzbek { std::shared_ptr GetLocale (); } /** * That map contains international language name lower-case, name in it's language and it's code @@ -44,12 +49,17 @@ namespace i18n { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, { "armenian", {"hայերէն", "hy", i2p::i18n::armenian::GetLocale} }, { "chinese", {"简体字", "zh-CN", i2p::i18n::chinese::GetLocale} }, + { "czech", {"čeština", "cs", i2p::i18n::czech::GetLocale} }, { "english", {"English", "en", i2p::i18n::english::GetLocale} }, { "french", {"Français", "fr", i2p::i18n::french::GetLocale} }, { "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} }, { "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} }, + { "polish", {"Polski", "pl", i2p::i18n::polish::GetLocale} }, + { "portuguese", {"Português", "pt", i2p::i18n::portuguese::GetLocale} }, { "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} }, { "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} }, + { "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} }, + { "turkish", {"Türk dili", "tr", i2p::i18n::turkish::GetLocale} }, { "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, { "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp index ef2e26d0..c51e15a5 100644 --- a/i18n/Italian.cpp +++ b/i18n/Italian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,9 +31,9 @@ namespace italian // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "in costruzione"}, {"failed", "fallito"}, {"expiring", "in scadenza"}, @@ -58,7 +58,6 @@ namespace italian // language namespace {"Unknown", "Sconosciuto"}, {"Proxy", "Proxy"}, {"Mesh", "Mesh"}, - {"Error", "Errore"}, {"Clock skew", "Orologio disallineato"}, {"Offline", "Disconnesso"}, {"Symmetric NAT", "NAT simmetrico"}, @@ -69,7 +68,7 @@ namespace italian // language namespace {"Family", "Famiglia"}, {"Tunnel creation success rate", "Percentuale di tunnel creati con successo"}, {"Received", "Ricevuti"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Inviati"}, {"Transit", "Transitati"}, {"Data path", "Percorso dati"}, @@ -95,7 +94,7 @@ namespace italian // language namespace {"Type", "Tipologia"}, {"EncType", "Tipo di crittografia"}, {"Inbound tunnels", "Tunnel in entrata"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Tunnel in uscita"}, {"Tags", "Tag"}, {"Incoming", "In entrata"}, @@ -117,9 +116,9 @@ namespace italian // language namespace {"Gateway", "Gateway"}, {"TunnelID", "TunnelID"}, {"EndDate", "Data di fine"}, - {"not floodfill", "no floodfill"}, {"Queue size", "Dimensione della coda"}, {"Run peer test", "Esegui il test dei peer"}, + {"Reload tunnels configuration", "Ricarica la configurazione dei tunnel"}, {"Decline transit tunnels", "Rifiuta tunnel di transito"}, {"Accept transit tunnels", "Accetta tunnel di transito"}, {"Cancel graceful shutdown", "Annulla l'interruzione controllata"}, @@ -147,8 +146,8 @@ namespace italian // language namespace {"Destination not found", "Destinazione non trovata"}, {"StreamID can't be null", "Lo StreamID non può essere null"}, {"Return to destination page", "Ritorna alla pagina di destinazione"}, - {"You will be redirected in 5 seconds", "Verrai reindirizzato in 5 secondi"}, - {"Transit tunnels count must not exceed 65535", "Il numero di tunnel di transito non può superare i 65535"}, + {"You will be redirected in %d seconds", "Sarai reindirizzato tra %d secondi"}, + {"Transit tunnels count must not exceed %d", "Il conteggio dei tunnel di transito non deve superare %d"}, {"Back to commands list", "Ritorna alla lista dei comandi"}, {"Register at reg.i2p", "Registra a reg.i2p"}, {"Description", "Descrizione"}, @@ -166,32 +165,26 @@ namespace italian // language namespace {"You may try to find this host on jump services below", "Si può provare a trovare questo host sui servizi di salto qui sotto"}, {"Invalid request", "Richiesta non valida"}, {"Proxy unable to parse your request", "Il proxy non è in grado di elaborare la tua richiesta"}, - {"addresshelper is not supported", "addresshelper non è supportato"}, - {"Host", "Host"}, - {"added to router's addressbook from helper", "aggiunto alla rubrica tramite l'helper"}, - {"Click here to proceed:", "Clicca qui per procedere:"}, - {"Continue", "Continua"}, - {"Addresshelper found", "Addresshelper trovato"}, - {"already in router's addressbook", "già presente nella rubrica del router"}, - {"Click here to update record:", "Clicca qui per aggiornare l'elemento:"}, - {"invalid request uri", "uri della richiesta non valido"}, + {"To add host %s in router's addressbook, click here: Continue.", "Per aggiungere host %s nella rubrica del router, clicca qui: Continua."}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Clicca qui per aggiornare il record: Continua."}, + {"Invalid request URI", "URI della richiesta non valido"}, {"Can't detect destination host from request", "Impossibile determinare l'host di destinazione dalla richiesta"}, {"Outproxy failure", "Fallimento del proxy di uscita"}, - {"bad outproxy settings", "impostazioni errate del proxy di uscita"}, - {"not inside I2P network, but outproxy is not enabled", "non all'interno della rete I2P, ma il proxy di uscita non è abilitato"}, - {"unknown outproxy url", "url del proxy di uscita sconosciuto"}, - {"cannot resolve upstream proxy", "impossibile identificare il flusso a monte del proxy"}, - {"hostname too long", "il nome dell'host è troppo lungo"}, - {"cannot connect to upstream socks proxy", "impossibile connettersi al flusso a monte del proxy socks"}, - {"Cannot negotiate with socks proxy", "Impossibile negoziare con il proxy socks"}, + {"Bad outproxy settings", "Impostazioni errate del proxy di uscita"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s non all'interno della rete I2P, ma il proxy di uscita non è abilitato"}, + {"Unknown outproxy URL", "URL del proxy di uscita sconosciuto"}, + {"Cannot resolve upstream proxy", "Impossibile identificare il flusso a monte del proxy"}, + {"Hostname is too long", "Il nome dell'host è troppo lungo"}, + {"Cannot connect to upstream SOCKS proxy", "Impossibile connettersi al flusso a monte del proxy SOCKS"}, + {"Cannot negotiate with SOCKS proxy", "Impossibile negoziare con il proxy SOCKS"}, {"CONNECT error", "Errore di connessione"}, - {"Failed to Connect", "Connessione fallita"}, - {"socks proxy error", "errore del proxy socks"}, - {"failed to send request to upstream", "invio della richiesta a monte non riuscito"}, - {"No Reply From socks proxy", "Nessuna risposta dal proxy socks"}, - {"cannot connect", "impossibile connettersi"}, - {"http out proxy not implemented", "proxy http di uscita non implementato"}, - {"cannot connect to upstream http proxy", "impossibile connettersi al proxy http a monte"}, + {"Failed to connect", "Connessione fallita"}, + {"SOCKS proxy error", "Errore del proxy SOCKS"}, + {"Failed to send request to upstream", "Invio della richiesta a monte non riuscito"}, + {"No reply from SOCKS proxy", "Nessuna risposta dal proxy SOCKS"}, + {"Cannot connect", "Impossibile connettersi"}, + {"HTTP out proxy not implemented", "Proxy HTTP di uscita non implementato"}, + {"Cannot connect to upstream HTTP proxy", "Impossibile connettersi al flusso a monte del proxy HTTP"}, {"Host is down", "L'host è offline"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Impossibile creare la connessione all'host richiesto, probabilmente è offline. Riprova più tardi."}, {"", ""}, @@ -199,10 +192,10 @@ namespace italian // language namespace static std::map> plurals { - {"days", {"giorno", "giorni"}}, - {"hours", {"ora", "ore"}}, - {"minutes", {"minuto", "minuti"}}, - {"seconds", {"secondo", "secondi"}}, + {"%d days", {"%d giorno", "%d giorni"}}, + {"%d hours", {"%d ora", "%d ore"}}, + {"%d minutes", {"%d minuto", "%d minuti"}}, + {"%d seconds", {"%d secondo", "%d secondi"}}, {"", {"", ""}}, }; diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp new file mode 100644 index 00000000..26661231 --- /dev/null +++ b/i18n/Polish.cpp @@ -0,0 +1,59 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Polish localization file + +namespace i2p +{ +namespace i18n +{ +namespace polish // language namespace +{ + // language name in lowercase + static std::string language = "polish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); + } + + static std::map strings + { + {"building", "Kompilowanie"}, + {"failed", "nieudane"}, + {"expiring", "wygasający"}, + {"established", "ustanowiony"}, + {"Main page", "Strona główna"}, + {"Router commands", "Komendy routera"}, + {"Tunnels", "Tunele"}, + {"OK", "Ok"}, + {"Uptime", "Czas pracy"}, + {"Sent", "Wysłane"}, + {"", ""}, + }; + + static std::map> plurals + { + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp new file mode 100644 index 00000000..528be389 --- /dev/null +++ b/i18n/Portuguese.cpp @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Portuguese localization file + +namespace i2p +{ +namespace i18n +{ +namespace portuguese // language namespace +{ + // language name in lowercase + static std::string language = "portuguese"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "construindo"}, + {"failed", "falhou"}, + {"expiring", "expirando"}, + {"established", "estabelecido"}, + {"unknown", "desconhecido"}, + {"exploratory", "exploratório"}, + {"Purple I2P Webconsole", "Webconsole Purple I2P"}, + {"i2pd webconsole", "webconsole i2pd"}, + {"Main page", "Página Principal"}, + {"Router commands", "Comandos do Roteador"}, + {"Local Destinations", "Destinos Locais"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Túneis"}, + {"Transit Tunnels", "Túneis de Trânsito"}, + {"Transports", "Transportes"}, + {"I2P tunnels", "Túneis I2P"}, + {"SAM sessions", "Sessões do SAM"}, + {"ERROR", "ERRO"}, + {"OK", "OK"}, + {"Testing", "Testando"}, + {"Firewalled", "Sob Firewall"}, + {"Unknown", "Desconhecido"}, + {"Proxy", "Proxy"}, + {"Mesh", "Malha"}, + {"Clock skew", "Defasagem do Relógio"}, + {"Offline", "Desligado"}, + {"Symmetric NAT", "NAT Simétrico"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Sem Descritores"}, + {"Uptime", "Tempo Ativo"}, + {"Network status", "Estado da rede"}, + {"Network status v6", "Estado da rede v6"}, + {"Stopping in", "Parando em"}, + {"Family", "Família"}, + {"Tunnel creation success rate", "Taxa de sucesso na criação de túneis"}, + {"Received", "Recebido"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Enviado"}, + {"Transit", "Trânsito"}, + {"Data path", "Caminho dos dados"}, + {"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."}, + {"Router Ident", "Identidade do Roteador"}, + {"Router Family", "Família do Roteador"}, + {"Router Caps", "Limites do Roteador"}, + {"Version", "Versão"}, + {"Our external address", "Nosso endereço externo"}, + {"supported", "suportado"}, + {"Routers", "Roteadores"}, + {"Floodfills", "Modo Inundação"}, + {"Client Tunnels", "Túneis de Clientes"}, + {"Services", "Serviços"}, + {"Enabled", "Ativado"}, + {"Disabled", "Desativado"}, + {"Encrypted B33 address", "Endereço B33 criptografado"}, + {"Address registration line", "Linha de cadastro de endereço"}, + {"Domain", "Domínio"}, + {"Generate", "Gerar"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Nota: A string resultante só pode ser usada para registrar domínios 2LD (exemplo.i2p). Para registrar subdomínios por favor utilize o i2pd-tools."}, + {"Address", "Endereço"}, + {"Type", "Tipo"}, + {"EncType", "Tipo de Criptografia"}, + {"Inbound tunnels", "Túneis de Entrada"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Túneis de Saída"}, + {"Tags", "Etiquetas"}, + {"Incoming", "Entradas"}, + {"Outgoing", "Saídas"}, + {"Destination", "Destinos"}, + {"Amount", "Quantidade"}, + {"Incoming Tags", "Etiquetas de Entrada"}, + {"Tags sessions", "Sessões de etiquetas"}, + {"Status", "Estado"}, + {"Local Destination", "Destinos Locais"}, + {"Streams", "Fluxos"}, + {"Close stream", "Fechar fluxo"}, + {"I2CP session not found", "Sessão do I2CP não encontrada"}, + {"I2CP is not enabled", "I2CP não está ativado"}, + {"Invalid", "Inválido"}, + {"Store type", "Tipo de armazenamento"}, + {"Expires", "Expira em"}, + {"Non Expired Leases", "Sessões não expiradas"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Data final"}, + {"floodfill mode is disabled", "Mode de inundação está desativado"}, + {"Queue size", "Tamanho da fila"}, + {"Run peer test", "Executar teste de peers"}, + {"Reload tunnels configuration", "Recarregar a configuração dos túneis"}, + {"Decline transit tunnels", "Negar túnel de trânsito"}, + {"Accept transit tunnels", "Aceitar túnel de trânsito"}, + {"Cancel graceful shutdown", "Cancelar desligamento gracioso"}, + {"Start graceful shutdown", "Iniciar desligamento gracioso"}, + {"Force shutdown", "Forçar desligamento"}, + {"Reload external CSS styles", "Recarregar estilos CSS externos"}, + {"Note: any action done here are not persistent and not changes your config files.", " Nota: Qualquer ação feita aqui não será permanente e não altera os seus arquivos de configuração."}, + {"Logging level", "Nível de registro"}, + {"Transit tunnels limit", "Limite nos túneis de trânsito"}, + {"Change", "Mudar"}, + {"Change language", "Trocar idioma"}, + {"no transit tunnels currently built", "Nenhum túnel de trânsito construido no momento"}, + {"SAM disabled", "SAM desativado"}, + {"no sessions currently running", "Nenhuma sessão funcionando no momento"}, + {"SAM session not found", "Nenhuma sessão do SAM encontrada"}, + {"SAM Session", "Sessão do SAM"}, + {"Server Tunnels", "Túneis de Servidor"}, + {"Client Forwards", "Túneis de Cliente"}, + {"Server Forwards", "Encaminhamentos de Servidor"}, + {"Unknown page", "Página desconhecida"}, + {"Invalid token", "Token Inválido"}, + {"SUCCESS", "SUCESSO"}, + {"Stream closed", "Fluxo fechado"}, + {"Stream not found or already was closed", "Fluxo não encontrado ou já encerrado"}, + {"Destination not found", "Destino não encontrado"}, + {"StreamID can't be null", "StreamID não pode ser nulo"}, + {"Return to destination page", "Retornar para à página de destino"}, + {"You will be redirected in %d seconds", "Você será redirecionado em %d segundos"}, + {"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"}, + {"Back to commands list", "Voltar para a lista de comandos"}, + {"Register at reg.i2p", "Registrar na reg.i2p"}, + {"Description", "Descrição"}, + {"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"}, + {"Submit", "Enviar"}, + {"Domain can't end with .b32.i2p", "O domínio não pode terminar com .b32.i2p"}, + {"Domain must end with .i2p", "O domínio não pode terminar com .i2p"}, + {"Such destination is not found", "Tal destino não foi encontrado"}, + {"Unknown command", "Comando desconhecido"}, + {"Command accepted", "Comando aceito"}, + {"Proxy error", "Erro no proxy"}, + {"Proxy info", "Informações do proxy"}, + {"Proxy error: Host not found", "Erro no proxy: Host não encontrado"}, + {"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"}, + {"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos jump services abaixo"}, + {"Invalid request", "Requisição inválida"}, + {"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"}, + {"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador. Cuidado: a fonte desta URL pode ser perigosa! Clique aqui para atualizar o registro: Continuar."}, + {"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"}, + {"To add host %s in router's addressbook, click here: Continue.", "Para adicionar o host %s ao catálogo de endereços do roteador, clique aqui: Continuar ."}, + {"Addresshelper request", "Requisição do Auxiliar de Endereços"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para proceder: Continuar ."}, + {"Addresshelper adding", "Auxiliar de Endereço adicionando"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador . Clique aqui para atualizar o registro: Continuar."}, + {"Addresshelper update", "Atualização do Auxiliar de Endereços"}, + {"Invalid request URI", "A URI de requisição é inválida"}, + {"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"}, + {"Outproxy failure", "Falha no outproxy"}, + {"Bad outproxy settings", "Configurações ruins de outproxy"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"}, + {"Unknown outproxy URL", "URL de outproxy desconhecida"}, + {"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"}, + {"Hostname is too long", "O hostname é muito longo"}, + {"Cannot connect to upstream SOCKS proxy", "Não é possível se conectar ao proxy SOCKS de entrada"}, + {"Cannot negotiate with SOCKS proxy", "Não é possível negociar com o proxy SOCKS"}, + {"CONNECT error", "Erro de CONEXÃO"}, + {"Failed to connect", "Falha ao conectar"}, + {"SOCKS proxy error", "Erro no proxy SOCKS"}, + {"Failed to send request to upstream", "Falha ao enviar requisição para o fluxo de entrada"}, + {"No reply from SOCKS proxy", "Sem resposta do proxy SOCKS"}, + {"Cannot connect", "Impossível conectar"}, + {"HTTP out proxy not implemented", "proxy de saída HTTP não implementado"}, + {"Cannot connect to upstream HTTP proxy", "Não é possível conectar ao proxy HTTP de entrada"}, + {"Host is down", "Host está desligado"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Não é possível se conectar ao host requisitado, talvez ele esteja for do ar. Por favor, tente novamente mais tarde."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d Dia", "%d Dias"}}, + {"%d hours", {"%d hora", "%d horas"}}, + {"%d minutes", {"%d minuto", "%d minutos"}}, + {"%d seconds", {"%d Segundo", "%d segundos"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index d7616e9e..dbfb13f2 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,15 +31,16 @@ namespace russian // language namespace static std::map strings { - {"KiB", "КиБ"}, - {"MiB", "МиБ"}, - {"GiB", "ГиБ"}, + {"%.2f KiB", "%.2f КиБ"}, + {"%.2f MiB", "%.2f МиБ"}, + {"%.2f GiB", "%.2f ГиБ"}, {"building", "строится"}, {"failed", "неудачный"}, {"expiring", "истекает"}, {"established", "работает"}, {"unknown", "неизвестно"}, {"exploratory", "исследовательский"}, + {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, {"i2pd webconsole", "Веб-консоль i2pd"}, {"Main page", "Главная"}, {"Router commands", "Команды роутера"}, @@ -57,10 +58,11 @@ namespace russian // language namespace {"Unknown", "Неизвестно"}, {"Proxy", "Прокси"}, {"Mesh", "MESH-сеть"}, - {"Error", "Ошибка"}, {"Clock skew", "Не точное время"}, {"Offline", "Оффлайн"}, {"Symmetric NAT", "Симметричный NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Нет дескрипторов"}, {"Uptime", "В сети"}, {"Network status", "Сетевой статус"}, {"Network status v6", "Сетевой статус v6"}, @@ -68,7 +70,7 @@ namespace russian // language namespace {"Family", "Семейство"}, {"Tunnel creation success rate", "Успешно построенных туннелей"}, {"Received", "Получено"}, - {"KiB/s", "КиБ/с"}, + {"%.2f KiB/s", "%.2f КиБ/с"}, {"Sent", "Отправлено"}, {"Transit", "Транзит"}, {"Data path", "Путь к данным"}, @@ -94,7 +96,7 @@ namespace russian // language namespace {"Type", "Тип"}, {"EncType", "ТипШифр"}, {"Inbound tunnels", "Входящие туннели"}, - {"ms", "мс"}, + {"%dms", "%dмс"}, {"Outbound tunnels", "Исходящие туннели"}, {"Tags", "Теги"}, {"Incoming", "Входящие"}, @@ -116,9 +118,10 @@ namespace russian // language namespace {"Gateway", "Шлюз"}, {"TunnelID", "ID туннеля"}, {"EndDate", "Заканчивается"}, - {"not floodfill", "не флудфил"}, + {"floodfill mode is disabled", "режим флудфила отключен"}, {"Queue size", "Размер очереди"}, {"Run peer test", "Запустить тестирование"}, + {"Reload tunnels configuration", "Перезагрузить конфигурацию туннелей"}, {"Decline transit tunnels", "Отклонять транзитные туннели"}, {"Accept transit tunnels", "Принимать транзитные туннели"}, {"Cancel graceful shutdown", "Отменить плавную остановку"}, @@ -146,8 +149,8 @@ namespace russian // language namespace {"Destination not found", "Точка назначения не найдена"}, {"StreamID can't be null", "StreamID не может быть пустым"}, {"Return to destination page", "Вернуться на страницу точки назначения"}, - {"You will be redirected in 5 seconds", "Вы будете переадресованы через 5 секунд"}, - {"Transit tunnels count must not exceed 65535", "Число транзитных туннелей не должно превышать 65535"}, + {"You will be redirected in %d seconds", "Вы будете переадресованы через %d секунд"}, + {"Transit tunnels count must not exceed %d", "Число транзитных туннелей не должно превышать %d"}, {"Back to commands list", "Вернуться к списку команд"}, {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, {"Description", "Описание"}, @@ -165,32 +168,33 @@ namespace russian // language namespace {"You may try to find this host on jump services below", "Вы можете попробовать найти узел через джамп сервисы ниже"}, {"Invalid request", "Некорректный запрос"}, {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, - {"addresshelper is not supported", "addresshelper не поддерживается"}, - {"Host", "Узел"}, - {"added to router's addressbook from helper", "добавлен в адресную книгу роутера через хелпер"}, - {"Click here to proceed:", "Нажмите здесь, чтобы продолжить:"}, - {"Continue", "Продолжить"}, - {"Addresshelper found", "Найден addresshelper"}, - {"already in router's addressbook", "уже в адресной книге роутера"}, - {"Click here to update record:", "Нажмите здесь, чтобы обновить запись:"}, - {"invalid request uri", "некорректный URI запроса"}, + {"Addresshelper is not supported", "Addresshelper не поддерживается"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Будьте осторожны: источник данной ссылки может быть вредоносным! Нажмите здесь, чтобы обновить запись: Продолжить."}, + {"Addresshelper forced update rejected", "Принудительное обновление через Addresshelper отклонено"}, + {"To add host %s in router's addressbook, click here: Continue.", "Чтобы добавить узел %s в адресную книгу роутера, нажмите здесь: Продолжить."}, + {"Addresshelper request", "Запрос добавления Addresshelper"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Узел %s добавлен в адресную книгу роутера через хелпер. Нажмите здесь, чтобы продолжить: Продолжить."}, + {"Addresshelper adding", "Добавление Addresshelper"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Нажмите здесь, чтобы обновить запись: Продолжить."}, + {"Addresshelper update", "Обновление записи через Addresshelper"}, + {"Invalid request URI", "Некорректный URI запроса"}, {"Can't detect destination host from request", "Не удалось определить адрес назначения из запроса"}, {"Outproxy failure", "Ошибка внешнего прокси"}, - {"bad outproxy settings", "некорректные настройки внешнего прокси"}, - {"not inside I2P network, but outproxy is not enabled", "не в I2P сети, но внешний прокси не включен"}, - {"unknown outproxy url", "неизвестный URL внешнего прокси"}, - {"cannot resolve upstream proxy", "не удается определить вышестоящий прокси"}, - {"hostname too long", "имя хоста слишком длинное"}, - {"cannot connect to upstream socks proxy", "не удается подключиться к вышестоящему SOCKS прокси"}, - {"Cannot negotiate with socks proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, + {"Bad outproxy settings", "Некорректные настройки внешнего прокси"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Узел %s не в I2P сети, но внешний прокси не включен"}, + {"Unknown outproxy URL", "Неизвестный URL внешнего прокси"}, + {"Cannot resolve upstream proxy", "Не удается определить вышестоящий прокси"}, + {"Hostname is too long", "Имя хоста слишком длинное"}, + {"Cannot connect to upstream SOCKS proxy", "Не удалось подключиться к вышестоящему SOCKS прокси серверу"}, + {"Cannot negotiate with SOCKS proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, {"CONNECT error", "Ошибка CONNECT запроса"}, - {"Failed to Connect", "Не удалось подключиться"}, - {"socks proxy error", "ошибка SOCKS прокси"}, - {"failed to send request to upstream", "не удалось отправить запрос вышестоящему прокси"}, - {"No Reply From socks proxy", "Нет ответа от SOCKS прокси сервера"}, - {"cannot connect", "не удалось подключиться"}, - {"http out proxy not implemented", "поддержка внешнего HTTP прокси сервера не реализована"}, - {"cannot connect to upstream http proxy", "не удалось подключиться к вышестоящему HTTP прокси серверу"}, + {"Failed to connect", "Не удалось соединиться"}, + {"SOCKS proxy error", "Ошибка SOCKS прокси"}, + {"Failed to send request to upstream", "Не удалось отправить запрос вышестоящему прокси серверу"}, + {"No reply from SOCKS proxy", "Нет ответа от SOCKS прокси сервера"}, + {"Cannot connect", "Не удалось подключиться"}, + {"HTTP out proxy not implemented", "Поддержка внешнего HTTP прокси сервера не реализована"}, + {"Cannot connect to upstream HTTP proxy", "Не удалось подключиться к вышестоящему HTTP прокси серверу"}, {"Host is down", "Узел недоступен"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному узлу, возможно он не в сети. Попробуйте повторить запрос позже."}, {"", ""}, @@ -198,10 +202,10 @@ namespace russian // language namespace static std::map> plurals { - {"days", {"день", "дня", "дней"}}, - {"hours", {"час", "часа", "часов"}}, - {"minutes", {"минуту", "минуты", "минут"}}, - {"seconds", {"секунду", "секунды", "секунд"}}, + {"%d days", {"%d день", "%d дня", "%d дней"}}, + {"%d hours", {"%d час", "%d часа", "%d часов"}}, + {"%d minutes", {"%d минуту", "%d минуты", "%d минут"}}, + {"%d seconds", {"%d секунду", "%d секунды", "%d секунд"}}, {"", {"", "", ""}}, }; diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp index a2f53927..a5ecc30a 100644 --- a/i18n/Spanish.cpp +++ b/i18n/Spanish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,9 +31,9 @@ namespace spanish // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "pendiente"}, {"failed", "fallido"}, {"expiring", "expiró"}, @@ -58,7 +58,6 @@ namespace spanish // language namespace {"Unknown", "Desconocido"}, {"Proxy", "Proxy"}, {"Mesh", "Malla"}, - {"Error", "Error"}, {"Clock skew", "Reloj desfasado"}, {"Offline", "Desconectado"}, {"Symmetric NAT", "NAT simétrico"}, @@ -69,7 +68,7 @@ namespace spanish // language namespace {"Family", "Familia"}, {"Tunnel creation success rate", "Tasa de éxito de creación de túneles"}, {"Received", "Recibido"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Enviado"}, {"Transit", "Tránsito"}, {"Data path", "Ruta de datos"}, @@ -95,7 +94,7 @@ namespace spanish // language namespace {"Type", "Tipo"}, {"EncType", "TipoEncrip"}, {"Inbound tunnels", "Túneles entrantes"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Túneles salientes"}, {"Tags", "Etiquetas"}, {"Incoming", "Entrante"}, @@ -117,7 +116,6 @@ namespace spanish // language namespace {"Gateway", "Puerta de enlace"}, {"TunnelID", "TunnelID"}, {"EndDate", "FechaVenc"}, - {"not floodfill", "no inundado"}, {"Queue size", "Tamaño de cola"}, {"Run peer test", "Ejecutar prueba de par"}, {"Decline transit tunnels", "Rechazar túneles de tránsito"}, @@ -147,8 +145,6 @@ namespace spanish // language namespace {"Destination not found", "Destino no encontrado"}, {"StreamID can't be null", "StreamID no puede ser nulo"}, {"Return to destination page", "Volver a la página de destino"}, - {"You will be redirected in 5 seconds", "Serás redirigido en 5 segundos"}, - {"Transit tunnels count must not exceed 65535", "La cantidad de túneles de tránsito no puede exceder 65535"}, {"Back to commands list", "Volver a lista de comandos"}, {"Register at reg.i2p", "Registrar en reg.i2p"}, {"Description", "Descripción"}, @@ -166,32 +162,24 @@ namespace spanish // language namespace {"You may try to find this host on jump services below", "Puede intentar encontrar este dominio en los siguientes servicios de salto"}, {"Invalid request", "Solicitud inválida"}, {"Proxy unable to parse your request", "Proxy no puede procesar su solicitud"}, - {"addresshelper is not supported", "ayudante de dirección no soportado"}, - {"Host", "Dominio"}, - {"added to router's addressbook from helper", "añadido a la libreta de direcciones desde el ayudante"}, - {"Click here to proceed:", "Haga clic aquí para continuar:"}, - {"Continue", "Continuar"}, - {"Addresshelper found", "Se encontró ayudante de dirección"}, - {"already in router's addressbook", "ya se encontró en libreta de direcciones"}, - {"Click here to update record:", "Haga clic aquí para actualizar el registro:"}, - {"invalid request uri", "uri de solicitud inválida"}, + {"Invalid request URI", "URI de solicitud inválida"}, {"Can't detect destination host from request", "No se puede detectar el host de destino de la solicitud"}, {"Outproxy failure", "Fallo en el proxy saliente"}, - {"bad outproxy settings", "configuración de outproxy incorrecta"}, - {"not inside I2P network, but outproxy is not enabled", "no está dentro de la red I2P, pero el proxy de salida no está activado"}, - {"unknown outproxy url", "url de proxy outproxy desconocido"}, - {"cannot resolve upstream proxy", "no se puede resolver el proxy de upstream"}, - {"hostname too long", "nombre de dominio muy largo"}, - {"cannot connect to upstream socks proxy", "no se puede conectar al proxy socks principal"}, - {"Cannot negotiate with socks proxy", "No se puede negociar con el proxy socks"}, + {"Bad outproxy settings", "Configuración de outproxy incorrecta"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Dominio %s no está dentro de la red I2P, pero el proxy de salida no está activado"}, + {"Unknown outproxy URL", "URL de proxy outproxy desconocido"}, + {"Cannot resolve upstream proxy", "No se puede resolver el proxy de upstream"}, + {"Hostname is too long", "Nombre de dominio muy largo"}, + {"Cannot connect to upstream SOCKS proxy", "No se puede conectar al proxy SOCKS principal"}, + {"Cannot negotiate with SOCKS proxy", "No se puede negociar con el proxy SOCKS"}, {"CONNECT error", "Error de CONNECT"}, - {"Failed to Connect", "Error al Conectar"}, - {"socks proxy error", "error de proxy socks"}, - {"failed to send request to upstream", "no se pudo enviar petición al principal"}, - {"No Reply From socks proxy", "Sin respuesta del proxy socks"}, - {"cannot connect", "no se puede conectar"}, - {"http out proxy not implemented", "proxy externo http no implementado"}, - {"cannot connect to upstream http proxy", "no se puede conectar al proxy http principal"}, + {"Failed to connect", "Error al conectar"}, + {"SOCKS proxy error", "Error de proxy SOCKS"}, + {"Failed to send request to upstream", "No se pudo enviar petición al principal"}, + {"No reply from SOCKS proxy", "Sin respuesta del proxy SOCKS"}, + {"Cannot connect", "No se puede conectar"}, + {"HTTP out proxy not implemented", "Proxy externo HTTP no implementado"}, + {"Cannot connect to upstream HTTP proxy", "No se puede conectar al proxy HTTP principal"}, {"Host is down", "Servidor caído"}, {"Can't create connection to requested host, it may be down. Please try again later.", "No se puede crear la conexión al servidor solicitado, puede estar caído. Intente de nuevo más tarde."}, {"", ""}, @@ -199,10 +187,10 @@ namespace spanish // language namespace static std::map> plurals { - {"days", {"día", "días"}}, - {"hours", {"hora", "horas"}}, - {"minutes", {"minuto", "minutos"}}, - {"seconds", {"segundo", "segundos"}}, + {"%d days", {"%d día", "%d días"}}, + {"%d hours", {"%d hora", "%d horas"}}, + {"%d minutes", {"%d minuto", "%d minutos"}}, + {"%d seconds", {"%d segundo", "%d segundos"}}, {"", {"", ""}}, }; diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp new file mode 100644 index 00000000..e7f84e69 --- /dev/null +++ b/i18n/Swedish.cpp @@ -0,0 +1,208 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Swedish localization file + +namespace i2p +{ +namespace i18n +{ +namespace swedish // language namespace +{ + // language name in lowercase + static std::string language = "swedish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "bygger"}, + {"failed", "misslyckad"}, + {"expiring", "utgår"}, + {"established", "upprättad"}, + {"unknown", "okänt"}, + {"exploratory", "utforskande"}, + {"Purple I2P Webconsole", "Purple I2P Webbkonsoll"}, + {"i2pd webconsole", "i2pd-Webbkonsoll"}, + {"Main page", "Huvudsida"}, + {"Router commands", "Routerkommandon"}, + {"Local Destinations", "Lokala Platser"}, + {"LeaseSets", "Hyresuppsättningar"}, + {"Tunnels", "Tunnlar"}, + {"Transit Tunnels", "Förmedlande Tunnlar"}, + {"Transports", "Transporter"}, + {"I2P tunnels", "I2P-tunnlar"}, + {"SAM sessions", "SAM-perioder"}, + {"ERROR", "FEL"}, + {"OK", "OK"}, + {"Testing", "Prövar"}, + {"Firewalled", "Bakom Brandvägg"}, + {"Unknown", "Okänt"}, + {"Proxy", "Proxy"}, + {"Mesh", "Mesh"}, + {"Clock skew", "Tidsförskjutning"}, + {"Offline", "Nedkopplad"}, + {"Symmetric NAT", "Symmetrisk NAT"}, + {"Uptime", "Upptid"}, + {"Network status", "Nätverkstillstånd"}, + {"Network status v6", "Nätverkstillstånd v6"}, + {"Stopping in", "Avstängd om"}, + {"Family", "Familj"}, + {"Tunnel creation success rate", "Andel framgångsrika tunnlar"}, + {"Received", "Mottaget"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Skickat"}, + {"Transit", "Förmedlat"}, + {"Data path", "Sökväg"}, + {"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."}, + {"Router Ident", "Routeridentitet"}, + {"Router Family", "Routerfamilj"}, + {"Router Caps", "Routerbegränsningar"}, + {"Version", "Version"}, + {"Our external address", "Vår externa adress"}, + {"supported", "stöds"}, + {"Routers", "Routrar"}, + {"Floodfills", "Översvämningsfyllare"}, + {"Client Tunnels", "Klienttunnlar"}, + {"Services", "Tjänster"}, + {"Enabled", "Påslaget"}, + {"Disabled", "Avslaget"}, + {"Encrypted B33 address", "Krypterad B33-Adress"}, + {"Address registration line", "Adressregistreringsrad"}, + {"Domain", "Domän"}, + {"Generate", "Skapa"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uppmärksamma: den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."}, + {"Address", "Adress"}, + {"Type", "Typ"}, + {"EncType", "EncTyp"}, + {"Inbound tunnels", "Ingående Tunnlar"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Utgående Tunnlar"}, + {"Tags", "Taggar"}, + {"Incoming", "Ingående"}, + {"Outgoing", "Utgående"}, + {"Destination", "Plats"}, + {"Amount", "Mängd"}, + {"Incoming Tags", "Ingående Taggar"}, + {"Tags sessions", "Tagg-perioder"}, + {"Status", "Tillstånd"}, + {"Local Destination", "Lokal Plats"}, + {"Streams", "Strömmar"}, + {"Close stream", "Stäng strömmen"}, + {"I2CP session not found", "I2CP-period hittades inte"}, + {"I2CP is not enabled", "I2CP är inte påslaget"}, + {"Invalid", "Ogiltig"}, + {"Store type", "Lagringstyp"}, + {"Expires", "Utgångsdatum"}, + {"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "EndDate"}, + {"Queue size", "Köstorlek"}, + {"Run peer test", "Utför utsiktstest"}, + {"Decline transit tunnels", "Avvisa förmedlande tunnlar"}, + {"Accept transit tunnels", "Tillåt förmedlande tunnlar"}, + {"Cancel graceful shutdown", "Avbryt välvillig avstängning"}, + {"Start graceful shutdown", "Påbörja välvillig avstängning"}, + {"Force shutdown", "Tvingad avstängning"}, + {"Reload external CSS styles", "Ladda om externa CSS-stilar"}, + {"Note: any action done here are not persistent and not changes your config files.", "Uppmärksamma: inga ändringar här är beständiga eller påverkar dina inställningsfiler."}, + {"Logging level", "Protokollförningsnivå"}, + {"Transit tunnels limit", "Begränsa förmedlande tunnlar"}, + {"Change", "Ändra"}, + {"Change language", "Ändra språk"}, + {"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"}, + {"SAM disabled", "SAM avslaget"}, + {"no sessions currently running", "inga perioder igång"}, + {"SAM session not found", "SAM-perioder hittades ej"}, + {"SAM Session", "SAM-period"}, + {"Server Tunnels", "Värdtunnlar"}, + {"Client Forwards", "Klientförpassningar"}, + {"Server Forwards", "Värdförpassningar"}, + {"Unknown page", "Okänd sida"}, + {"Invalid token", "Ogiltig polett"}, + {"SUCCESS", "FRAMGÅNG"}, + {"Stream closed", "Ström stängd"}, + {"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"}, + {"Destination not found", "Plats hittades ej"}, + {"StreamID can't be null", "Ström-ID kan inte vara null"}, + {"Return to destination page", "Återvänd till platssidan"}, + {"You will be redirected in %d seconds", "Du omdirigeras inom %d sekunder"}, + {"Transit tunnels count must not exceed %d", "Förmedlande tunnlar får inte överstiga %d"}, + {"Back to commands list", "Tillbaka till kommandolistan"}, + {"Register at reg.i2p", "Registrera vid reg.i2p"}, + {"Description", "Beskrivning"}, + {"A bit information about service on domain", "Ett stycke information om domänens tjänst"}, + {"Submit", "Skicka"}, + {"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"}, + {"Domain must end with .i2p", "Domänen måste sluta med .i2p"}, + {"Such destination is not found", "En sådan plats hittas ej"}, + {"Unknown command", "Okänt kommando"}, + {"Command accepted", "Kommando accepterades"}, + {"Proxy error", "Proxyfel"}, + {"Proxy info", "Proxyinfo"}, + {"Proxy error: Host not found", "Proxyfel: Värden hittades ej"}, + {"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"}, + {"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"}, + {"Invalid request", "Ogiltig förfrågan"}, + {"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"}, + {"Addresshelper is not supported", "Adresshjälparen stöds ej"}, + {"Invalid request URI", "Ogiltig förfrågnings-URI"}, + {"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"}, + {"Outproxy failure", "Utproxyfel"}, + {"Bad outproxy settings", "Ogiltig utproxyinställning"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Värd %s är inte inom I2P-näverket, men utproxy är inte påslaget"}, + {"Unknown outproxy URL", "okänt Utproxy-URL"}, + {"Cannot resolve upstream proxy", "Hittar inte uppströmsproxyt"}, + {"Hostname is too long", "Värdnamnet är för långt"}, + {"Cannot connect to upstream SOCKS proxy", "Kan inte ansluta till uppström SOCKS-proxy"}, + {"Cannot negotiate with SOCKS proxy", "Kan inte förhandla med SOCKSproxyt"}, + {"CONNECT error", "CONNECT-fel"}, + {"Failed to connect", "Anslutningen misslyckades"}, + {"SOCKS proxy error", "SOCKSproxyfel"}, + {"Failed to send request to upstream", "Förfrågan uppströms kunde ej skickas"}, + {"No reply from SOCKS proxy", "Fick inget svar från SOCKSproxyt"}, + {"Cannot connect", "Kan inte ansluta"}, + {"HTTP out proxy not implemented", "HTTP-Utproxy ej implementerat"}, + {"Cannot connect to upstream HTTP proxy", "Kan inte ansluta till uppströms HTTP-proxy"}, + {"Host is down", "Värden är nere"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d dag", "%d dagar"}}, + {"%d hours", {"%d timme", "%d timmar"}}, + {"%d minutes", {"%d minut", "%d minuter"}}, + {"%d seconds", {"%d sekund", "%d sekunder"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p + diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp new file mode 100644 index 00000000..d4398ebe --- /dev/null +++ b/i18n/Turkish.cpp @@ -0,0 +1,114 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Turkish localization file + +namespace i2p +{ +namespace i18n +{ +namespace turkish // language namespace +{ + // language name in lowercase + static std::string language = "turkish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "kuruluyor"}, + {"failed", "başarısız"}, + {"expiring", "süresi geçiyor"}, + {"established", "kurulmuş"}, + {"unknown", "bilinmeyen"}, + {"Purple I2P Webconsole", "Mor I2P Webkonsolu"}, + {"i2pd webconsole", "i2pd webkonsolu"}, + {"Main page", "Ana sayfa"}, + {"Router commands", "Router komutları"}, + {"Local Destinations", "Yerel Hedefler"}, + {"Tunnels", "Tüneller"}, + {"Transit Tunnels", "Transit Tünelleri"}, + {"Transports", "Taşıma"}, + {"I2P tunnels", "I2P tünelleri"}, + {"SAM sessions", "SAM oturumları"}, + {"ERROR", "HATA"}, + {"OK", "TAMAM"}, + {"Testing", "Test ediliyor"}, + {"Firewalled", "Güvenlik Duvarı Kısıtlaması"}, + {"Unknown", "Bilinmeyen"}, + {"Proxy", "Proxy"}, + {"Clock skew", "Saat sorunu"}, + {"Offline", "Çevrimdışı"}, + {"Symmetric NAT", "Simetrik NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Tanımlayıcı Yok"}, + {"Uptime", "Bağlantı süresi"}, + {"Network status", "Ağ durumu"}, + {"Network status v6", "Ağ durumu v6"}, + {"Family", "Aile"}, + {"Tunnel creation success rate", "Tünel oluşturma başarı oranı"}, + {"Received", "Alındı"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Gönderildi"}, + {"Transit", "Transit"}, + {"Data path", "Veri yolu"}, + {"Hidden content. Press on text to see.", "Gizlenmiş içerik. Görmek için yazıya tıklayınız."}, + {"Router Family", "Router Familyası"}, + {"Decline transit tunnels", "Transit tünellerini reddet"}, + {"Accept transit tunnels", "Transit tünellerini kabul et"}, + {"Cancel graceful shutdown", "Düzgün durdurmayı iptal Et"}, + {"Start graceful shutdown", "Düzgün durdurmayı başlat"}, + {"Force shutdown", "Durdurmaya zorla"}, + {"Reload external CSS styles", "Harici CSS stilini yeniden yükle"}, + {"Note: any action done here are not persistent and not changes your config files.", "Not: burada yapılan ayarların hiçbiri kalıcı değildir ve ayar dosyalarınızı değiştirmez."}, + {"Logging level", "Kayıt tutma seviyesi"}, + {"Transit tunnels limit", "Transit tünel limiti"}, + {"Change", "Değiştir"}, + {"Change language", "Dil değiştir"}, + {"no transit tunnels currently built", "kurulmuş bir transit tüneli bulunmamakta"}, + {"SAM disabled", "SAM devre dışı"}, + {"no sessions currently running", "hiçbir oturum şu anda çalışmıyor"}, + {"SAM session not found", "SAM oturumu bulunamadı"}, + {"SAM Session", "SAM oturumu"}, + {"Server Tunnels", "Sunucu Tünelleri"}, + {"Unknown page", "Bilinmeyen sayfa"}, + {"Invalid token", "Geçersiz token"}, + {"SUCCESS", "BAŞARILI"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d gün", "%d gün"}}, + {"%d hours", {"%d saat", "%d saat"}}, + {"%d minutes", {"%d dakika", "%d dakika"}}, + {"%d seconds", {"%d saniye", "%d saniye"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 356ada85..35ee0f89 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,15 +31,16 @@ namespace turkmen // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "bina"}, {"failed", "şowsuz"}, {"expiring", "möhleti gutarýar"}, {"established", "işleýär"}, {"unknown", "näbelli"}, {"exploratory", "gözleg"}, + {"Purple I2P Webconsole", "Web konsoly Purple I2P"}, {"i2pd webconsole", "Web konsoly i2pd"}, {"Main page", "Esasy sahypa"}, {"Router commands", "Marşrutizator buýruklary"}, @@ -57,7 +58,6 @@ namespace turkmen // language namespace {"Unknown", "Näbelli"}, {"Proxy", "Proksi"}, {"Mesh", "MESH-tor"}, - {"Error", "Ýalňyşlyk"}, {"Clock skew", "Takyk wagt däl"}, {"Offline", "Awtonom"}, {"Symmetric NAT", "Simmetriklik NAT"}, @@ -68,7 +68,7 @@ namespace turkmen // language namespace {"Family", "Maşgala"}, {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, {"Received", "Alnan"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Ýerleşdirildi"}, {"Transit", "Tranzit"}, {"Data path", "Maglumat ýoly"}, @@ -94,7 +94,7 @@ namespace turkmen // language namespace {"Type", "Görnüş"}, {"EncType", "Şifrlemek görnüşi"}, {"Inbound tunnels", "Gelýän tuneller"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Çykýan tuneller"}, {"Tags", "Bellikler"}, {"Incoming", "Gelýän"}, @@ -116,7 +116,6 @@ namespace turkmen // language namespace {"Gateway", "Derweze"}, {"TunnelID", "Tuneliň ID"}, {"EndDate", "Gutarýar"}, - {"not floodfill", "fludfil däl"}, {"Queue size", "Nobatyň ululygy"}, {"Run peer test", "Synag başlaň"}, {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, @@ -146,8 +145,6 @@ namespace turkmen // language namespace {"Destination not found", "Niýetlenen ýeri tapylmady"}, {"StreamID can't be null", "StreamID boş bolup bilmez"}, {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, - {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, - {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, {"Back to commands list", "Topar sanawyna dolan"}, {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, {"Description", "Beýany"}, @@ -165,32 +162,24 @@ namespace turkmen // language namespace {"You may try to find this host on jump services below", "Aşakdaky böküş hyzmatlarynda bu öý eýesini tapmaga synanyşyp bilersiňiz"}, {"Invalid request", "Nädogry haýyş"}, {"Proxy unable to parse your request", "Proksi haýyşyňyzy derňäp bilmeýär"}, - {"addresshelper is not supported", "Salgylandyryjy goldanok"}, - {"Host", "Adres"}, - {"added to router's addressbook from helper", "marşruteriň adresini kömekçiden goşdy"}, - {"Click here to proceed:", "Dowam etmek bu ýerde basyň:"}, - {"Continue", "Dowam et"}, - {"Addresshelper found", "Forgelper tapyldy"}, - {"already in router's addressbook", "marşruteriň adres kitaby"}, - {"Click here to update record:", "Recordazgyny täzelemek üçin bu ýerde basyň:"}, - {"invalid request uri", "nädogry haýyş URI"}, + {"Invalid request URI", "Nädogry haýyş URI"}, {"Can't detect destination host from request", "Haýyşdan barmaly ýerini tapyp bilemok"}, {"Outproxy failure", "Daşarky proksi ýalňyşlyk"}, - {"bad outproxy settings", "daşarky daşarky proksi sazlamalary nädogry"}, - {"not inside I2P network, but outproxy is not enabled", "I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, - {"unknown outproxy url", "näbelli daşarky proksi URL"}, - {"cannot resolve upstream proxy", "has ýokary proksi kesgitläp bilmeýär"}, - {"hostname too long", "hoster eýesi ady gaty uzyn"}, - {"cannot connect to upstream socks proxy", "ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, - {"Cannot negotiate with socks proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, + {"Bad outproxy settings", "Daşarky Daşarky proksi sazlamalary nädogry"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Adres %s I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, + {"Unknown outproxy URL", "Näbelli daşarky proksi URL"}, + {"Cannot resolve upstream proxy", "Has ýokary proksi kesgitläp bilmeýär"}, + {"Hostname is too long", "Hoster eýesi ady gaty uzyn"}, + {"Cannot connect to upstream SOCKS proxy", "Ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, + {"Cannot negotiate with SOCKS proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, {"CONNECT error", "Bagyr haýyşy säwligi"}, - {"Failed to Connect", "Birikdirip bilmedi"}, - {"socks proxy error", "socks proksi ýalňyşlygy"}, - {"failed to send request to upstream", "öý eýesi proksi üçin haýyş iberip bilmedi"}, - {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, - {"cannot connect", "birikdirip bilmedi"}, - {"http out proxy not implemented", "daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, - {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, + {"Failed to connect", "Birikdirip bilmedi"}, + {"SOCKS proxy error", "SOCKS proksi ýalňyşlygy"}, + {"Failed to send request to upstream", "Öý eýesi proksi üçin haýyş iberip bilmedi"}, + {"No reply from SOCKS proxy", "Jorap SOCKS proksi serwerinden hiç hili jogap ýok"}, + {"Cannot connect", "Birikdirip bilmedi"}, + {"HTTP out proxy not implemented", "Daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, + {"Cannot connect to upstream HTTP proxy", "Ýokary jorap HTTP proksi bilen birigip bolmaýar"}, {"Host is down", "Salgy elýeterli däl"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyşy soň gaýtalamaga synanyşyň."}, {"", ""}, @@ -198,10 +187,10 @@ namespace turkmen // language namespace static std::map> plurals { - {"days", {"gün", "gün"}}, - {"hours", {"sagat", "sagat"}}, - {"minutes", {"minut", "minut"}}, - {"seconds", {"sekunt", "sekunt"}}, + {"%d days", {"%d gün", "%d gün"}}, + {"%d hours", {"%d sagat", "%d sagat"}}, + {"%d minutes", {"%d minut", "%d minut"}}, + {"%d seconds", {"%d sekunt", "%d sekunt"}}, {"", {"", ""}}, }; diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index abbe8f81..c1e222ea 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,15 +31,16 @@ namespace ukrainian // language namespace static std::map strings { - {"KiB", "КіБ"}, - {"MiB", "МіБ"}, - {"GiB", "ГіБ"}, + {"%.2f KiB", "%.2f КіБ"}, + {"%.2f MiB", "%.2f МіБ"}, + {"%.2f GiB", "%.2f ГіБ"}, {"building", "будується"}, {"failed", "невдалий"}, {"expiring", "завершується"}, {"established", "працює"}, {"unknown", "невідомо"}, {"exploratory", "дослідницький"}, + {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, {"i2pd webconsole", "Веб-консоль i2pd"}, {"Main page", "Головна"}, {"Router commands", "Команди маршрутизатора"}, @@ -57,10 +58,11 @@ namespace ukrainian // language namespace {"Unknown", "Невідомо"}, {"Proxy", "Проксі"}, {"Mesh", "MESH-мережа"}, - {"Error", "Помилка"}, {"Clock skew", "Неточний час"}, {"Offline", "Офлайн"}, {"Symmetric NAT", "Симетричний NAT"}, + {"Full cone NAT", "Повний NAT"}, + {"No Descriptors", "Немає Описів"}, {"Uptime", "У мережі"}, {"Network status", "Мережевий статус"}, {"Network status v6", "Мережевий статус v6"}, @@ -68,7 +70,7 @@ namespace ukrainian // language namespace {"Family", "Сімейство"}, {"Tunnel creation success rate", "Успішно побудованих тунелів"}, {"Received", "Отримано"}, - {"KiB/s", "КіБ/с"}, + {"%.2f KiB/s", "%.2f КіБ/с"}, {"Sent", "Відправлено"}, {"Transit", "Транзит"}, {"Data path", "Шлях до даних"}, @@ -94,7 +96,7 @@ namespace ukrainian // language namespace {"Type", "Тип"}, {"EncType", "ТипШифр"}, {"Inbound tunnels", "Вхідні тунелі"}, - {"ms", "мс"}, + {"%dms", "%dмс"}, {"Outbound tunnels", "Вихідні тунелі"}, {"Tags", "Теги"}, {"Incoming", "Вхідні"}, @@ -116,9 +118,10 @@ namespace ukrainian // language namespace {"Gateway", "Шлюз"}, {"TunnelID", "ID тунеля"}, {"EndDate", "Закінчується"}, - {"not floodfill", "не флудфіл"}, + {"floodfill mode is disabled", "режим floodfill вимкнено"}, {"Queue size", "Розмір черги"}, {"Run peer test", "Запустити тестування"}, + {"Reload tunnels configuration", "Перезавантажити налаштування тунелів"}, {"Decline transit tunnels", "Відхиляти транзитні тунелі"}, {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, {"Cancel graceful shutdown", "Скасувати плавну зупинку"}, @@ -146,8 +149,8 @@ namespace ukrainian // language namespace {"Destination not found", "Точка призначення не знайдена"}, {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, {"Return to destination page", "Повернутися на сторінку точки призначення"}, - {"You will be redirected in 5 seconds", "Ви будете переадресовані через 5 секунд"}, - {"Transit tunnels count must not exceed 65535", "Кількість транзитних тунелів не повинна перевищувати 65535"}, + {"You will be redirected in %d seconds", "Ви будете переадресовані через %d секунд"}, + {"Transit tunnels count must not exceed %d", "Кількість транзитних тунелів не повинна перевищувати %d"}, {"Back to commands list", "Повернутися до списку команд"}, {"Register at reg.i2p", "Зареєструвати на reg.i2p"}, {"Description", "Опис"}, @@ -165,32 +168,33 @@ namespace ukrainian // language namespace {"You may try to find this host on jump services below", "Ви можете спробувати знайти дану адресу на джамп сервісах нижче"}, {"Invalid request", "Некоректний запит"}, {"Proxy unable to parse your request", "Проксі не може розібрати ваш запит"}, - {"addresshelper is not supported", "addresshelper не підтримується"}, - {"Host", "Адреса"}, - {"added to router's addressbook from helper", "доданий в адресну книгу маршрутизатора через хелпер"}, - {"Click here to proceed:", "Натисніть тут щоб продовжити:"}, - {"Continue", "Продовжити"}, - {"Addresshelper found", "Знайдено addresshelper"}, - {"already in router's addressbook", "вже в адресній книзі маршрутизатора"}, - {"Click here to update record:", "Натисніть тут щоб оновити запис:"}, - {"invalid request uri", "некоректний URI запиту"}, + {"Addresshelper is not supported", "Адресна книга не підтримується"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Будьте обережні: джерело цієї адреси може зашкодити! Натисніть тут, щоб оновити запис: Продовжити."}, + {"Addresshelper forced update rejected", "Адресна книга відхилила примусове оновлення"}, + {"To add host %s in router's addressbook, click here: Continue.", "Щоб додати хост %s в адресі маршрутизатора, натисніть тут: Продовжити."}, + {"Addresshelper request", "Запит на адресну сторінку"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Хост %s доданий в адресну книгу маршрутизатора від помічника. Натисніть тут, щоб продовжити: Продовжити."}, + {"Addresshelper adding", "Адреса додана"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Натисніть тут, щоб оновити запис: Продовжити."}, + {"Addresshelper update", "Оновлення адресної книги"}, + {"Invalid request URI", "Некоректний URI запиту"}, {"Can't detect destination host from request", "Не вдалось визначити адресу призначення з запиту"}, {"Outproxy failure", "Помилка зовнішнього проксі"}, - {"bad outproxy settings", "некоректні налаштування зовнішнього проксі"}, - {"not inside I2P network, but outproxy is not enabled", "не в I2P мережі, але зовнішній проксі не включений"}, - {"unknown outproxy url", "невідомий URL зовнішнього проксі"}, - {"cannot resolve upstream proxy", "не вдається визначити висхідний проксі"}, - {"hostname too long", "ім'я вузла надто довге"}, - {"cannot connect to upstream socks proxy", "не вдається підключитися до висхідного SOCKS проксі"}, - {"Cannot negotiate with socks proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, + {"Bad outproxy settings", "Некоректні налаштування зовнішнього проксі"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Адрес %s не в I2P мережі, але зовнішній проксі не включений"}, + {"Unknown outproxy URL", "Невідомий URL зовнішнього проксі"}, + {"Cannot resolve upstream proxy", "Не вдається визначити висхідний проксі"}, + {"Hostname is too long", "Ім'я вузла надто довге"}, + {"Cannot connect to upstream SOCKS proxy", "Не вдалося підключитися до висхідного SOCKS проксі сервера"}, + {"Cannot negotiate with SOCKS proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, {"CONNECT error", "Помилка CONNECT запиту"}, - {"Failed to Connect", "Не вдалося підключитися"}, - {"socks proxy error", "помилка SOCKS проксі"}, - {"failed to send request to upstream", "не вдалося відправити запит висхідному проксі"}, - {"No Reply From socks proxy", "Немає відповіді від SOCKS проксі сервера"}, - {"cannot connect", "не вдалося підключитися"}, - {"http out proxy not implemented", "підтримка зовнішнього HTTP проксі сервера не реалізована"}, - {"cannot connect to upstream http proxy", "не вдалося підключитися до висхідного HTTP проксі сервера"}, + {"Failed to connect", "Не вдалося підключитися"}, + {"SOCKS proxy error", "Помилка SOCKS проксі"}, + {"Failed to send request to upstream", "Не вдалося відправити запит висхідному проксі"}, + {"No reply from SOCKS proxy", "Немає відповіді від SOCKS проксі сервера"}, + {"Cannot connect", "Не вдалося підключитися"}, + {"HTTP out proxy not implemented", "Підтримка зовнішнього HTTP проксі сервера не реалізована"}, + {"Cannot connect to upstream HTTP proxy", "Не вдалося підключитися до висхідного HTTP проксі сервера"}, {"Host is down", "Вузол недоступний"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, {"", ""}, @@ -198,10 +202,10 @@ namespace ukrainian // language namespace static std::map> plurals { - {"days", {"день", "дня", "днів"}}, - {"hours", {"годину", "години", "годин"}}, - {"minutes", {"хвилину", "хвилини", "хвилин"}}, - {"seconds", {"секунду", "секунди", "секунд"}}, + {"%d days", {"%d день", "%d дня", "%d днів"}}, + {"%d hours", {"%d годину", "%d години", "%d годин"}}, + {"%d minutes", {"%d хвилину", "%d хвилини", "%d хвилин"}}, + {"%d seconds", {"%d секунду", "%d секунди", "%d секунд"}}, {"", {"", "", ""}}, }; diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp index e750918f..9d798be4 100644 --- a/i18n/Uzbek.cpp +++ b/i18n/Uzbek.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,15 +31,16 @@ namespace uzbek // language namespace static std::map strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "yaratilmoqda"}, {"failed", "muvaffaqiyatsiz"}, {"expiring", "muddati tugaydi"}, {"established", "aloqa o'rnatildi"}, {"unknown", "noma'lum"}, {"exploratory", "tadqiqiy"}, + {"Purple I2P Webconsole", "Veb-konsoli Purple I2P"}, {"i2pd webconsole", "i2pd veb-konsoli"}, {"Main page", "Asosiy sahifa"}, {"Router commands", "Router buyruqlari"}, @@ -57,10 +58,11 @@ namespace uzbek // language namespace {"Unknown", "Notanish"}, {"Proxy", "Proksi"}, {"Mesh", "Mesh To'r"}, - {"Error", "Xato"}, {"Clock skew", "Aniq vaqt emas"}, {"Offline", "Oflayn"}, {"Symmetric NAT", "Simmetrik NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Deskriptorlar yo'q"}, {"Uptime", "Ish vaqti"}, {"Network status", "Tarmoq holati"}, {"Network status v6", "Tarmoq holati v6"}, @@ -68,7 +70,7 @@ namespace uzbek // language namespace {"Family", "Oila"}, {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, {"Received", "Qabul qilindi"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Yuborilgan"}, {"Transit", "Tranzit"}, {"Data path", "Ma'lumotlar joylanishi"}, @@ -94,7 +96,7 @@ namespace uzbek // language namespace {"Type", "Turi"}, {"EncType", "ShifrlashTuri"}, {"Inbound tunnels", "Kirish tunnellari"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Chiquvchi tunnellar"}, {"Tags", "Teglar"}, {"Incoming", "Kiruvchi"}, @@ -116,9 +118,10 @@ namespace uzbek // language namespace {"Gateway", "Kirish yo'li"}, {"TunnelID", "TunnelID"}, {"EndDate", "Tugash Sanasi"}, - {"not floodfill", "floodfill emas"}, + {"floodfill mode is disabled", "floodfill rejimi o'chirilgan"}, {"Queue size", "Navbat hajmi"}, {"Run peer test", "Sinovni boshlang"}, + {"Reload tunnels configuration", "Tunnel konfiguratsiyasini qayta yuklash"}, {"Decline transit tunnels", "Tranzit tunnellarini rad etish"}, {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qilish"}, @@ -146,8 +149,8 @@ namespace uzbek // language namespace {"Destination not found", "Yo'nalish topilmadi"}, {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, {"Return to destination page", "Manzilgoh sahifasiga qaytish"}, - {"You will be redirected in 5 seconds", "Siz 5 soniya ichida qayta yo'naltirilasiz"}, - {"Transit tunnels count must not exceed 65535", "Tranzit tunnellar soni 65535 dan oshmasligi kerak"}, + {"You will be redirected in %d seconds", "Siz %d soniyadan so‘ng boshqa yo‘nalishga yo‘naltirilasiz"}, + {"Transit tunnels count must not exceed %d", "Tranzit tunnellar soni %d dan oshmasligi kerak"}, {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, {"Description", "Tavsif"}, @@ -165,32 +168,33 @@ namespace uzbek // language namespace {"You may try to find this host on jump services below", "Siz xost quyida o'tish xizmatlari orqali topishga harakat qilishingiz mumkin"}, {"Invalid request", "Noto‘g‘ri so‘rov"}, {"Proxy unable to parse your request", "Proksi sizning so'rovingizni aniqlab ololmayapti"}, - {"addresshelper is not supported", "addresshelper qo'llab -quvvatlanmaydi"}, - {"Host", "Xost"}, - {"added to router's addressbook from helper", "'helper'dan routerning 'addressbook'ga qo'shildi"}, - {"Click here to proceed:", "Davom etish uchun shu yerni bosing:"}, - {"Continue", "Davom etish"}, - {"Addresshelper found", "Addresshelper topildi"}, - {"already in router's addressbook", "allaqachon 'addressbook'da yozilgan"}, - {"Click here to update record:", "Yozuvni yangilash uchun shu yerni bosing:"}, - {"invalid request uri", "noto'g'ri URI so'rovi"}, + {"Addresshelper is not supported", "Addresshelper qo'llab-quvvatlanmaydi"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Ehtiyot bo'ling: bu URL manbasi zararli bo'lishi mumkin! Yozuvni yangilash uchun bu yerni bosing: Davom etish."}, + {"Addresshelper forced update rejected", "Addresshelperni majburiy yangilash rad etildi"}, + {"To add host %s in router's addressbook, click here: Continue.", "Routerning manzillar kitobiga %s xostini qo'shish uchun bu yerni bosing: Davom etish."}, + {"Addresshelper request", "Addresshelper so'rovi"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Yordamchidan router manzillar kitobiga %s xost qo‘shildi. Davom etish uchun bu yerga bosing: Davom etish."}, + {"Addresshelper adding", "Addresshelperni qo'shish"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Yozuvni yangilash uchun shu yerni bosing: Davom etish."}, + {"Addresshelper update", "Addresshelperni yangilash"}, + {"Invalid request URI", "Noto'g'ri URI so'rovi"}, {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, - {"bad outproxy settings", "noto'g'ri tashqi proksi-server sozlamalari"}, - {"not inside I2P network, but outproxy is not enabled", "I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, - {"unknown outproxy url", "noma'lum outproxy url"}, - {"cannot resolve upstream proxy", "yuqoridagi 'proxy-server'ni aniqlab olib bolmayapti"}, - {"hostname too long", "xost nomi juda uzun"}, - {"cannot connect to upstream socks proxy", "yuqori 'socks proxy'ga ulanib bo'lmayapti"}, - {"Cannot negotiate with socks proxy", "'Socks proxy' bilan muzokara olib bo'lmaydi"}, + {"Bad outproxy settings", "Noto'g'ri tashqi proksi-server sozlamalari"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Xost %s I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, + {"Unknown outproxy URL", "Noma'lum outproxy URL"}, + {"Cannot resolve upstream proxy", "Yuqoridagi 'proxy-server'ni aniqlab olib bolmayapti"}, + {"Hostname is too long", "Xost nomi juda uzun"}, + {"Cannot connect to upstream SOCKS proxy", "Yuqori 'SOCKS proxy'ga ulanib bo'lmayapti"}, + {"Cannot negotiate with SOCKS proxy", "'SOCKS proxy' bilan muzokara olib bo'lmaydi"}, {"CONNECT error", "CONNECT xatosi"}, - {"Failed to Connect", "Ulanib bo'lmayapti"}, - {"socks proxy error", "'socks proxy' xatosi"}, - {"failed to send request to upstream", "yuqori http proksi-serveriga so'rovni uborib bo'lmadi"}, - {"No Reply From socks proxy", "'Socks proxy'dan javob yo'q"}, - {"cannot connect", "ulanib bo'lmaydi"}, - {"http out proxy not implemented", "tashqi HTTP proksi-serverni qo'llab-quvvatlash amalga oshirilmagan"}, - {"cannot connect to upstream http proxy", "yuqori http 'proxy-server'iga ulanib bo'lmayapti"}, + {"Failed to connect", "Ulanib bo'lmayapti"}, + {"SOCKS proxy error", "'SOCKS proxy' xatosi"}, + {"Failed to send request to upstream", "Yuqori proksi-serveriga so'rovni uborib bo'lmadi"}, + {"No reply from SOCKS proxy", "'SOCKS proxy'dan javob yo'q"}, + {"Cannot connect", "Ulanib bo'lmaydi"}, + {"HTTP out proxy not implemented", "Tashqi HTTP proksi-serverni qo'llab-quvvatlash amalga oshirilmagan"}, + {"Cannot connect to upstream HTTP proxy", "Yuqori 'HTTP proxy'ga ulanib bo'lmayapti"}, {"Host is down", "Xost ishlamayapti"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Talab qilingan xost bilan aloqa o'rnatilmadi, u ishlamay qolishi mumkin. Iltimos keyinroq qayta urinib ko'ring."}, {"", ""}, @@ -198,10 +202,10 @@ namespace uzbek // language namespace static std::map> plurals { - {"days", {"kun", "kun"}}, - {"hours", {"soat", "soat"}}, - {"minutes", {"daqiqa", "daqiqa"}}, - {"seconds", {"soniya", "soniya"}}, + {"%d days", {"%d kun", "%d kun"}}, + {"%d hours", {"%d soat", "%d soat"}}, + {"%d minutes", {"%d daqiqa", "%d daqiqa"}}, + {"%d seconds", {"%d soniya", "%d soniya"}}, {"", {"", ""}}, }; diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 65e5f78c..ced086e1 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -135,7 +135,7 @@ namespace data //---------------------------------------------------------- const uint8_t B33_TWO_BYTES_SIGTYPE_FLAG = 0x01; - const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now + // const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now const uint8_t B33_PER_CLIENT_AUTH_FLAG = 0x04; BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, bool clientAuth): diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 046f0a25..8df08118 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,7 +45,7 @@ namespace config { ("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)") ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") - ("host", value()->default_value("0.0.0.0"), "External IP") + ("host", value()->default_value(""), "External IP") ("ifname", value()->default_value(""), "Network interface to bind to") ("ifname4", value()->default_value(""), "Network interface to bind to for ipv4") ("ifname6", value()->default_value(""), "Network interface to bind to for ipv6") @@ -77,7 +77,7 @@ namespace config { limits.add_options() ("limits.coresize", value()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") ("limits.openfiles", value()->default_value(0), "Maximum number of open files (0 - use system default)") - ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") + ("limits.transittunnels", value()->default_value(5000), "Maximum active transit tunnels (default:5000)") ("limits.ntcpsoft", value()->default_value(0), "Ignored") ("limits.ntcphard", value()->default_value(0), "Ignored") ("limits.ntcpthreads", value()->default_value(1), "Ignored") @@ -95,6 +95,7 @@ namespace config { ("http.hostname", value()->default_value("localhost"), "Expected hostname for WebUI") ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") ("http.lang", value()->default_value("english"), "WebUI language (default: english )") + ("http.showTotalTCSR", value()->default_value(false), "Show additional value with total TCSR since router's start (default: false)") ; options_description httpproxy("HTTP Proxy options"); @@ -284,7 +285,7 @@ namespace config { options_description nettime("Time sync options"); nettime.add_options() - ("nettime.enabled", value()->default_value(false), "Disable time sync (default: disabled)") + ("nettime.enabled", value()->default_value(false), "Enable NTP time sync (default: disabled)") ("nettime.ntpservers", value()->default_value( "0.pool.ntp.org," "1.pool.ntp.org," diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index b152cdd0..c9670f6c 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -159,8 +159,10 @@ namespace crypto // DH/ElGamal +#if !defined(__x86_64__) const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; +#endif const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index c39a2533..5d6b71b9 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -399,6 +399,11 @@ namespace client void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) { + if (len < DATABASE_STORE_HEADER_SIZE) + { + LogPrint (eLogError, "Destination: Database store msg is too short ", len); + return; + } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) @@ -406,6 +411,11 @@ namespace client LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore"); offset += 36; } + if (offset > len || len > i2p::data::MAX_LS_BUFFER_SIZE + offset) + { + LogPrint (eLogError, "Destination: Database store message is too long ", len); + return; + } i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET); std::shared_ptr leaseSet; switch (buf[DATABASE_STORE_TYPE_OFFSET]) @@ -467,12 +477,15 @@ namespace client { auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ()); - if (ls2->IsValid ()) + if (ls2->IsValid () && !ls2->IsExpired ()) { + leaseSet = ls2; + std::lock_guard lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup - leaseSet = ls2; } + else + LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed"); } else LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2"); @@ -763,9 +776,17 @@ namespace client request->requestTime = ts; if (!SendLeaseSetRequest (dest, floodfill, request)) { - // request failed - m_LeaseSetRequests.erase (ret.first); - if (requestComplete) requestComplete (nullptr); + // try another + LogPrint (eLogWarning, "Destination: Couldn't send LeaseSet request to ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another"); + request->excluded.insert (floodfill->GetIdentHash ()); + floodfill = i2p::data::netdb.GetClosestFloodfill (dest, request->excluded); + if (!SendLeaseSetRequest (dest, floodfill, request)) + { + // request failed + LogPrint (eLogWarning, "Destination: LeaseSet request for ", dest.ToBase32 (), " was not sent"); + m_LeaseSetRequests.erase (ret.first); + if (requestComplete) requestComplete (nullptr); + } } } else // duplicate @@ -792,11 +813,11 @@ namespace client std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) - request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); - if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); + request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); // outbound from floodfill + if (!request->replyTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) - request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); - if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); // inbound from floodfill + if (!request->outboundTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) { @@ -1096,13 +1117,13 @@ namespace client } auto leaseSet = FindLeaseSet (dest); if (leaseSet) - { + { auto stream = CreateStream (leaseSet, port); - GetService ().post ([streamRequestComplete, stream]() - { + GetService ().post ([streamRequestComplete, stream]() + { streamRequestComplete(stream); }); - } + } else { auto s = GetSharedFromThis (); @@ -1135,35 +1156,41 @@ namespace client }); } - template - std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, int port) + template + std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, int port) { + volatile bool done = false; std::shared_ptr stream; std::condition_variable streamRequestComplete; std::mutex streamRequestCompleteMutex; - std::unique_lock l(streamRequestCompleteMutex); CreateStream ( - [&streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) + [&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) { stream = s; std::unique_lock l(streamRequestCompleteMutex); streamRequestComplete.notify_all (); + done = true; }, dest, port); - streamRequestComplete.wait (l); + while (!done) + { + std::unique_lock l(streamRequestCompleteMutex); + if (!done) + streamRequestComplete.wait (l); + } return stream; - } + } - std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port) + std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port) { return CreateStreamSync (dest, port); - } + } std::shared_ptr ClientDestination::CreateStream (std::shared_ptr dest, int port) { return CreateStreamSync (dest, port); - } - + } + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) { if (m_StreamingDestination) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 9d369a92..9e2d171f 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -284,9 +284,9 @@ namespace client void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); void ReadAuthKey (const std::string& group, const std::map * params); - template + template std::shared_ptr CreateStreamSync (const Dest& dest, int port); - + private: i2p::data::PrivateKeys m_Keys; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index e240e925..7f9fb72d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -335,7 +335,8 @@ namespace garlic case eECIESx25519BlkAckRequest: { LogPrint (eLogDebug, "Garlic: Ack request"); - m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); + if (receiveTagset) + m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); break; } case eECIESx25519BlkTermination: diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 8791eadb..7cc87df8 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -160,6 +160,7 @@ namespace http return true; } else if (url.at(pos_c) == '?') { /* found query part */ + hasquery = true; path = url.substr(pos_p, pos_c - pos_p); pos_p = pos_c + 1; pos_c = url.find('#', pos_p); @@ -218,8 +219,10 @@ namespace http } } out += path; + if (hasquery) // add query even if it was empty + out += "?"; if (query != "") - out += "?" + query; + out += query; if (frag != "") out += "#" + frag; return out; @@ -347,6 +350,14 @@ namespace http return ""; } + size_t HTTPReq::GetNumHeaders (const std::string& name) const + { + size_t num = 0; + for (auto& it : headers) + if (it.first == name) num++; + return num; + } + bool HTTPRes::is_chunked() const { auto it = headers.find("Transfer-Encoding"); diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 9445a01a..f9d5d009 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,10 +33,11 @@ namespace http std::string host; unsigned short int port; std::string path; + bool hasquery; std::string query; std::string frag; - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), query(""), frag("") {}; /** * @brief Tries to parse url from string @@ -101,6 +102,8 @@ namespace http void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; std::string GetHeader (const std::string& name) const; + size_t GetNumHeaders (const std::string& name) const; + size_t GetNumHeaders () const { return headers.size (); }; }; struct HTTPRes : HTTPMsg { diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e19e782d..4d1ab6d4 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -392,7 +392,8 @@ namespace i2p clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) + retCode = 30; } else retCode = 30; // always reject with bandwidth reason (30) @@ -558,7 +559,8 @@ namespace i2p return; } auto& noiseState = i2p::context.GetCurrentNoiseState (); - uint8_t replyKey[32], layerKey[32], ivKey[32]; + uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305 + i2p::crypto::AESKey layerKey, ivKey; // AES i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); memcpy (replyKey, noiseState.m_CK + 32, 32); i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); @@ -589,7 +591,8 @@ namespace i2p layerKey, ivKey, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) + retCode = 30; } // encrypt reply diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index e60f6a9c..a388dc52 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -309,7 +309,7 @@ namespace tunnel std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; - const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; + const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000; void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); uint16_t GetMaxNumTransitTunnels (); } diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 310e6081..bf9c26c5 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -64,7 +64,7 @@ namespace data else for (int i = 0; i < 8; i++) // 256 bytes memcpy (m_StandardIdentity.publicKey + 32*i, randomPaddingBlock, 32); - } + } if (type != SIGNING_KEY_TYPE_DSA_SHA1) { size_t excessLen = 0; @@ -717,7 +717,7 @@ namespace data uint8_t publicKey[256]; if (isDestination) RAND_bytes (keys.m_PrivateKey, 256); - else + else GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); // identity keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType); diff --git a/libi2pd/KadDHT.cpp b/libi2pd/KadDHT.cpp new file mode 100644 index 00000000..48486675 --- /dev/null +++ b/libi2pd/KadDHT.cpp @@ -0,0 +1,372 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include "KadDHT.h" + +namespace i2p +{ +namespace data +{ + DHTNode::DHTNode (): + zero (nullptr), one (nullptr) + { + } + + DHTNode::~DHTNode () + { + if (zero) delete zero; + if (one) delete one; + } + + void DHTNode::MoveRouterUp (bool fromOne) + { + DHTNode *& side = fromOne ? one : zero; + if (side) + { + if (router) router = nullptr; // shouldn't happen + router = side->router; + side->router = nullptr; + delete side; + side = nullptr; + } + } + + DHTTable::DHTTable (): + m_Size (0) + { + m_Root = new DHTNode; + } + + DHTTable::~DHTTable () + { + delete m_Root; + } + + void DHTTable::Clear () + { + m_Size = 0; + delete m_Root; + m_Root = new DHTNode; + } + + void DHTTable::Insert (const std::shared_ptr& r) + { + if (!r) return; + return Insert (r, m_Root, 0); + } + + void DHTTable::Insert (const std::shared_ptr& r, DHTNode * root, int level) + { + if (root->router) + { + if (root->router->GetIdentHash () == r->GetIdentHash ()) + { + root->router = r; // replace + return; + } + auto r2 = root->router; + root->router = nullptr; m_Size--; + int bit1, bit2; + do + { + bit1 = r->GetIdentHash ().GetBit (level); + bit2 = r2->GetIdentHash ().GetBit (level); + if (bit1 == bit2) + { + if (bit1) + { + if (root->one) return; // someting wrong + root->one = new DHTNode; + root = root->one; + } + else + { + if (root->zero) return; // someting wrong + root->zero = new DHTNode; + root = root->zero; + } + level++; + } + } + while (bit1 == bit2); + + if (!root->zero) + root->zero = new DHTNode; + if (!root->one) + root->one = new DHTNode; + if (bit1) + { + Insert (r2, root->zero, level + 1); + Insert (r, root->one, level + 1); + } + else + { + Insert (r2, root->one, level + 1); + Insert (r, root->zero, level + 1); + } + } + else + { + if (!root->zero && !root->one) + { + root->router = r; m_Size++; + return; + } + int bit = r->GetIdentHash ().GetBit (level); + if (bit) + { + if (!root->one) + root->one = new DHTNode; + Insert (r, root->one, level + 1); + } + else + { + if (!root->zero) + root->zero = new DHTNode; + Insert (r, root->zero, level + 1); + } + } + } + + bool DHTTable::Remove (const IdentHash& h) + { + return Remove (h, m_Root, 0); + } + + bool DHTTable::Remove (const IdentHash& h, DHTNode * root, int level) + { + if (root) + { + if (root->router && root->router->GetIdentHash () == h) + { + root->router = nullptr; + m_Size--; + return true; + } + int bit = h.GetBit (level); + if (bit) + { + if (root->one && Remove (h, root->one, level + 1)) + { + if (root->one->IsEmpty ()) + { + delete root->one; + root->one = nullptr; + if (root->zero && root->zero->router) + root->MoveRouterUp (false); + } + else if (root->one->router && !root->zero) + root->MoveRouterUp (true); + return true; + } + } + else + { + if (root->zero && Remove (h, root->zero, level + 1)) + { + if (root->zero->IsEmpty ()) + { + delete root->zero; + root->zero = nullptr; + if (root->one && root->one->router) + root->MoveRouterUp (true); + } + else if (root->zero->router && !root->one) + root->MoveRouterUp (false); + return true; + } + } + } + return false; + } + + std::shared_ptr DHTTable::FindClosest (const IdentHash& h, const Filter& filter) + { + if (filter) m_Filter = filter; + auto r = FindClosest (h, m_Root, 0); + m_Filter = nullptr; + return r; + } + + std::shared_ptr DHTTable::FindClosest (const IdentHash& h, DHTNode * root, int level) + { + bool split = false; + do + { + if (root->router) + return (!m_Filter || m_Filter (root->router)) ? root->router : nullptr; + split = root->zero && root->one; + if (!split) + { + if (root->zero) root = root->zero; + else if (root->one) root = root->one; + else return nullptr; + level++; + } + } + while (!split); + int bit = h.GetBit (level); + if (bit) + { + if (root->one) + { + auto r = FindClosest (h, root->one, level + 1); + if (r) return r; + } + if (root->zero) + { + auto r = FindClosest (h, root->zero, level + 1); + if (r) return r; + } + } + else + { + if (root->zero) + { + auto r = FindClosest (h, root->zero, level + 1); + if (r) return r; + } + if (root->one) + { + auto r = FindClosest (h, root->one, level + 1); + if (r) return r; + } + } + return nullptr; + } + + std::vector > DHTTable::FindClosest (const IdentHash& h, size_t num, const Filter& filter) + { + std::vector > vec; + if (num > 0) + { + if (filter) m_Filter = filter; + FindClosest (h, num, m_Root, 0, vec); + m_Filter = nullptr; + } + return vec; + } + + void DHTTable::FindClosest (const IdentHash& h, size_t num, DHTNode * root, int level, std::vector >& hashes) + { + if (hashes.size () >= num) return; + bool split = false; + do + { + if (root->router) + { + if (!m_Filter || m_Filter (root->router)) + hashes.push_back (root->router); + return; + } + split = root->zero && root->one; + if (!split) + { + if (root->zero) root = root->zero; + else if (root->one) root = root->one; + else return; + level++; + } + } + while (!split); + int bit = h.GetBit (level); + if (bit) + { + if (root->one) + FindClosest (h, num, root->one, level + 1, hashes); + if (hashes.size () < num && root->zero) + FindClosest (h, num, root->zero, level + 1, hashes); + } + else + { + if (root->zero) + FindClosest (h, num, root->zero, level + 1, hashes); + if (hashes.size () < num && root->one) + FindClosest (h, num, root->one, level + 1, hashes); + } + } + + void DHTTable::Cleanup (Filter filter) + { + if (filter) + { + m_Filter = filter; + Cleanup (m_Root); + m_Filter = nullptr; + } + else + Clear (); + } + + void DHTTable::Cleanup (DHTNode * root) + { + if (!root) return; + if (root->router) + { + if (!m_Filter || !m_Filter (root->router)) + { + m_Size--; + root->router = nullptr; + } + return; + } + if (root->zero) + { + Cleanup (root->zero); + if (root->zero->IsEmpty ()) + { + delete root->zero; + root->zero = nullptr; + } + } + if (root->one) + { + Cleanup (root->one); + if (root->one->IsEmpty ()) + { + delete root->one; + root->one = nullptr; + if (root->zero && root->zero->router) + root->MoveRouterUp (false); + } + else if (root->one->router && !root->zero) + root->MoveRouterUp (true); + } + } + + void DHTTable::Print (std::stringstream& s) + { + Print (s, m_Root, 0); + } + + void DHTTable::Print (std::stringstream& s, DHTNode * root, int level) + { + if (!root) return; + s << std::string (level, '-'); + if (root->router) + { + if (!root->zero && !root->one) + s << '>' << GetIdentHashAbbreviation (root->router->GetIdentHash ()); + else + s << "error"; + } + s << std::endl; + if (root->zero) + { + s << std::string (level, '-') << "0" << std::endl; + Print (s, root->zero, level + 1); + } + if (root->one) + { + s << std::string (level, '-') << "1" << std::endl; + Print (s, root->one, level + 1); + } + } +} +} diff --git a/libi2pd/KadDHT.h b/libi2pd/KadDHT.h new file mode 100644 index 00000000..c280a1de --- /dev/null +++ b/libi2pd/KadDHT.h @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#ifndef KADDHT_H__ +#define KADDHT_H__ + +#include +#include +#include +#include +#include "RouterInfo.h" + +// Kademlia DHT (XOR distance) + +namespace i2p +{ +namespace data +{ + struct DHTNode + { + DHTNode * zero, * one; + std::shared_ptr router; + + DHTNode (); + ~DHTNode (); + + bool IsEmpty () const { return !zero && !one && !router; }; + void MoveRouterUp (bool fromOne); + }; + + class DHTTable + { + typedef std::function&)> Filter; + public: + + DHTTable (); + ~DHTTable (); + + void Insert (const std::shared_ptr& r); + bool Remove (const IdentHash& h); + std::shared_ptr FindClosest (const IdentHash& h, const Filter& filter = nullptr); + std::vector > FindClosest (const IdentHash& h, size_t num, const Filter& filter = nullptr); + + void Print (std::stringstream& s); + size_t GetSize () const { return m_Size; }; + void Clear (); + void Cleanup (Filter filter); + + private: + + void Insert (const std::shared_ptr& r, DHTNode * root, int level); // recursive + bool Remove (const IdentHash& h, DHTNode * root, int level); + std::shared_ptr FindClosest (const IdentHash& h, DHTNode * root, int level); + void FindClosest (const IdentHash& h, size_t num, DHTNode * root, int level, std::vector >& hashes); + void Cleanup (DHTNode * root); + void Print (std::stringstream& s, DHTNode * root, int level); + + private: + + DHTNode * m_Root; + size_t m_Size; + // transient + Filter m_Filter; + }; +} +} + +#endif diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 12b064b5..4d756bdb 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -261,12 +261,12 @@ namespace data { LogPrint (eLogError, "LeaseSet: Buffer is too long ", len); len = MAX_LS_BUFFER_SIZE; - } - if (m_Buffer && len > m_BufferLen) - { + } + if (m_Buffer && len > m_BufferLen) + { delete[] m_Buffer; m_Buffer = nullptr; - } + } if (!m_Buffer) m_Buffer = new uint8_t[len]; m_BufferLen = len; @@ -315,7 +315,7 @@ namespace data { // standard LS2 header std::shared_ptr identity; - if (readIdentity) + if (readIdentity || !GetIdentity ()) { identity = std::make_shared(buf, len); SetIdentity (identity); @@ -366,6 +366,8 @@ namespace data VerifySignature (identity, buf, len, offset); SetIsValid (verified); } + else + SetIsValid (true); offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : identity->GetSignatureLen (); if (offset > len) { LogPrint (eLogWarning, "LeaseSet2: short buffer: wanted ", int(offset), "bytes, have ", int(len)); diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index a79a5870..566c4655 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -145,6 +145,7 @@ namespace data { public: + LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only uint8_t GetStoreType () const { return m_StoreType; }; diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index e90b5e2b..65b00e10 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,11 +18,11 @@ namespace log { /** * @brief Maps our loglevel to their symbolic name */ - static const char * g_LogLevelStr[eNumLogLevels] = + static const char *g_LogLevelStr[eNumLogLevels] = { "none", // eLogNone "error", // eLogError - "warn", // eLogWarn + "warn", // eLogWarning "info", // eLogInfo "debug" // eLogDebug }; @@ -35,12 +35,12 @@ namespace log { static const char *LogMsgColors[] = { "", "", "", "", "", "" }; #else /* UNIX */ static const char *LogMsgColors[] = { - [eLogNone] = "\033[0m", /* reset */ - [eLogError] = "\033[1;31m", /* red */ - [eLogWarning] = "\033[1;33m", /* yellow */ - [eLogInfo] = "\033[1;36m", /* cyan */ - [eLogDebug] = "\033[1;34m", /* blue */ - [eNumLogLevels] = "\033[0m", /* reset */ + "\033[1;32m", /* none: green */ + "\033[1;31m", /* error: red */ + "\033[1;33m", /* warning: yellow */ + "\033[1;36m", /* info: cyan */ + "\033[1;34m", /* debug: blue */ + "\033[0m" /* reset */ }; #endif @@ -126,7 +126,7 @@ namespace log { if (level == "none") { m_MinLevel = eLogNone; } else if (level == "error") { m_MinLevel = eLogError; } else if (level == "warn") { m_MinLevel = eLogWarning; } - else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "info") { m_MinLevel = eLogInfo; } else if (level == "debug") { m_MinLevel = eLogDebug; } else { LogPrint(eLogError, "Log: Unknown loglevel: ", level); diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index f78999be..eaa186f8 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -139,7 +139,7 @@ namespace transport m3p2[3] = 0; // flag memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex // 2 bytes reserved - htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsA + htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds // 4 bytes reserved // sign and encrypt options, use m_H as AD uint8_t nonce[12]; @@ -162,7 +162,7 @@ namespace transport uint8_t options[16]; memset (options, 0, 16); htobe16buf (options + 2, paddingLen); // padLen - htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsB + htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds // sign and encrypt options, use m_H as AD uint8_t nonce[12]; memset (nonce, 0, 12); // set nonce to zero @@ -374,10 +374,16 @@ namespace transport transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCP2Session (shared_from_this ()); m_SendQueue.clear (); + m_SendQueueSize = 0; LogPrint (eLogDebug, "NTCP2: Session terminated"); } } + void NTCP2Session::Close () + { + m_Socket.close (); + } + void NTCP2Session::TerminateByTimeout () { SendTerminationAndTerminate (eNTCP2IdleTimeout); @@ -687,10 +693,20 @@ namespace transport SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = ri.GetNTCP2AddressWithStaticKey (m_Establisher->m_RemoteStaticKey); - if (!addr) + auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () : + (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ()); + if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) { - LogPrint (eLogError, "NTCP2: No NTCP2 address with static key found in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); + Terminate (); + return; + } + if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host && + (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? + 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 (); return; } @@ -746,6 +762,8 @@ namespace transport void NTCP2Session::ServerLogin () { + SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); m_Establisher->CreateEphemeralKey (); boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), @@ -866,8 +884,20 @@ namespace transport switch (blk) { case eNTCP2BlkDateTime: + { LogPrint (eLogDebug, "NTCP2: Datetime"); - break; + if (m_IsEstablished) + { + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + uint64_t tsA = bufbe32toh (frame + offset); + if (tsA < ts - NTCP2_CLOCK_SKEW || tsA > ts + NTCP2_CLOCK_SKEW) + { + LogPrint (eLogWarning, "NTCP2: Established session time difference ", (int)(ts - tsA), " exceeds clock skew"); + SendTerminationAndTerminate (eNTCP2ClockSkew); + } + } + break; + } case eNTCP2BlkOptions: LogPrint (eLogDebug, "NTCP2: Options"); break; @@ -1050,7 +1080,10 @@ namespace transport SendRouterInfo (); } else + { SendQueue (); + m_SendQueueSize = m_SendQueue.size (); + } } } @@ -1109,12 +1142,17 @@ namespace transport { if (!IsEstablished ()) return; auto riLen = i2p::context.GetRouterInfo ().GetBufferLen (); - size_t payloadLen = riLen + 4; // 3 bytes block header + 1 byte RI flag + size_t payloadLen = riLen + 3 + 1 + 7; // 3 bytes block header + 1 byte RI flag + 7 bytes DateTime m_NextSendBuffer = new uint8_t[payloadLen + 16 + 2 + 64]; // up to 64 bytes padding - m_NextSendBuffer[2] = eNTCP2BlkRouterInfo; - htobe16buf (m_NextSendBuffer + 3, riLen + 1); // size - m_NextSendBuffer[5] = 0; // flag - memcpy (m_NextSendBuffer + 6, i2p::context.GetRouterInfo ().GetBuffer (), riLen); + // DateTime block + m_NextSendBuffer[2] = eNTCP2BlkDateTime; + htobe16buf (m_NextSendBuffer + 3, 4); + htobe32buf (m_NextSendBuffer + 5, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + // RouterInfo block + m_NextSendBuffer[9] = eNTCP2BlkRouterInfo; + htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size + m_NextSendBuffer[12] = 0; // flag + memcpy (m_NextSendBuffer + 13, i2p::context.GetRouterInfo ().GetBuffer (), riLen); // padding block auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); payloadLen += paddingSize; @@ -1158,7 +1196,7 @@ namespace transport { if (m_IsTerminated) return; for (auto it: msgs) - m_SendQueue.push_back (it); + m_SendQueue.push_back (std::move (it)); if (!m_IsSending) SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) @@ -1167,6 +1205,7 @@ namespace transport GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } + m_SendQueueSize = m_SendQueue.size (); } void NTCP2Session::SendLocalRouterInfo (bool update) @@ -1289,7 +1328,7 @@ namespace transport for (auto& it: ntcpSessions) it.second->Terminate (); for (auto& it: m_PendingIncomingSessions) - it->Terminate (); + it.second->Terminate (); } m_NTCP2Sessions.clear (); @@ -1305,20 +1344,32 @@ namespace transport { if (!session) return false; if (incoming) - m_PendingIncomingSessions.remove (session); - if (!session->GetRemoteIdentity ()) return false; + m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ()); + if (!session->GetRemoteIdentity ()) + { + LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ()); + session->Terminate (); + return false; + } auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); auto it = m_NTCP2Sessions.find (ident); if (it != m_NTCP2Sessions.end ()) { - LogPrint (eLogWarning, "NTCP2: Session to ", ident.ToBase64 (), " already exists"); + LogPrint (eLogWarning, "NTCP2: Session with ", ident.ToBase64 (), " already exists. ", incoming ? "Replaced" : "Dropped"); if (incoming) + { // replace by new session - it->second->Terminate (); + auto s = it->second; + m_NTCP2Sessions.erase (it); + s->Terminate (); + } else + { + session->Terminate (); return false; + } } - m_NTCP2Sessions.insert (std::make_pair (ident, session)); + m_NTCP2Sessions.emplace (ident, session); return true; } @@ -1406,36 +1457,42 @@ namespace transport void NTCP2Server::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error) + if (!error && conn) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (conn) + if (!i2p::util::net::IsInReservedRange(ep.address ())) { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); - conn = nullptr; + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) + { + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + conn = nullptr; + } + else + LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } + else + LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } else - { + { LogPrint (eLogError, "NTCP2: Accept error ", error.message ()); if (error == boost::asio::error::no_descriptors) { i2p::context.SetError (eRouterErrorNoDescriptors); - return; - } - } - + return; + } + } + if (error != boost::asio::error::operation_aborted) - { + { if (!conn) // connection is used, create new one conn = std::make_shared (*this); else // reuse failed @@ -1447,36 +1504,47 @@ namespace transport void NTCP2Server::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error) + if (!error && conn) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (conn) + if (!i2p::util::net::IsInReservedRange(ep.address ()) || + i2p::util::net::IsYggdrasilAddress (ep.address ())) { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) + { + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + conn = nullptr; + } + else + LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } + else + LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } else - { + { LogPrint (eLogError, "NTCP2: Accept ipv6 error ", error.message ()); if (error == boost::asio::error::no_descriptors) { i2p::context.SetErrorV6 (eRouterErrorNoDescriptors); - return; - } - } + return; + } + } if (error != boost::asio::error::operation_aborted) { - conn = std::make_shared (*this); + if (!conn) // connection is used, create new one + conn = std::make_shared (*this); + else // reuse failed + conn->Close (); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); } @@ -1507,34 +1575,34 @@ namespace transport // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { - if ((*it)->IsEstablished () || (*it)->IsTerminationTimeoutExpired (ts)) + if (it->second->IsEstablished () || it->second->IsTerminationTimeoutExpired (ts)) { - (*it)->Terminate (); + it->second->Terminate (); it = m_PendingIncomingSessions.erase (it); // established of expired } - else if ((*it)->IsTerminated ()) + else if (it->second->IsTerminated ()) it = m_PendingIncomingSessions.erase (it); // already terminated else it++; } ScheduleTermination (); - + // try to restart acceptors if no description - // we do it after timer to let timer take descriptor first + // we do it after timer to let timer take descriptor first if (i2p::context.GetError () == eRouterErrorNoDescriptors) { i2p::context.SetError (eRouterErrorNone); auto conn = std::make_shared (*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); - } + } if (i2p::context.GetErrorV6 () == eRouterErrorNoDescriptors) { i2p::context.SetErrorV6 (eRouterErrorNone); auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); - } + } } } @@ -1742,15 +1810,15 @@ namespace transport }); boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size - boost::asio::transfer_all(), - [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) + boost::asio::transfer_all(), + [timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred) { if (e) LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message()); else if (!(*readbuff)[1]) // succeeded { boost::system::error_code ec; - size_t moreBytes = conn->GetSocket ().available(ec); + size_t moreBytes = conn->GetSocket ().available(ec); if (moreBytes) // read remaining portion of reply if ipv6 received boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec); timer->cancel(); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 754f5a6d..ba1380c3 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -134,22 +134,22 @@ namespace transport ~NTCP2Session (); void Terminate (); void TerminateByTimeout (); - void Done (); - void Close () { m_Socket.close (); }; // for accept + void Done () override; + void Close (); // for accept void DeleteNextReceiveBuffer (uint64_t ts); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; - bool IsEstablished () const { return m_IsEstablished; }; + bool IsEstablished () const override { return m_IsEstablished; }; bool IsTerminated () const { return m_IsTerminated; }; void ClientLogin (); // Alice void ServerLogin (); // Bob - void SendLocalRouterInfo (bool update); // after handshake or by update - void SendI2NPMessages (const std::vector >& msgs); + void SendLocalRouterInfo (bool update) override; // after handshake or by update + void SendI2NPMessages (const std::vector >& msgs) override; private: @@ -277,7 +277,7 @@ namespace transport boost::asio::deadline_timer m_TerminationTimer; std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; std::map > m_NTCP2Sessions; - std::list > m_PendingIncomingSessions; + std::map > m_PendingIncomingSessions; ProxyType m_ProxyType; std::string m_ProxyAddress, m_ProxyAuthorization; diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index d588b809..11cf8066 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -85,8 +85,7 @@ namespace data if (m_IsRunning) { if (m_PersistProfiles) - for (auto& it: m_RouterInfos) - it.second->SaveProfile (); + SaveProfiles (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); m_Floodfills.clear (); @@ -153,13 +152,13 @@ namespace data if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastManageRequest >= 15) // manage requests every 15 seconds + if (ts - lastManageRequest >= 15 || ts + 15 < lastManageRequest) // manage requests every 15 seconds { m_Requests.ManageRequests (); lastManageRequest = ts; } - if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute + if (ts - lastSave >= 60 || ts + 60 < lastSave) // save routers, manage leasesets and validate subscriptions every minute { if (lastSave) { @@ -169,14 +168,17 @@ namespace data lastSave = ts; } - if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) + if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT || + ts + i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT < lastDestinationCleanup) { i2p::context.CleanupDestination (); lastDestinationCleanup = ts; } - if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)) + if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance) || + ts + i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT < lastProfilesCleanup) { + if (m_PersistProfiles) PersistProfiles (); DeleteObsoleteProfiles (); lastProfilesCleanup = ts; profilesCleanupVariance = (rand () % (2 * i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE) - i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE); @@ -192,7 +194,8 @@ namespace data if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; } else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) + ts - lastPublish >= NETDB_PUBLISH_INTERVAL || + ts + NETDB_PUBLISH_INTERVAL < lastPublish) { // new publish m_PublishExcluded.clear (); @@ -208,7 +211,7 @@ namespace data } } - if (ts - lastExploratory >= 30) // exploratory every 30 seconds + if (ts - lastExploratory >= 30 || ts + 30 < lastExploratory) // exploratory every 30 seconds { auto numRouters = m_RouterInfos.size (); if (!numRouters) @@ -273,7 +276,24 @@ namespace data bool wasFloodfill = r->IsFloodfill (); { std::unique_lock l(m_RouterInfosMutex); - r->Update (buf, len); + if (!r->Update (buf, len)) + { + updated = false; + m_Requests.RequestComplete (ident, r); + return r; + } + if (r->IsUnreachable ()) + { + // delete router as invalid after update + m_RouterInfos.erase (ident); + if (wasFloodfill) + { + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.remove (r); + } + m_Requests.RequestComplete (ident, nullptr); + return nullptr; + } } LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated @@ -295,7 +315,8 @@ namespace data else { r = std::make_shared (buf, len); - if (!r->IsUnreachable () && r->HasValidAddresses ()) + if (!r->IsUnreachable () && r->HasValidAddresses () && (!r->IsFloodfill () || !r->GetProfile ()->IsUnreachable ()) && + i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp ()) { bool inserted = false; { @@ -365,15 +386,16 @@ namespace data bool NetDb::AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType) { - std::unique_lock lock(m_LeaseSetsMutex); auto leaseSet = std::make_shared (storeType, buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find(ident); if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) { - if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) + if (leaseSet->IsPublic () && !leaseSet->IsExpired () && + i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ()) { // TODO: implement actual update LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); @@ -382,7 +404,7 @@ namespace data } else { - LogPrint (eLogWarning, "NetDb: Unpublished or expired LeaseSet2 received: ", ident.ToBase32()); + LogPrint (eLogWarning, "NetDb: Unpublished or expired or future LeaseSet2 received: ", ident.ToBase32()); m_LeaseSets.erase (ident); } } @@ -425,7 +447,15 @@ namespace data { auto it = m_RouterInfos.find (ident); if (it != m_RouterInfos.end ()) - return it->second->SetUnreachable (unreachable); + { + it->second->SetUnreachable (unreachable); + if (unreachable) + { + auto profile = it->second->GetProfile (); + if (profile) + profile->Unreachable (); + } + } } void NetDb::Reseed () @@ -494,7 +524,7 @@ namespace data { auto r = std::make_shared(path); if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses () && - ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) + ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) // too old { r->DeleteBuffer (); if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) @@ -617,27 +647,43 @@ namespace data std::string ident = it.second->GetIdentHashBase64(); if (it.second->IsUpdated ()) { - it.second->SaveToFile (m_Storage.Path(ident)); + if (it.second->GetBuffer ()) + { + // we have something to save + it.second->SaveToFile (m_Storage.Path(ident)); + it.second->SetUnreachable (false); + it.second->DeleteBuffer (); + } it.second->SetUpdated (false); - it.second->SetUnreachable (false); - it.second->DeleteBuffer (); updatedCount++; continue; } + if (it.second->GetProfile ()->IsUnreachable ()) + it.second->SetUnreachable (true); // make router reachable back if too few routers or floodfills if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) it.second->SetUnreachable (false); - // find & mark expired routers - if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & RouterInfo::eSSU2V4)) - // non-reachable router, but reachable by ipv4 SSU2 means introducers - { - if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) - // RouterInfo expires after 1 hour if uses introducer + if (!it.second->IsUnreachable ()) + { + // find & mark expired routers + if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & RouterInfo::eSSU2V4)) + // non-reachable router, but reachable by ipv4 SSU2 means introducers + { + if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) + // RouterInfo expires after 1 hour if uses introducer + it.second->SetUnreachable (true); + } + else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) it.second->SetUnreachable (true); - } - else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) + else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ()) + { + LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds"); it.second->SetUnreachable (true); + } + if (it.second->IsUnreachable () && i2p::transport::transports.IsConnected (it.second->GetIdentHash ())) + it.second->SetUnreachable (false); // don't expire connected router + } if (it.second->IsUnreachable ()) { @@ -651,8 +697,8 @@ namespace data m_RouterInfoBuffersPool.CleanUpMt (); m_RouterInfoAddressesPool.CleanUpMt (); - m_RouterInfoAddressVectorsPool.CleanUpMt (); - + m_RouterInfoAddressVectorsPool.CleanUpMt (); + if (updatedCount > 0) LogPrint (eLogInfo, "NetDb: Saved ", updatedCount, " new/updated routers"); if (deletedCount > 0) @@ -664,12 +710,12 @@ namespace data for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { if (it->second->IsUnreachable ()) - { - if (m_PersistProfiles) it->second->SaveProfile (); it = m_RouterInfos.erase (it); - continue; - } - ++it; + else + { + it->second->DropProfile (); + it++; + } } } // clean up expired floodfills or not floodfills anymore @@ -679,7 +725,7 @@ namespace data if ((*it)->IsUnreachable () || !(*it)->IsFloodfill ()) it = m_Floodfills.erase (it); else - ++it; + it++; } } } @@ -756,7 +802,7 @@ namespace data { LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped"); return; - } + } IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); if (ident.IsZero ()) { @@ -771,7 +817,7 @@ namespace data { LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped"); return; - } + } auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; @@ -800,6 +846,11 @@ namespace data uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET]; if (storeType) // LeaseSet or LeaseSet2 { + if (len > MAX_LS_BUFFER_SIZE + offset) + { + LogPrint (eLogError, "NetDb: Database store message is too long ", len); + return; + } if (!m->from) // unsolicited LS must be received directly { if (storeType == NETDB_STORE_TYPE_LEASESET) // 1 @@ -809,7 +860,7 @@ namespace data } else // all others are considered as LeaseSet2 { - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", storeType, " for ", ident.ToBase32()); + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32()); updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType); } } @@ -994,11 +1045,10 @@ namespace data lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) { auto router = FindRouter (ident); - if (router) + if (router && !router->IsUnreachable ()) { LogPrint (eLogDebug, "NetDb: Requested RouterInfo ", key, " found"); - PopulateRouterInfoBuffer (router); - if (router->GetBuffer ()) + if (PopulateRouterInfoBuffer (router)) replyMsg = CreateDatabaseStoreMsg (router); } } @@ -1310,19 +1360,16 @@ namespace data } std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, - const std::set& excluded, bool closeThanUsOnly) const + const std::set& excluded) const { std::shared_ptr r; XORMetric minMetric; IdentHash destKey = CreateRoutingKey (destination); - if (closeThanUsOnly) - minMetric = destKey ^ i2p::context.GetIdentHash (); - else - minMetric.SetMax (); + minMetric.SetMax (); std::unique_lock l(m_FloodfillsMutex); for (const auto& it: m_Floodfills) { - if (!it->IsUnreachable ()) + if (!it->IsUnreachable () && !it->GetProfile ()->IsUnreachable ()) { XORMetric m = destKey ^ it->GetIdentHash (); if (m < minMetric && !excluded.count (it->GetIdentHash ())) @@ -1353,7 +1400,7 @@ namespace data std::unique_lock l(m_FloodfillsMutex); for (const auto& it: m_Floodfills) { - if (!it->IsUnreachable ()) + if (!it->IsUnreachable () && !it->GetProfile ()->IsUnreachable ()) { XORMetric m = destKey ^ it->GetIdentHash (); if (closeThanUsOnly && ourMetric < m) continue; @@ -1435,10 +1482,11 @@ namespace data m_LeasesPool.CleanUpMt (); } - void NetDb::PopulateRouterInfoBuffer (std::shared_ptr r) + bool NetDb::PopulateRouterInfoBuffer (std::shared_ptr r) { - if (!r || r->GetBuffer ()) return; - r->LoadBuffer (m_Storage.Path (r->GetIdentHashBase64 ())); + if (!r) return false; + if (r->GetBuffer ()) return true; + return r->LoadBuffer (m_Storage.Path (r->GetIdentHashBase64 ())); } } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 95f6c692..192d2644 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -44,11 +44,12 @@ namespace data const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours const int NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT = 180; // in days + const int NETDB_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes const int NETDB_PUBLISH_INTERVAL = 60 * 40; const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; - const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 - const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 /** function for visiting a leaseset stored in a floodfill */ @@ -92,7 +93,7 @@ namespace data std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const; std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::set& excluded) const; - std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; + std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly = false) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; @@ -123,13 +124,13 @@ namespace data void ClearRouterInfos () { m_RouterInfos.clear (); }; std::shared_ptr NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); }; - void PopulateRouterInfoBuffer (std::shared_ptr r); + bool PopulateRouterInfoBuffer (std::shared_ptr r); std::shared_ptr NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); }; - boost::shared_ptr NewRouterInfoAddresses () - { - return boost::shared_ptr(m_RouterInfoAddressVectorsPool.AcquireMt (), + boost::shared_ptr NewRouterInfoAddresses () + { + return boost::shared_ptr(m_RouterInfoAddressVectorsPool.AcquireMt (), std::bind ::*)(RouterInfo::Addresses *)> - (&i2p::util::MemoryPoolMt::ReleaseMt, + (&i2p::util::MemoryPoolMt::ReleaseMt, &m_RouterInfoAddressVectorsPool, std::placeholders::_1)); }; std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 55b95831..311d1c86 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,34 +7,42 @@ */ #include +#include +#include +#include #include #include #include "Base.h" #include "FS.h" #include "Log.h" +#include "Timestamp.h" #include "Profiling.h" namespace i2p { namespace data { - i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + static std::unordered_map > g_Profiles; + static std::mutex g_ProfilesMutex; + static boost::posix_time::ptime GetTime () + { + return boost::posix_time::second_clock::local_time(); + } + RouterProfile::RouterProfile (): - m_LastUpdateTime (boost::posix_time::second_clock::local_time()), + m_LastUpdateTime (GetTime ()), m_IsUpdated (false), + m_LastDeclineTime (0), m_LastUnreachableTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), m_NumTimesTaken (0), m_NumTimesRejected (0) { } - boost::posix_time::ptime RouterProfile::GetTime () const - { - return boost::posix_time::second_clock::local_time(); - } - void RouterProfile::UpdateTime () { m_LastUpdateTime = GetTime (); + m_IsUpdated = true; } void RouterProfile::Save (const IdentHash& identHash) @@ -50,12 +58,14 @@ namespace data // fill property tree boost::property_tree::ptree pt; pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); + if (m_LastUnreachableTime) + pt.put (PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); // save to file std::string ident = identHash.ToBase64 (); - std::string path = m_ProfilesStorage.Path(ident); + std::string path = g_ProfilesStorage.Path(ident); try { boost::property_tree::write_ini (path, pt); @@ -68,7 +78,7 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { std::string ident = identHash.ToBase64 (); - std::string path = m_ProfilesStorage.Path(ident); + std::string path = g_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; if (!i2p::fs::Exists(path)) @@ -94,6 +104,7 @@ namespace data m_LastUpdateTime = boost::posix_time::time_from_string (t); if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) { + m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0); try { // read participations @@ -131,15 +142,29 @@ namespace data { UpdateTime (); if (ret > 0) + { m_NumTunnelsDeclined++; + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } else + { m_NumTunnelsAgreed++; + m_LastDeclineTime = 0; + } } void RouterProfile::TunnelNonReplied () { m_NumTunnelsNonReplied++; UpdateTime (); + if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3) + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } + + void RouterProfile::Unreachable () + { + m_LastUnreachableTime = i2p::util::GetSecondsSinceEpoch (); + UpdateTime (); } bool RouterProfile::IsLowPartcipationRate () const @@ -153,8 +178,19 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } + bool RouterProfile::IsDeclinedRecently () + { + if (!m_LastDeclineTime) return false; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL || + ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime) + m_LastDeclineTime = 0; + return (bool)m_LastDeclineTime; + } + bool RouterProfile::IsBad () { + if (IsDeclinedRecently () || IsUnreachable ()) return true; auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { @@ -168,32 +204,98 @@ namespace data return isBad; } + bool RouterProfile::IsUnreachable () + { + if (!m_LastUnreachableTime) return false; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > m_LastUnreachableTime + PEER_PROFILE_UNREACHABLE_INTERVAL || + ts + PEER_PROFILE_UNREACHABLE_INTERVAL < m_LastUnreachableTime) + m_LastUnreachableTime = 0; + return (bool)m_LastUnreachableTime; + } + std::shared_ptr GetRouterProfile (const IdentHash& identHash) { + { + std::unique_lock l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second; + } auto profile = std::make_shared (); profile->Load (identHash); // if possible + std::unique_lock l(g_ProfilesMutex); + g_Profiles.emplace (identHash, profile); return profile; } void InitProfilesStorage () { - m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); - m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); + g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); + g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); } + void PersistProfiles () + { + auto ts = GetTime (); + std::list > > tmp; + { + std::unique_lock l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if ((ts - it->second->GetLastUpdateTime ()).total_seconds () > PEER_PROFILE_PERSIST_INTERVAL) + { + if (it->second->IsUpdated ()) + tmp.push_back (std::make_pair (it->first, it->second)); + it = g_Profiles.erase (it); + } + else + it++; + } + } + for (auto& it: tmp) + if (it.second) it.second->Save (it.first); + } + + void SaveProfiles () + { + std::unordered_map > tmp; + { + std::unique_lock l(g_ProfilesMutex); + tmp = g_Profiles; + g_Profiles.clear (); + } + auto ts = GetTime (); + for (auto& it: tmp) + if (it.second->IsUpdated () && (ts - it.second->GetLastUpdateTime ()).total_seconds () < PEER_PROFILE_EXPIRATION_TIMEOUT*3600) + it.second->Save (it.first); + } + void DeleteObsoleteProfiles () { + { + auto ts = GetTime (); + std::unique_lock l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if ((ts - it->second->GetLastUpdateTime ()).total_seconds () >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) + it = g_Profiles.erase (it); + else + it++; + } + } + struct stat st; std::time_t now = std::time(nullptr); std::vector files; - m_ProfilesStorage.Traverse(files); + g_ProfilesStorage.Traverse(files); for (const auto& path: files) { if (stat(path.c_str(), &st) != 0) { LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); continue; } - if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) { + if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) { LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); i2p::fs::Remove(path); } diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 49e362ca..752d6190 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -22,15 +22,19 @@ namespace data const char PEER_PROFILE_SECTION_USAGE[] = "usage"; // params const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; + const char PEER_PROFILE_LAST_UNREACHABLE_TIME[] = "lastunreachabletime"; const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; const char PEER_PROFILE_USAGE_TAKEN[] = "taken"; const char PEER_PROFILE_USAGE_REJECTED[] = "rejected"; - const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days) - const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 24 * 3600; // in seconds (1 day) - const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3 * 3600; // in seconds (3 hours) + const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36; // in hours (1.5 days) + const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 6 * 3600; // in seconds (6 hours) + const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3600; // in seconds (1 hour) + const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes) + const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes) + const int PEER_PROFILE_UNREACHABLE_INTERVAL = 2*3600; // on seconds (2 hours) class RouterProfile { @@ -43,22 +47,30 @@ namespace data void Load (const IdentHash& identHash); bool IsBad (); + bool IsUnreachable (); void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); + void Unreachable (); + + boost::posix_time::ptime GetLastUpdateTime () const { return m_LastUpdateTime; }; + bool IsUpdated () const { return m_IsUpdated; }; + private: - boost::posix_time::ptime GetTime () const; void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; bool IsLowPartcipationRate () const; bool IsLowReplyRate () const; + bool IsDeclinedRecently (); private: - boost::posix_time::ptime m_LastUpdateTime; + boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono + bool m_IsUpdated; + uint64_t m_LastDeclineTime, m_LastUnreachableTime; // in seconds // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -71,6 +83,8 @@ namespace data std::shared_ptr GetRouterProfile (const IdentHash& identHash); void InitProfilesStorage (); void DeleteObsoleteProfiles (); + void SaveProfiles (); + void PersistProfiles (); } } diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 8928fc7f..72b723ea 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -85,86 +85,103 @@ namespace i2p bool ssu2Published = false; if (ssu2) i2p::config::GetOption("ssu2.published", ssu2Published); - uint8_t caps = 0, addressCaps = 0; + uint8_t caps = 0; if (ipv4) { - std::string host = "127.0.0.1"; - if (!i2p::config::IsDefault("host")) - i2p::config::GetOption("host", host); - else if (!nat) - { + std::string host; + if (!nat) // we have no NAT so set external address from local address - std::string address4; i2p::config::GetOption("address4", address4); - if (!address4.empty ()) host = address4; - } + i2p::config::GetOption("address4", host); + if (host.empty ()) i2p::config::GetOption("host", host); if (ntcp2) { - if (ntcp2Published) - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v4::from_string (host), port); - else // add non-published NTCP2 address + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + if (ntcp2Published && ntcp2Port) { - addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v4()) + addr = boost::asio::ip::address_v4 (); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + } + else + { + // add non-published NTCP2 address + uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, addressCaps); } } if (ssu2) { - if (ssu2Published) + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + if (ssu2Published && ssu2Port) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v4::from_string (host), ssu2Port); + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v4()) + addr = boost::asio::ip::address_v4 (); + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); } else { - addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro); + uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, addressCaps); } } } if (ipv6) { - std::string host = "::1"; - if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only - i2p::config::GetOption("host", host); - else - { - std::string address6; i2p::config::GetOption("address6", address6); - if (!address6.empty ()) host = address6; - } + std::string host; i2p::config::GetOption("address6", host); + if (host.empty () && !ipv4) i2p::config::GetOption("host", host); // use host for ipv6 only if ipv4 is not presented if (ntcp2) { - if (ntcp2Published) + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + if (ntcp2Published && ntcp2Port) { std::string ntcp2Host; if (!i2p::config::IsDefault ("ntcp2.addressv6")) i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); else ntcp2Host = host; - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port); + boost::asio::ip::address addr; + if (!ntcp2Host.empty ()) + addr = boost::asio::ip::address::from_string (ntcp2Host); + if (!addr.is_v6()) + addr = boost::asio::ip::address_v6 (); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); } else { if (!ipv4) // no other ntcp2 addresses yet - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); - addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::AddressCaps::eV6); } } if (ssu2) { - if (ssu2Published) + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + if (ssu2Published && ssu2Port) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), ssu2Port); + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v6()) + addr = boost::asio::ip::address_v6 (); + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); } else { if (!ipv4) // no other ssu2 addresses yet - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro); - addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::AddressCaps::eV6); } } } @@ -175,8 +192,6 @@ namespace i2p routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); } - if (addressCaps) - routerInfo.SetUnreachableAddressesTransportCaps (addressCaps); routerInfo.UpdateCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); @@ -187,8 +202,13 @@ namespace i2p uint16_t RouterContext::SelectRandomPort () const { - uint16_t port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser + uint16_t port; + do + { + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + } + while(i2p::util::net::IsPortInReservedRange(port)); + return port; } @@ -230,7 +250,6 @@ namespace i2p if (status != m_Status) { m_Status = status; - m_Error = eRouterErrorNone; switch (m_Status) { case eRouterStatusOK: @@ -239,18 +258,20 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; + case eRouterStatusTesting: + m_Error = eRouterErrorNone; + break; default: ; } } } - + void RouterContext::SetStatusV6 (RouterStatus status) { if (status != m_StatusV6) { m_StatusV6 = status; - m_ErrorV6 = eRouterErrorNone; switch (m_StatusV6) { case eRouterStatusOK: @@ -259,12 +280,15 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (false, true); // ipv6 break; + case eRouterStatusTesting: + m_ErrorV6 = eRouterErrorNone; + break; default: ; } } } - + void RouterContext::UpdatePort (int port) { auto addresses = m_RouterInfo.GetAddresses (); @@ -272,7 +296,7 @@ namespace i2p bool updated = false; for (auto& address : *addresses) { - if (address->port != port && address->transportStyle == i2p::data::RouterInfo::eTransportSSU2) + if (address && address->port != port) { address->port = port; updated = true; @@ -282,68 +306,67 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::PublishNTCP2Address (std::shared_ptr address, + int port, bool publish) const + { + if (!address) return; + if (!port && !address->port) port = SelectRandomPort (); + if (port) address->port = port; + address->published = publish; + memcpy (address->i, m_NTCP2Keys->iv, 16); + } + void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg) { if (!m_NTCP2Keys) return; auto addresses = m_RouterInfo.GetAddresses (); if (!addresses) return; bool updated = false; - for (auto& address : *addresses) + if (v4) { - if (address->IsNTCP2 () && (address->port != port || address->published != publish)) + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; + if (addr && (addr->port != port || addr->published != publish)) { - bool isAddr = v4 && address->IsV4 (); - if (!isAddr && (v6 || ygg)) - { - if (i2p::util::net::IsYggdrasilAddress (address->host)) - isAddr = ygg; - else - isAddr = v6 && address->IsV6 (); - } - if (isAddr) - { - if (!port && !address->port) port = SelectRandomPort (); - if (port) address->port = port; - address->published = publish; - memcpy (address->i, m_NTCP2Keys->iv, 16); - updated = true; - } + PublishNTCP2Address (addr, port, publish); + updated = true; } } + if (v6) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && (addr->port != port || addr->published != publish)) + { + PublishNTCP2Address (addr, port, publish); + updated = true; + } + } + if (ygg) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; + if (addr && (addr->port != port || addr->published != publish)) + { + PublishNTCP2Address (addr, port, publish); + updated = true; + } + } + if (updated) UpdateRouterInfo (); } - void RouterContext::UpdateNTCP2Address (bool enable) + void RouterContext::UpdateNTCP2Keys () { + if (!m_NTCP2Keys) return; auto addresses = m_RouterInfo.GetAddresses (); if (!addresses) return; - bool found = false, updated = false; - for (auto it = addresses->begin (); it != addresses->end ();) + for (auto& it: *addresses) { - if ((*it)->IsNTCP2 ()) + if (it && it->IsNTCP2 ()) { - found = true; - if (enable) - { - (*it)->s = m_NTCP2Keys->staticPublicKey; - memcpy ((*it)->i, m_NTCP2Keys->iv, 16); - it++; - } - else - it = addresses->erase (it); - updated = true; + it->s = m_NTCP2Keys->staticPublicKey; + memcpy (it->i, m_NTCP2Keys->iv, 16); } - else - it++; } - if (enable && !found) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); - updated = true; - } - if (updated) - UpdateRouterInfo (); } void RouterContext::PublishSSU2Address (int port, bool publish, bool v4, bool v6) @@ -355,7 +378,7 @@ namespace i2p if (!port) { for (const auto& address : *addresses) - if (address->port) + if (address && address->port) { newPort = address->port; break; @@ -365,7 +388,7 @@ namespace i2p bool updated = false; for (auto& address : *addresses) { - if (address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && + if (address && address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) { if (port) address->port = port; @@ -382,50 +405,19 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::UpdateSSU2Address (bool enable) + void RouterContext::UpdateSSU2Keys () { + if (!m_SSU2Keys) return; auto addresses = m_RouterInfo.GetAddresses (); if (!addresses) return; - bool found = false, updated = false; - for (auto it = addresses->begin (); it != addresses->end ();) + for (auto& it: *addresses) { - if ((*it)->IsSSU2 ()) + if (it && it->IsSSU2 ()) { - found = true; - if (enable) - { - (*it)->s = m_SSU2Keys->staticPublicKey; - (*it)->i = m_SSU2Keys->intro; - it++; - } - else - it = addresses->erase (it); - updated = true; + it->s = m_SSU2Keys->staticPublicKey; + it->i = m_SSU2Keys->intro; } - else - it++; } - if (enable && !found) - { - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - if (ipv4) m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::AddressCaps::eV4); - if (ipv6) m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::AddressCaps::eV6); - } - else - { - uint8_t addressCaps = 0; - if (ipv4) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; - if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addressCaps); - } - updated = true; - } - if (updated) - UpdateRouterInfo (); } void RouterContext::UpdateAddress (const boost::asio::ip::address& host) @@ -433,34 +425,53 @@ namespace i2p auto addresses = m_RouterInfo.GetAddresses (); if (!addresses) return; bool updated = false; - for (auto& address : *addresses) + if (host.is_v4 ()) { - if (address->host != host && address->IsCompatible (host) && - !i2p::util::net::IsYggdrasilAddress (address->host)) + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; + if (addr && addr->host != host) { - // update host - address->host = host; + addr->host = host; updated = true; } - if (host.is_v6 () && address->IsV6 () && address->ssu && - (!address->ssu->mtu || updated) && m_StatusV6 != eRouterStatusProxy) + addr = (*addresses)[i2p::data::RouterInfo::eSSU2V4Idx]; + if (addr && addr->host != host) { - // update MTU - auto mtu = i2p::util::net::GetMTU (host); - if (mtu) - { - LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); - if (mtu > maxMTU) - { - mtu = maxMTU; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); - } - address->ssu->mtu = mtu; - updated = true; - } + addr->host = host; + updated = true; } } + else if (host.is_v6 ()) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && addr->host != host) + { + addr->host = host; + updated = true; + } + addr = (*addresses)[i2p::data::RouterInfo::eSSU2V6Idx]; + if (addr && (addr->host != host || !addr->ssu->mtu)) + { + addr->host = host; + if (m_StatusV6 != eRouterStatusProxy) + { + // update MTU + auto mtu = i2p::util::net::GetMTU (host); + if (mtu) + { + LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); + int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); + if (mtu > maxMTU) + { + mtu = maxMTU; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); + } + addr->ssu->mtu = mtu; + } + } + updated = true; + } + } + auto ts = i2p::util::GetSecondsSinceEpoch (); if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) UpdateRouterInfo (); @@ -482,18 +493,12 @@ namespace i2p void RouterContext::ClearSSU2Introducers (bool v4) { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - bool updated = false; - for (auto& addr : *addresses) - if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())) && - addr->ssu && !addr->ssu->introducers.empty ()) - { - addr->ssu->introducers.clear (); - updated = true; - } - if (updated) + auto addr = m_RouterInfo.GetSSU2Address (v4); + if (addr && !addr->ssu->introducers.empty ()) + { + addr->ssu->introducers.clear (); UpdateRouterInfo (); + } } void RouterContext::SetFloodfill (bool floodfill) @@ -609,17 +614,17 @@ namespace i2p uint16_t port = 0; // delete previous introducers auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { + if (addresses) + { for (auto& addr : *addresses) - if (addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) { addr->published = false; addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer addr->ssu->introducers.clear (); port = addr->port; } - } + } // unpublish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) @@ -645,17 +650,17 @@ namespace i2p // delete previous introducers bool isSSU2Published; i2p::config::GetOption ("ssu2.published", isSSU2Published); auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { + if (addresses) + { for (auto& addr : *addresses) - if (addr->ssu && isSSU2Published && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + if (addr && addr->ssu && isSSU2Published && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) { addr->published = true; addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; addr->ssu->introducers.clear (); if (addr->port) port = addr->port; } - } + } // publish NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) @@ -681,11 +686,11 @@ namespace i2p bool foundNTCP2 = false, foundSSU2 = false; uint16_t port = 0; auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) + if (addresses) { for (auto& addr: *addresses) { - if (addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) + if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) { switch (addr->transportStyle) { @@ -698,7 +703,7 @@ namespace i2p default: ; } } - port = addr->port; + if (addr) port = addr->port; } } if (!port) @@ -707,45 +712,69 @@ namespace i2p if (!port) port = SelectRandomPort (); } // NTCP2 - if (!foundNTCP2) + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2) + if (!foundNTCP2) { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + bool added = false; + bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); if (ntcp2Published) { std::string ntcp2Host; if (!i2p::config::IsDefault ("ntcp2.addressv6")) i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); else - ntcp2Host = "::1"; - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + i2p::config::GetOption("host", ntcp2Host); + if (!ntcp2Host.empty () && ntcp2Port) + { + auto addr = boost::asio::ip::address::from_string (ntcp2Host); + if (addr.is_v6 ()) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + added = true; + } + } } - else - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV6); + if (!added) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV6); } } + else + m_RouterInfo.RemoveNTCP2Address (false); // SSU2 - if (!foundSSU2) + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) { - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) + if (!foundSSU2) { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + bool added = false; bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - if (ssu2Published) + if (ssu2Published && ssu2Port) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("::1"), ssu2Port); + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v6 ()) + { + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + added = true; + } + } } - else - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV6); + if (!added) + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV6); } } - m_RouterInfo.EnableV6 (); + else + m_RouterInfo.RemoveSSU2Address (false); + if (ntcp2 || ssu2) + m_RouterInfo.EnableV6 (); } else m_RouterInfo.DisableV6 (); @@ -754,21 +783,16 @@ namespace i2p void RouterContext::SetSupportsV4 (bool supportsV4) { - // check if updates - if (supportsV4 && SupportsV4 ()) return; - if (!supportsV4 && !SupportsV4 ()) return; - // update if (supportsV4) { bool foundNTCP2 = false, foundSSU2 = false; - std::string host = "127.0.0.1"; uint16_t port = 0; auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) + if (addresses) { for (auto& addr: *addresses) { - if (addr->IsV4 ()) + if (addr && addr->IsV4 ()) { switch (addr->transportStyle) { @@ -781,49 +805,75 @@ namespace i2p default: ; } } - if (addr->port) port = addr->port; + if (addr && addr->port) port = addr->port; } - } + } if (!port) { i2p::config::GetOption("port", port); if (!port) port = SelectRandomPort (); } // NTCP2 - if (!foundNTCP2) + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + if (!foundNTCP2) { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + bool added = false; bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published) + if (ntcp2Published && ntcp2Port) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v4 ()) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + added = true; + } + } } - else - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV4); + if (!added) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV4); } } + else + m_RouterInfo.RemoveNTCP2Address (true); // SSU2 - if (!foundSSU2) + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) { - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) + if (!foundSSU2) { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + bool added = false; bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - if (ssu2Published) + std::string host; i2p::config::GetOption("host", host); + if (ssu2Published && ssu2Port) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("127.0.0.1"), ssu2Port); + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v4 ()) + { + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + added = true; + } + } } - else - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV4); + if (!added) + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV4); } } - m_RouterInfo.EnableV4 (); + else + m_RouterInfo.RemoveSSU2Address (true); + if (ntcp2 || ssu2) + m_RouterInfo.EnableV4 (); } else m_RouterInfo.DisableV4 (); @@ -834,26 +884,26 @@ namespace i2p { if (supportsmesh) { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; m_RouterInfo.EnableMesh (); + if ((*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]) return; // we have mesh address already uint16_t port = 0; i2p::config::GetOption ("ntcp2.port", port); if (!port) i2p::config::GetOption("port", port); - bool foundMesh = false; - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) + if (!port) { for (auto& addr: *addresses) { - if (!port) port = addr->port; - if (i2p::util::net::IsYggdrasilAddress (addr->host)) + if (addr && addr->port) { - foundMesh = true; + port = addr->port; break; } } - } - if (!foundMesh) - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); + } + if (!port) port = SelectRandomPort (); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); } else m_RouterInfo.DisableMesh (); @@ -867,8 +917,8 @@ namespace i2p if (!addresses) return; for (auto& addr: *addresses) { - if (addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) - { + if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) + { addr->ssu->mtu = mtu; LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); } @@ -877,29 +927,18 @@ namespace i2p void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { - bool isYgg = i2p::util::net::IsYggdrasilAddress (host); - bool updated = false; auto addresses = m_RouterInfo.GetAddresses (); if (!addresses) return; - for (auto& addr: *addresses) + std::shared_ptr addr; + if (i2p::util::net::IsYggdrasilAddress (host)) // yggdrasil + addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; + else if (host.is_v6 ()) + addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && addr->IsPublishedNTCP2 () && addr->host != host) { - if (addr->IsPublishedNTCP2 ()) - { - bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); - if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) - { - if (addr->host != host) - { - addr->host = host; - updated = true; - } - break; - } - } - } - - if (updated) + addr->host = host; UpdateRouterInfo (); + } } void RouterContext::UpdateStats () @@ -967,6 +1006,20 @@ namespace i2p } n2k.close (); } + // read SSU2 keys if available + std::ifstream s2k (i2p::fs::DataDirPath (SSU2_KEYS), std::ifstream::in | std::ifstream::binary); + if (s2k) + { + s2k.seekg (0, std::ios::end); + size_t len = s2k.tellg(); + s2k.seekg (0, std::ios::beg); + if (len == sizeof (SSU2PrivateKeys)) + { + m_SSU2Keys.reset (new SSU2PrivateKeys ()); + s2k.read ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); + } + s2k.close (); + } // read RouterInfo m_RouterInfo.SetRouterIdentity (oldIdentity ? oldIdentity : GetIdentity ()); i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); @@ -987,40 +1040,26 @@ namespace i2p if (IsUnreachable ()) SetReachable (true, true); // we assume reachable until we discover firewall through peer tests - // read NTCP2 + bool updated = false; + // create new NTCP2 keys if required bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - if (ntcp2 || ygg) + if ((ntcp2 || ygg) && !m_NTCP2Keys) { - if (!m_NTCP2Keys) NewNTCP2Keys (); - UpdateNTCP2Address (true); // enable NTCP2 + NewNTCP2Keys (); + UpdateNTCP2Keys (); + updated = true; } - else - UpdateNTCP2Address (false); // disable NTCP2 - - // read SSU2 + // create new SSU2 keys if required bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) + if (ssu2 && !m_SSU2Keys) { - // read SSU2 keys if available - std::ifstream s2k (i2p::fs::DataDirPath (SSU2_KEYS), std::ifstream::in | std::ifstream::binary); - if (s2k) - { - s2k.seekg (0, std::ios::end); - size_t len = s2k.tellg(); - s2k.seekg (0, std::ios::beg); - if (len == sizeof (SSU2PrivateKeys)) - { - m_SSU2Keys.reset (new SSU2PrivateKeys ()); - s2k.read ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); - } - s2k.close (); - } - if (!m_SSU2Keys) NewSSU2Keys (); - UpdateSSU2Address (true); // enable SSU2 + NewSSU2Keys (); + UpdateSSU2Keys (); + updated = true; } - else - UpdateSSU2Address (false); // disable SSU2 + if (updated) + UpdateRouterInfo (); return true; } @@ -1048,13 +1087,18 @@ namespace i2p bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { + if (typeID == eI2NPGarlic) + { + // TODO: implement + LogPrint (eLogWarning, "Router: garlic message in garlic clove. Dropped"); + return false; + } auto msg = CreateI2NPMessage (typeID, payload, len, msgID); if (!msg) return false; i2p::HandleI2NPMessage (msg); return true; } - void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index dd7f0272..9fbbb178 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -48,7 +48,8 @@ namespace garlic eRouterErrorClockSkew = 1, eRouterErrorOffline = 2, eRouterErrorSymmetricNAT = 3, - eRouterErrorNoDescriptors = 4 + eRouterErrorFullConeNAT = 4, + eRouterErrorNoDescriptors = 5 }; class RouterContext: public i2p::garlic::GarlicDestination @@ -115,11 +116,9 @@ namespace garlic bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data); void UpdatePort (int port); // called from Daemon - void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon + void UpdateAddress (const boost::asio::ip::address& host); // called from SSU2 or Daemon void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); - void UpdateNTCP2Address (bool enable); void PublishSSU2Address (int port, bool publish, bool v4, bool v6); - void UpdateSSU2Address (bool enable); bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4); void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4); void ClearSSU2Introducers (bool v4); @@ -176,10 +175,12 @@ namespace garlic void UpdateRouterInfo (); void NewNTCP2Keys (); void NewSSU2Keys (); - bool IsSSU2Only () const; // SSU2 and no SSU + void UpdateNTCP2Keys (); + void UpdateSSU2Keys (); bool Load (); void SaveKeys (); uint16_t SelectRandomPort () const; + void PublishNTCP2Address (std::shared_ptr address, int port, bool publish) const; bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 13a0731e..3fb95814 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -79,13 +79,12 @@ namespace data { } - void RouterInfo::Update (const uint8_t * buf, size_t len) + bool RouterInfo::Update (const uint8_t * buf, size_t len) { if (len > MAX_RI_BUFFER_SIZE) { - LogPrint (eLogError, "RouterInfo: Buffer is too long ", len); - m_IsUnreachable = true; - return; + LogPrint (eLogWarning, "RouterInfo: Updated buffer is too long ", len, ". Not changed"); + return false; } // verify signature since we have identity already int l = len - m_RouterIdentity->GetSignatureLen (); @@ -99,20 +98,21 @@ namespace data m_Caps = 0; // don't clean up m_Addresses, it will be replaced in ReadFromStream ClearProperties (); - // copy buffer - UpdateBuffer (buf, len); // skip identity size_t identityLen = m_RouterIdentity->GetFullLen (); // read new RI - std::stringstream str (std::string ((char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen)); + std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen)); ReadFromStream (str); + if (!m_IsUnreachable) + UpdateBuffer (buf, len); // save buffer // don't delete buffer until saved to the file } else - { - LogPrint (eLogError, "RouterInfo: Signature verification failed"); - m_IsUnreachable = true; - } + { + LogPrint (eLogWarning, "RouterInfo: Updated signature verification failed. Not changed"); + return false; + } + return true; } void RouterInfo::SetRouterIdentity (std::shared_ptr identity) @@ -206,14 +206,13 @@ namespace data s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses - auto addresses = netdb.NewRouterInfoAddresses (); + auto addresses = NewAddresses (); uint8_t numAddresses; s.read ((char *)&numAddresses, sizeof (numAddresses)); - addresses->reserve (numAddresses); for (int i = 0; i < numAddresses; i++) { uint8_t supportedTransports = 0; - auto address = netdb.NewRouterInfoAddress (); + auto address = NewAddress (); uint8_t cost; // ignore s.read ((char *)&cost, sizeof (cost)); s.read ((char *)&address->date, sizeof (address->date)); @@ -253,7 +252,15 @@ namespace data { boost::system::error_code ecode; address->host = boost::asio::ip::address::from_string (value, ecode); - if (!ecode && !address->host.is_unspecified ()) isHost = true; + if (!ecode && !address->host.is_unspecified ()) + { + if (!i2p::util::net::IsInReservedRange (address->host) || + i2p::util::net::IsYggdrasilAddress (address->host)) + isHost = true; + else + // we consider such address as invalid + address->transportStyle = eTransportUnknown; + } } else if (!strcmp (key, "port")) { @@ -330,9 +337,9 @@ namespace data } Introducer& introducer = address->ssu->introducers.at (index); if (!strcmp (key, "ihost")) - introducer.isH = false; // SSU1 + introducer.isH = false; // SSU1 else if (!strcmp (key, "iport")) - introducer.isH = false; // SSU1 + introducer.isH = false; // SSU1 else if (!strcmp (key, "itag")) { try @@ -345,10 +352,10 @@ namespace data } } else if (!strcmp (key, "ih")) - { + { Base64ToByteStream (value, strlen (value), introducer.iH, 32); - introducer.isH = true; - } + introducer.isH = true; + } else if (!strcmp (key, "iexp")) { try @@ -391,7 +398,7 @@ namespace data { if (address->IsV4 ()) supportedTransports |= eSSU2V4; if (address->IsV6 ()) supportedTransports |= eSSU2V6; - if (address->port) + if (isHost && address->port) { if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4; if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6; @@ -418,7 +425,11 @@ namespace data if (supportedTransports) { if (!(m_SupportedTransports & supportedTransports)) // avoid duplicates - addresses->push_back(address); + { + for (uint8_t i = 0; i < eNumTransports; i++) + if ((1 << i) & supportedTransports) + (*addresses)[i] = address; + } m_SupportedTransports |= supportedTransports; } } @@ -578,20 +589,24 @@ namespace data { if (LoadFile (fullPath)) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); + else + return nullptr; } return m_Buffer->data (); } bool RouterInfo::SaveToFile (const std::string& fullPath) { + if (m_IsUnreachable) return false; // don't save bad router if (!m_Buffer) { - LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); + LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) { - LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); + if (!f.is_open ()) + { + LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath); return false; } f.write ((char *)m_Buffer->data (), m_BufferLen); @@ -617,45 +632,106 @@ namespace data return l+1; } - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port, uint8_t caps) + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) { auto addr = std::make_shared

(); - addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP2; addr->caps = caps; addr->date = 0; - if (port) addr->published = true; + addr->published = false; memcpy (addr->s, staticKey, 32); memcpy (addr->i, iv, 16); if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; - if (addr->published) m_ReachableTransports |= eNTCP2V4; + (*m_Addresses)[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eNTCP2V6; - if (addr->published) m_ReachableTransports |= eNTCP2V6; + (*m_Addresses)[eNTCP2V6Idx] = addr; } - m_Addresses->push_back(std::move(addr)); } - void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps) + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, + const boost::asio::ip::address& host, int port) + { + auto addr = std::make_shared
(); + addr->host = host; + addr->port = port; + addr->transportStyle = eTransportNTCP2; + addr->date = 0; + addr->published = true; + memcpy (addr->s, staticKey, 32); + memcpy (addr->i, iv, 16); + addr->caps = 0; + if (host.is_unspecified ()) + { + if (host.is_v4 ()) addr->caps |= eV4; + if (host.is_v6 ()) addr->caps |= eV6; + } + if (addr->IsV4 ()) + { + m_SupportedTransports |= eNTCP2V4; + m_ReachableTransports |= eNTCP2V4; + (*m_Addresses)[eNTCP2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (addr->host)) + { + m_SupportedTransports |= eNTCP2V6Mesh; + m_ReachableTransports |= eNTCP2V6Mesh; + (*m_Addresses)[eNTCP2V6MeshIdx] = addr; + } + else + { + m_SupportedTransports |= eNTCP2V6; + m_ReachableTransports |= eNTCP2V6; + (*m_Addresses)[eNTCP2V6Idx] = addr; + } + } + } + + void RouterInfo::RemoveNTCP2Address (bool v4) + { + if (v4) + { + if ((*m_Addresses)[eNTCP2V6Idx]) + (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eNTCP2V4Idx].reset (); + } + else + { + if ((*m_Addresses)[eNTCP2V4Idx]) + (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eNTCP2V6Idx].reset (); + } + UpdateSupportedTransports (); + } + + void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps) { auto addr = std::make_shared
(); addr->transportStyle = eTransportSSU2; - addr->port = 0; + addr->port = port; addr->caps = caps; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); - if (addr->IsV4 ()) m_SupportedTransports |= eSSU2V4; - if (addr->IsV6 ()) m_SupportedTransports |= eSSU2V6; - m_Addresses->push_back(std::move(addr)); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eSSU2V4; + (*m_Addresses)[eSSU2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eSSU2V6; + (*m_Addresses)[eSSU2V6Idx] = addr; + } } void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, @@ -666,23 +742,48 @@ namespace data addr->host = host; addr->port = port; addr->published = true; - addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); + if (!host.is_unspecified ()) + addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; + else + { + addr->caps = 0; + if (host.is_v4 ()) addr->caps |= eV4; + if (host.is_v6 ()) addr->caps |= eV6; + } if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; m_ReachableTransports |= eSSU2V4; + (*m_Addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; m_ReachableTransports |= eSSU2V6; + (*m_Addresses)[eSSU2V6Idx] = addr; } - m_Addresses->push_back(std::move(addr)); + } + + void RouterInfo::RemoveSSU2Address (bool v4) + { + if (v4) + { + if ((*m_Addresses)[eSSU2V6Idx]) + (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eSSU2V4Idx].reset (); + } + else + { + if ((*m_Addresses)[eSSU2V4Idx]) + (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eSSU2V6Idx].reset (); + } + UpdateSupportedTransports (); } bool RouterInfo::IsNTCP2 (bool v4only) const @@ -721,21 +822,17 @@ namespace data { if (IsV6 ()) { - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + if ((*m_Addresses)[eNTCP2V6Idx]) { - auto addr = *it; - if (addr->IsV6 ()) - { - if (addr->IsV4 ()) - { - addr->caps &= ~AddressCaps::eV6; - ++it; - } - else - it = m_Addresses->erase (it); - } - else - ++it; + if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx]) + (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eNTCP2V6Idx].reset (); + } + if ((*m_Addresses)[eSSU2V6Idx]) + { + if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx]) + (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -745,21 +842,17 @@ namespace data { if (IsV4 ()) { - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + if ((*m_Addresses)[eNTCP2V4Idx]) { - auto addr = *it; - if (addr->IsV4 ()) - { - if (addr->IsV6 ()) - { - addr->caps &= ~AddressCaps::eV4; - ++it; - } - else - it = m_Addresses->erase (it); - } - else - ++it; + if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx]) + (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eNTCP2V4Idx].reset (); + } + if ((*m_Addresses)[eSSU2V4Idx]) + { + if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx]) + (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eSSU2V4Idx].reset (); } UpdateSupportedTransports (); } @@ -780,33 +873,18 @@ namespace data { m_SupportedTransports &= ~eNTCP2V6Mesh; m_ReachableTransports &= ~eNTCP2V6Mesh; - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) - { - auto addr = *it; - if (i2p::util::net::IsYggdrasilAddress (addr->host)) - it = m_Addresses->erase (it); - else - ++it; - } + (*m_Addresses)[eNTCP2V6MeshIdx].reset (); } } std::shared_ptr RouterInfo::GetSSU2V4Address () const { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU2) && address->IsV4(); - }); + return (*GetAddresses ())[eSSU2V4Idx]; } std::shared_ptr RouterInfo::GetSSU2V6Address () const { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU2) && address->IsV6(); - }); + return (*GetAddresses ())[eSSU2V6Idx]; } std::shared_ptr RouterInfo::GetSSU2Address (bool v4) const @@ -831,8 +909,8 @@ namespace data #else return m_Addresses; #endif - } - + } + template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { @@ -843,65 +921,49 @@ namespace data auto addresses = m_Addresses; #endif for (const auto& address : *addresses) - if (filter (address)) return address; + if (address && filter (address)) return address; return nullptr; } - std::shared_ptr RouterInfo::GetNTCP2AddressWithStaticKey (const uint8_t * key) const + std::shared_ptr RouterInfo::GetNTCP2V4Address () const { - if (!key) return nullptr; - return GetAddress ( - [key](std::shared_ptr address)->bool - { - return address->IsNTCP2 () && !memcmp (address->s, key, 32); - }); + return (*GetAddresses ())[eNTCP2V4Idx]; } - std::shared_ptr RouterInfo::GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const + std::shared_ptr RouterInfo::GetNTCP2V6Address () const { - if (!key) return nullptr; - return GetAddress ( - [key, isV6](std::shared_ptr address)->bool - { - return address->IsSSU2 () && !memcmp (address->s, key, 32) && - ((isV6 && address->IsV6 ()) || (!isV6 && address->IsV4 ())); - }); + return (*GetAddresses ())[eNTCP2V6Idx]; } std::shared_ptr RouterInfo::GetPublishedNTCP2V4Address () const { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return address->IsPublishedNTCP2 () && address->host.is_v4 (); - }); + auto addr = (*GetAddresses ())[eNTCP2V4Idx]; + if (addr && addr->IsPublishedNTCP2 ()) return addr; + return nullptr; } std::shared_ptr RouterInfo::GetPublishedNTCP2V6Address () const { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return address->IsPublishedNTCP2 () && address->host.is_v6 () && - !i2p::util::net::IsYggdrasilAddress (address->host); - }); + auto addr = (*GetAddresses ())[eNTCP2V6Idx]; + if (addr && addr->IsPublishedNTCP2 ()) return addr; + return nullptr; } std::shared_ptr RouterInfo::GetYggdrasilAddress () const { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return address->IsPublishedNTCP2 () && i2p::util::net::IsYggdrasilAddress (address->host); - }); + return (*GetAddresses ())[eNTCP2V6MeshIdx]; } std::shared_ptr RouterInfo::GetProfile () const { - if (!m_Profile) - m_Profile = GetRouterProfile (GetIdentHash ()); - return m_Profile; + auto profile = m_Profile; + if (!profile) + { + profile = GetRouterProfile (GetIdentHash ()); + m_Profile = profile; + } + return profile; } void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted) const @@ -921,30 +983,22 @@ namespace data bool RouterInfo::IsSSU2PeerTesting (bool v4) const { if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; - return (bool)GetAddress ( - [v4](std::shared_ptr address)->bool - { - return (address->IsSSU2 ()) && address->IsPeerTesting () && - ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU (); - }); + auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + return addr && addr->IsPeerTesting () && addr->IsReachableSSU (); } bool RouterInfo::IsSSU2Introducer (bool v4) const { if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; - return (bool)GetAddress ( - [v4](std::shared_ptr address)->bool - { - return (address->IsSSU2 ()) && address->IsIntroducer () && - ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); - }); + auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + return addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port; } void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { for (auto& addr: *m_Addresses) { - if (!addr->published && (addr->transportStyle == eTransportNTCP2 || addr->transportStyle == eTransportSSU2)) + if (addr && !addr->published) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; @@ -958,6 +1012,7 @@ namespace data m_ReachableTransports = 0; for (const auto& addr: *m_Addresses) { + if (!addr) continue; uint8_t transports = 0; switch (addr->transportStyle) { @@ -994,6 +1049,16 @@ namespace data return netdb.NewRouterInfoBuffer (); } + std::shared_ptr RouterInfo::NewAddress () const + { + return netdb.NewRouterInfoAddress (); + } + + boost::shared_ptr RouterInfo::NewAddresses () const + { + return netdb.NewRouterInfoAddresses (); + } + void RouterInfo::RefreshTimestamp () { m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); @@ -1057,14 +1122,26 @@ namespace data { auto addresses = GetAddresses (); if (!addresses) return; - + uint64_t ts = htobe64 (GetTimestamp ()); s.write ((const char *)&ts, sizeof (ts)); // addresses - uint8_t numAddresses = addresses->size (); - s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (const auto& addr_ptr : *addresses) + uint8_t numAddresses = 0; + for (size_t idx = 0; idx < addresses->size(); idx++) { + auto addr_ptr = (*addresses)[idx]; + if (!addr_ptr) continue; + if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; + if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; + numAddresses++; + } + s.write ((char *)&numAddresses, sizeof (numAddresses)); + for (size_t idx = 0; idx < addresses->size(); idx++) + { + auto addr_ptr = (*addresses)[idx]; + if (!addr_ptr) continue; + if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; + if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; const Address& address = *addr_ptr; // calculate cost uint8_t cost = 0x7f; @@ -1162,7 +1239,7 @@ namespace data i = 0; for (const auto& introducer: address.ssu->introducers) { - WriteString ("ih" + boost::lexical_cast(i), properties); + WriteString ("ih" + boost::lexical_cast(i), properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64); @@ -1182,7 +1259,7 @@ namespace data } } } - + if (address.transportStyle == eTransportSSU2) { // write mtu @@ -1263,20 +1340,28 @@ namespace data return std::make_shared (); } + std::shared_ptr LocalRouterInfo::NewAddress () const + { + return std::make_shared
(); + } + + boost::shared_ptr LocalRouterInfo::NewAddresses () const + { + return boost::make_shared (); + } + bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) { auto addresses = GetAddresses (); if (!addresses) return false; - for (auto& addr : *addresses) + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) { - if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) - { - for (auto& intro: addr->ssu->introducers) - if (intro.iTag == introducer.iTag) return false; // already presented - addr->ssu->introducers.push_back (introducer); - SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); - return true; - } + for (auto& intro: addr->ssu->introducers) + if (intro.iTag == introducer.iTag) return false; // already presented + addr->ssu->introducers.push_back (introducer); + SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); + return true; } return false; } @@ -1285,19 +1370,17 @@ namespace data { auto addresses = GetAddresses (); if (!addresses) return false; - for (auto& addr: *addresses) + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) { - if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) - { - for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) - if (h == it->iH) - { - addr->ssu->introducers.erase (it); - if (addr->ssu->introducers.empty ()) - SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6)); - return true; - } - } + for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) + if (h == it->iH) + { + addr->ssu->introducers.erase (it); + if (addr->ssu->introducers.empty ()) + SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6)); + return true; + } } return false; } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 9e98769f..1024cda8 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -60,13 +60,25 @@ namespace data { public: + enum SupportedTransportsIdx + { + eNTCP2V4Idx = 0, + eNTCP2V6Idx, + eSSU2V4Idx, + eSSU2V6Idx, + eNTCP2V6MeshIdx, + eNumTransports + }; + +#define TransportBit(tr) e##tr = (1 << e##tr##Idx) + enum SupportedTransports { - eNTCP2V4 = 0x01, - eNTCP2V6 = 0x02, - eSSU2V4 = 0x04, - eSSU2V6 = 0x08, - eNTCP2V6Mesh = 0x10, + TransportBit(NTCP2V4), // 0x01 + TransportBit(NTCP2V6), // 0x02 + TransportBit(SSU2V4), // 0x04 + TransportBit(SSU2V6), // 0x08 + TransportBit(NTCP2V6Mesh), // 0x10 eAllTransports = 0xFF }; typedef uint8_t CompatibleTransports; @@ -160,7 +172,7 @@ namespace data Buffer (const uint8_t * buf, size_t len); }; - typedef std::vector > Addresses; + typedef std::array, eNumTransports> Addresses; RouterInfo (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; @@ -177,8 +189,8 @@ namespace data 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 - std::shared_ptr GetNTCP2AddressWithStaticKey (const uint8_t * key) const; - std::shared_ptr GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const; + std::shared_ptr GetNTCP2V4Address () const; + std::shared_ptr GetNTCP2V6Address () const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetYggdrasilAddress () const; @@ -186,11 +198,14 @@ namespace data std::shared_ptr GetSSU2V6Address () const; std::shared_ptr GetSSU2Address (bool v4) const; + void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps); // non published void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); - void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps = 0); // non published + const boost::asio::ip::address& host, int port); // published + void RemoveNTCP2Address (bool v4); + void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps); // non published void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, const boost::asio::ip::address& host, int port); // published + void RemoveSSU2Address (bool v4); void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; @@ -227,7 +242,7 @@ namespace data void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; - const uint8_t * GetBuffer () const { return m_Buffer->data (); }; + const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; }; const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary size_t GetBufferLen () const { return m_BufferLen; }; @@ -236,9 +251,9 @@ namespace data bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; - void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; + void DropProfile () { m_Profile = nullptr; }; - void Update (const uint8_t * buf, size_t len); + bool Update (const uint8_t * buf, size_t len); void DeleteBuffer () { m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; @@ -273,6 +288,8 @@ namespace data template 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; private: @@ -312,6 +329,8 @@ namespace data void UpdateCapsProperty (); 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; private: diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index dba228fd..3773f03d 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -22,7 +22,7 @@ namespace transport RunnableServiceWithWork ("SSU2"), m_ReceiveService ("SSU2r"), m_SocketV4 (m_ReceiveService.GetService ()), m_SocketV6 (m_ReceiveService.GetService ()), m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()), - m_TerminationTimer (GetService ()), m_ResendTimer (GetService ()), + m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()), m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()), m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false) { @@ -109,6 +109,7 @@ namespace transport m_ReceiveService.Start (); } ScheduleTermination (); + ScheduleCleanup (); ScheduleResend (false); } } @@ -118,6 +119,7 @@ namespace transport if (IsRunning ()) { m_TerminationTimer.cancel (); + m_CleanupTimer.cancel (); m_ResendTimer.cancel (); m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimerV6.cancel (); @@ -173,7 +175,7 @@ namespace transport mtu = i2p::util::net::GetMTU (localAddress); if (mtu > maxMTU) mtu = maxMTU; } - else + else if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; i2p::context.SetMTU (mtu, false); @@ -210,6 +212,8 @@ namespace transport boost::asio::ip::udp::socket& socket = localEndpoint.address ().is_v6 () ? m_SocketV6 : m_SocketV4; try { + if (socket.is_open ()) + socket.close (); socket.open (localEndpoint.protocol ()); if (localEndpoint.address ().is_v6 ()) socket.set_option (boost::asio::ip::v6_only (true)); @@ -807,6 +811,22 @@ namespace transport it++; } + ScheduleTermination (); + } + } + + void SSU2Server::ScheduleCleanup () + { + m_CleanupTimer.expires_from_now (boost::posix_time::seconds(SSU2_CLEANUP_INTERVAL)); + m_CleanupTimer.async_wait (std::bind (&SSU2Server::HandleCleanupTimer, + this, std::placeholders::_1)); + } + + void SSU2Server::HandleCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Relays.begin (); it != m_Relays.begin ();) { if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) @@ -833,7 +853,9 @@ namespace transport m_PacketsPool.CleanUpMt (); m_SentPacketsPool.CleanUp (); - ScheduleTermination (); + m_IncompleteMessagesPool.CleanUp (); + m_FragmentsPool.CleanUp (); + ScheduleCleanup (); } } @@ -876,7 +898,7 @@ namespace transport { // token expired m_OutgoingTokens.erase (it); - return 0; + return 0; } return it->second.first; } @@ -1210,9 +1232,9 @@ namespace transport { if (!m_ProxyEndpoint) return; m_UDPAssociateSocket.reset (new boost::asio::ip::tcp::socket (m_ReceiveService.GetService ())); - m_UDPAssociateSocket->async_connect (*m_ProxyEndpoint, + m_UDPAssociateSocket->async_connect (*m_ProxyEndpoint, [this] (const boost::system::error_code& ecode) - { + { if (ecode) { LogPrint (eLogError, "SSU2: Can't connect to proxy ", *m_ProxyEndpoint, " ", ecode.message ()); @@ -1227,7 +1249,7 @@ namespace transport void SSU2Server::HandshakeWithProxy () { if (!m_UDPAssociateSocket) return; - m_UDPRequestHeader[0] = SOCKS5_VER; + m_UDPRequestHeader[0] = SOCKS5_VER; m_UDPRequestHeader[1] = 1; // 1 method m_UDPRequestHeader[2] = 0; // no authentication boost::asio::async_write (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, 3), boost::asio::transfer_all(), @@ -1274,8 +1296,8 @@ namespace transport void SSU2Server::SendUDPAssociateRequest () { if (!m_UDPAssociateSocket) return; - m_UDPRequestHeader[0] = SOCKS5_VER; - m_UDPRequestHeader[1] = SOCKS5_CMD_UDP_ASSOCIATE; + m_UDPRequestHeader[0] = SOCKS5_VER; + m_UDPRequestHeader[1] = SOCKS5_CMD_UDP_ASSOCIATE; m_UDPRequestHeader[2] = 0; // RSV m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4; // TODO: implement ipv6 proxy memset (m_UDPRequestHeader + 4, 0, 6); // address and port all zeros @@ -1374,7 +1396,7 @@ namespace transport LogPrint(eLogInfo, "SSU2: Reconnecting to proxy"); ConnectToProxy (); } - }); + }); } bool SSU2Server::SetProxy (const std::string& address, uint16_t port) diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 13607c29..2e652786 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,7 +18,8 @@ namespace i2p { namespace transport { - const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // in seconds + const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds + const int SSU2_CLEANUP_INTERVAL = 72; // in seconds const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds @@ -30,7 +31,7 @@ namespace transport const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes const int SSU2_KEEP_ALIVE_INTERVAL = 30; // in seconds const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds - + class SSU2Server: private i2p::util::RunnableServiceWithWork { struct Packet @@ -97,6 +98,8 @@ namespace transport void RescheduleIntroducersUpdateTimerV6 (); i2p::util::MemoryPool& GetSentPacketsPool () { return m_SentPacketsPool; }; + i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; + i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; private: @@ -111,6 +114,9 @@ namespace transport void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); + void ScheduleCleanup (); + void HandleCleanupTimer (const boost::system::error_code& ecode); + void ScheduleResend (bool more); void HandleResendTimer (const boost::system::error_code& ecode); @@ -132,7 +138,7 @@ namespace transport void SendUDPAssociateRequest (); void ReadUDPAssociateReply (); void ReadUDPAssociateSocket (); // handle if closed by peer - + private: ReceiveService m_ReceiveService; @@ -147,7 +153,9 @@ namespace transport std::list m_Introducers, m_IntroducersV6; // introducers we are connected to i2p::util::MemoryPoolMt m_PacketsPool; i2p::util::MemoryPool m_SentPacketsPool; - boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer, + i2p::util::MemoryPool m_IncompleteMessagesPool; + i2p::util::MemoryPool m_FragmentsPool; + boost::asio::deadline_timer m_TerminationTimer, m_CleanupTimer, m_ResendTimer, m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6; std::shared_ptr m_LastSession; bool m_IsPublished; // if we maintain introducers @@ -160,7 +168,7 @@ namespace transport std::unique_ptr m_UDPAssociateSocket; std::unique_ptr m_ProxyRelayEndpoint; std::unique_ptr m_ProxyConnectRetryTimer; - + public: // for HTTP/I2PControl diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 85c91f71..f2b002cd 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -32,15 +32,60 @@ namespace transport nextFragmentNum++; } + bool SSU2IncompleteMessage::ConcatOutOfSequenceFragments () + { + bool isLast = false; + while (outOfSequenceFragments) + { + if (outOfSequenceFragments->fragmentNum == nextFragmentNum) + { + AttachNextFragment (outOfSequenceFragments->buf, outOfSequenceFragments->len); + isLast = outOfSequenceFragments->isLast; + if (isLast) + outOfSequenceFragments = nullptr; + else + outOfSequenceFragments = outOfSequenceFragments->next; + } + else + break; + } + return isLast; + } + + void SSU2IncompleteMessage::AddOutOfSequenceFragment (std::shared_ptr fragment) + { + if (!fragment || !fragment->fragmentNum) return; // fragment 0 not allowed + if (fragment->fragmentNum < nextFragmentNum) return; // already processed + if (!outOfSequenceFragments) + outOfSequenceFragments = fragment; + else + { + auto frag = outOfSequenceFragments; + std::shared_ptr prev; + do + { + if (fragment->fragmentNum < frag->fragmentNum) break; // found + if (fragment->fragmentNum == frag->fragmentNum) return; // duplicate + prev = frag; frag = frag->next; + } + while (frag); + fragment->next = frag; + if (prev) + prev->next = fragment; + else + outOfSequenceFragments = fragment; + } + lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + } SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter, std::shared_ptr addr): TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), m_Server (server), m_Address (addr), m_RemoteTransports (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), - m_SendPacketNum (0), m_ReceivePacketNum (0), m_IsDataReceived (false), - m_WindowSize (SSU2_MIN_WINDOW_SIZE), m_RTT (SSU2_RESEND_INTERVAL), - m_RTO (SSU2_RESEND_INTERVAL*SSU2_kAPPA), m_RelayTag (0), + m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0), + m_IsDataReceived (false), m_WindowSize (SSU2_MIN_WINDOW_SIZE), + m_RTT (SSU2_RESEND_INTERVAL), m_RTO (SSU2_RESEND_INTERVAL*SSU2_kAPPA), m_RelayTag (0), m_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose), m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32) // min size { @@ -96,7 +141,7 @@ namespace transport // timeout expired if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer LogPrint (eLogWarning, "SSU2: Session was not introduced after ", SSU2_CONNECT_TIMEOUT, " seconds"); - else + else LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds"); Terminate (); } @@ -216,10 +261,12 @@ namespace transport m_SessionConfirmedFragment.reset (nullptr); m_PathChallenge.reset (nullptr); m_SendQueue.clear (); + m_SendQueueSize = 0; 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 ()); LogPrint (eLogDebug, "SSU2: Session terminated"); @@ -290,7 +337,7 @@ namespace transport { if (m_State == eSSU2SessionStateTerminated) return; for (auto it: msgs) - m_SendQueue.push_back (it); + m_SendQueue.push_back (std::move (it)); SendQueue (); if (m_SendQueue.size () > 0) // windows is full @@ -304,6 +351,7 @@ namespace transport RequestTermination (eSSU2TerminationReasonTimeout); } } + m_SendQueueSize = m_SendQueue.size (); } bool SSU2Session::SendQueue () @@ -463,6 +511,7 @@ namespace transport LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session"); m_SentPackets.clear (); m_SendQueue.clear (); + m_SendQueueSize = 0; RequestTermination (eSSU2TerminationReasonTimeout); return resentPackets.size (); } @@ -527,7 +576,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); break; - } + } const uint8_t nonce[12] = {0}; uint64_t headerX[2]; i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); @@ -570,7 +619,7 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, ts/1000); + htobe32buf (payload + 3, (ts + 500)/1000); size_t payloadSize = 7; if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ()) { @@ -615,7 +664,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: SessionRequest message too short ", len); return; - } + } const uint8_t nonce[12] = {0}; uint8_t headerX[48]; i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); @@ -684,7 +733,7 @@ namespace transport size_t maxPayloadSize = m_MaxPayloadSize - 48; payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, ts/1000); + htobe32buf (payload + 3, (ts + 500)/1000); size_t payloadSize = 7; payloadSize += CreateAddressBlock (payload + payloadSize, maxPayloadSize - payloadSize, m_RemoteEndpoint); if (m_RelayTag) @@ -886,7 +935,7 @@ namespace transport LogPrint (eLogWarning, "SSU2: SessionConfirmed fragment too short ", len); if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); return false; - } + } if (!(header.h.flags[0] & 0xF0)) { // first fragment @@ -939,7 +988,7 @@ namespace transport LogPrint (eLogWarning, "SSU2: SessionConfirmed message too short ", len); if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); return false; - } + } // KDF for Session Confirmed part 1 m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) // decrypt part1 @@ -992,10 +1041,18 @@ namespace transport LogPrint (eLogError, "SSU2: SessionConfirmed malformed RouterInfo block"); return false; } - m_Address = ri->GetSSU2AddressWithStaticKey (S, m_RemoteEndpoint.address ().is_v6 ()); - if (!m_Address) + m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address (); + if (!m_Address || memcmp (S, m_Address->s, 32)) { - LogPrint (eLogError, "SSU2: No SSU2 address with static key found in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + LogPrint (eLogError, "SSU2: Wrong static key in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + return false; + } + if (m_Address->published && m_RemoteEndpoint.address () != m_Address->host && + (!m_RemoteEndpoint.address ().is_v6 () || + memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address + { + LogPrint (eLogError, "SSU2: Host mismatch between published address ", m_Address->host, + " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); return false; } // update RouterInfo in netdb @@ -1046,7 +1103,7 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); size_t payloadSize = 7; payloadSize += CreatePaddingBlock (payload + payloadSize, 25 - payloadSize, 1); // encrypt @@ -1117,7 +1174,7 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); size_t payloadSize = 7; payloadSize += CreateAddressBlock (payload + payloadSize, 56 - payloadSize, m_RemoteEndpoint); if (m_TerminationReason != eSSU2TerminationReasonNormalClose) @@ -1152,7 +1209,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: Retry message too short ", len); return false; - } + } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); @@ -1205,7 +1262,7 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); size_t payloadSize = 7; payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, ep); payloadSize += CreateRelayResponseBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, @@ -1241,7 +1298,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: HolePunch 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); @@ -1281,7 +1338,7 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + 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); @@ -1317,7 +1374,7 @@ namespace transport { 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); @@ -1389,7 +1446,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: Data message too short ", len); return; - } + } uint8_t payload[SSU2_MAX_PACKET_SIZE]; size_t payloadSize = len - 32; uint32_t packetNum = be32toh (header.h.packetNum); @@ -1447,7 +1504,7 @@ namespace transport nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header memcpy (nextMsg->GetNTCP2Header (), buf + offset, size); nextMsg->FromNTCP2 (); // SSU2 has the same format as NTCP2 - m_Handler.PutNextMessage (std::move (nextMsg)); + HandleI2NPMsg (std::move (nextMsg)); m_IsDataReceived = true; break; } @@ -1462,19 +1519,19 @@ namespace transport m_IsDataReceived = true; break; case eSSU2BlkTermination: - { - uint8_t rsn = buf[11]; // reason + { + uint8_t rsn = buf[11]; // reason LogPrint (eLogDebug, "SSU2: Termination reason=", (int)rsn); if (IsEstablished () && rsn != eSSU2TerminationReasonTerminationReceived) RequestTermination (eSSU2TerminationReasonTerminationReceived); - else if (m_State != eSSU2SessionStateTerminated) + else if (m_State != eSSU2SessionStateTerminated) { - if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived) + if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived) m_State = eSSU2SessionStateClosingConfirmed; Done (); - } + } break; - } + } case eSSU2BlkRelayRequest: LogPrint (eLogDebug, "SSU2: RelayRequest"); HandleRelayRequest (buf + offset, size); @@ -1560,6 +1617,7 @@ namespace transport { case eSSU2SessionStateSessionRequestReceived: case eSSU2SessionStateTokenRequestReceived: + case eSSU2SessionStateEstablished: if (std::abs (offset) > SSU2_CLOCK_SKEW) m_TerminationReason = eSSU2TerminationReasonClockSkew; break; @@ -1661,25 +1719,20 @@ namespace transport bool isV4 = ep.address ().is_v4 (); if (ep.port () != m_Server.GetPort (isV4)) { + LogPrint (eLogInfo, "SSU2: Our port ", ep.port (), " received from ", m_RemoteEndpoint, " is different from ", m_Server.GetPort (isV4)); if (isV4) { - if (i2p::context.GetStatus () == eRouterStatusTesting || - m_State == eSSU2SessionStatePeerTest) - { + if (i2p::context.GetStatus () == eRouterStatusTesting) i2p::context.SetError (eRouterErrorSymmetricNAT); - i2p::context.SetStatus (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimer (); - } + else if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetError (eRouterErrorFullConeNAT); } else { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting || - m_State == eSSU2SessionStatePeerTest) - { + if (i2p::context.GetStatusV6 () == eRouterStatusTesting) i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT); - i2p::context.SetStatusV6 (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimerV6 (); - } + else if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetErrorV6 (eRouterErrorFullConeNAT); } } else @@ -1687,11 +1740,23 @@ namespace transport if (isV4) { if (i2p::context.GetError () == eRouterErrorSymmetricNAT) + { + if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetStatus (eRouterStatusOK); + i2p::context.SetError (eRouterErrorNone); + } + else if (i2p::context.GetError () == eRouterErrorFullConeNAT) i2p::context.SetError (eRouterErrorNone); } else { if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT) + { + if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetStatusV6 (eRouterStatusOK); + i2p::context.SetErrorV6 (eRouterErrorNone); + } + else if (i2p::context.GetErrorV6 () == eRouterErrorFullConeNAT) i2p::context.SetErrorV6 (eRouterErrorNone); } } @@ -1716,17 +1781,17 @@ namespace transport } else { - m = std::make_shared(); + m = m_Server.GetIncompleteMessagesPool ().AcquireShared (); m_IncompleteMessages.emplace (msgID, m); } m->msg = msg; m->nextFragmentNum = 1; m->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - if (found && ConcatOutOfSequenceFragments (m)) + if (found && m->ConcatOutOfSequenceFragments ()) { // we have all follow-on fragments already m->msg->FromNTCP2 (); - m_Handler.PutNextMessage (std::move (m->msg)); + HandleI2NPMsg (std::move (m->msg)); m_IncompleteMessages.erase (it); } } @@ -1735,11 +1800,17 @@ namespace transport { if (len < 5) return; uint8_t fragmentNum = buf[0] >> 1; + if (!fragmentNum || fragmentNum >= SSU2_MAX_NUM_FRAGMENTS) + { + LogPrint (eLogWarning, "SSU2: Invalid follow-on fragment num ", fragmentNum); + return; + } bool isLast = buf[0] & 0x01; uint32_t msgID; memcpy (&msgID, buf + 1, 4); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end ()) { + if (fragmentNum < it->second->nextFragmentNum) return; // duplicate if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS && it->second->msg) { @@ -1748,14 +1819,14 @@ namespace transport if (isLast) { it->second->msg->FromNTCP2 (); - m_Handler.PutNextMessage (std::move (it->second->msg)); + HandleI2NPMsg (std::move (it->second->msg)); m_IncompleteMessages.erase (it); } else { - if (ConcatOutOfSequenceFragments (it->second)) + if (it->second->ConcatOutOfSequenceFragments ()) { - m_Handler.PutNextMessage (std::move (it->second->msg)); + HandleI2NPMsg (std::move (it->second->msg)); m_IncompleteMessages.erase (it); } else @@ -1767,38 +1838,17 @@ namespace transport else { // follow-on fragment before first fragment - auto msg = std::make_shared (); + auto msg = m_Server.GetIncompleteMessagesPool ().AcquireShared (); msg->nextFragmentNum = 0; it = m_IncompleteMessages.emplace (msgID, msg).first; } // insert out of sequence fragment - if (fragmentNum >= SSU2_MAX_NUM_FRAGMENTS) - { - LogPrint (eLogWarning, "SSU2: Fragment number ", fragmentNum, " exceeds ", SSU2_MAX_NUM_FRAGMENTS); - return; - } - auto fragment = std::make_shared (); + auto fragment = m_Server.GetFragmentsPool ().AcquireShared (); memcpy (fragment->buf, buf + 5, len -5); fragment->len = len - 5; + fragment->fragmentNum = fragmentNum; fragment->isLast = isLast; - it->second->outOfSequenceFragments.emplace (fragmentNum, fragment); - it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - } - - bool SSU2Session::ConcatOutOfSequenceFragments (std::shared_ptr m) - { - if (!m) return false; - bool isLast = false; - for (auto it = m->outOfSequenceFragments.begin (); it != m->outOfSequenceFragments.end ();) - if (it->first == m->nextFragmentNum) - { - m->AttachNextFragment (it->second->buf, it->second->len); - isLast = it->second->isLast; - it = m->outOfSequenceFragments.erase (it); - } - else - break; - return isLast; + it->second->AddOutOfSequenceFragment (fragment); } void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) @@ -1822,10 +1872,9 @@ namespace transport // send relay intro to Charlie auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI - if (r) - i2p::data::netdb.PopulateRouterInfoBuffer (r); - else - LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); + 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) @@ -1836,7 +1885,7 @@ namespace transport session->SendData (payload, payloadSize); } - void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len) + void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) { // we are Charlie SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; @@ -1891,9 +1940,22 @@ namespace transport code = eSSU2RelayResponseCodeCharlieSignatureFailure; } } + else if (!attempts) + { + // RouterInfo might come in the next packet, try again + auto vec = std::make_shared >(len); + memcpy (vec->data (), buf, len); + auto s = shared_from_this (); + m_Server.GetService ().post ([s, vec, attempts]() + { + LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1); + s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1); + }); + return; + } else { - LogPrint (eLogError, "SSU2: RelayIntro unknown router to introduce"); + LogPrint (eLogWarning, "SSU2: RelayIntro unknown router to introduce"); code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; } // send relay response to Bob @@ -2006,7 +2068,7 @@ namespace transport auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Alice's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); - if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); + if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; if (!packet->payloadSize && r) session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); @@ -2110,7 +2172,7 @@ namespace transport uint8_t payload[SSU2_MAX_PACKET_SIZE]; // Charlie's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); - if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); + 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)); @@ -2234,15 +2296,31 @@ namespace transport m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce)); break; case 7: // Alice from Charlie 2 - m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce)); 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]); } } + void SSU2Session::HandleI2NPMsg (std::shared_ptr&& msg) + { + if (!msg) return; + uint32_t msgID = msg->GetMsgID (); + if (!msg->IsExpired ()) + { + // m_LastActivityTimestamp is updated in ProcessData before + if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)m_LastActivityTimestamp).second) + m_Handler.PutNextMessage (std::move (msg)); + else + LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received"); + } + else + LogPrint (eLogDebug, "SSU2: Message ", msgID, " expired"); + } + bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep) { if (size < 2) return false; @@ -2385,30 +2463,33 @@ namespace transport if (ackThrough) { if (m_OutOfSequencePackets.empty ()) - acnt = std::min ((int)ackThrough, 255); // no gaps + acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps else { auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num while (it != m_OutOfSequencePackets.rend () && *it == ackThrough - acnt - 1) { acnt++; - it++; + if (acnt >= SSU2_MAX_NUM_ACK_PACKETS) + break; + else + it++; } // ranges uint32_t lastNum = ackThrough - acnt; - if (acnt > 255) + if (acnt > SSU2_MAX_NUM_ACNT) { - auto d = std::div (acnt - 255, 255); - acnt = 255; + auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); + acnt = SSU2_MAX_NUM_ACNT; if (d.quot > maxNumRanges) { d.quot = maxNumRanges; d.rem = 0; } - // Acks only ragnes for acnt + // Acks only ranges for acnt for (int i = 0; i < d.quot; i++) { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = 255; // NACKs 0, Acks 255 + buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 numRanges++; } if (d.rem > 0) @@ -2417,21 +2498,25 @@ namespace transport numRanges++; } } - while (it != m_OutOfSequencePackets.rend () && numRanges < maxNumRanges) + int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; + while (it != m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - if (lastNum - (*it) > 255) + if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) { // NACKs only ranges - if (lastNum > (*it) + 255*(maxNumRanges - numRanges)) break; // too many NACKs - while (lastNum - (*it) > 255) + if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) { - buf[8 + numRanges*2] = 255; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 - lastNum -= 255; + buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= SSU2_MAX_NUM_ACNT; numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; } } // NACKs and Acks ranges buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs + numPackets += buf[8 + numRanges*2]; lastNum = *it; it++; int numAcks = 1; while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) @@ -2439,28 +2524,31 @@ namespace transport numAcks++; lastNum--; it++; } - while (numAcks > 255) + while (numAcks > SSU2_MAX_NUM_ACNT) { // Acks only ranges - buf[8 + numRanges*2 + 1] = 255; // Acks 255 - numAcks -= 255; + buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 + numAcks -= SSU2_MAX_NUM_ACNT; numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2] = 0; // NACKs 0 - if (numRanges >= maxNumRanges) break; + if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; } - if (numAcks > 255) numAcks = 255; + if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numPackets += numAcks; numRanges++; } - if (numRanges < maxNumRanges && it == m_OutOfSequencePackets.rend ()) + if (it == m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - // add range between out-of-seqence and received + // add range between out-of-sequence and received int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; if (nacks > 0) { - if (nacks > 255) nacks = 255; + if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2] = nacks; - buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, 255); + buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); numRanges++; } } @@ -2687,15 +2775,25 @@ namespace transport if (packetNum <= m_ReceivePacketNum) return false; // duplicate if (packetNum == m_ReceivePacketNum + 1) { - for (auto it = m_OutOfSequencePackets.begin (); it != m_OutOfSequencePackets.end ();) + if (!m_OutOfSequencePackets.empty ()) { + auto it = m_OutOfSequencePackets.begin (); if (*it == packetNum + 1) { - packetNum++; - it = m_OutOfSequencePackets.erase (it); + // first out of sequence packet is in sequence now + packetNum++; it++; + while (it != m_OutOfSequencePackets.end ()) + { + if (*it == packetNum + 1) + { + packetNum++; + it++; + } + else // next out of sequence + break; + } + m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it); } - else - break; } m_ReceivePacketNum = packetNum; } @@ -2707,7 +2805,16 @@ namespace transport void SSU2Session::SendQuickAck () { uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreateAckBlock (payload, m_MaxPayloadSize); + size_t payloadSize = 0; + if (m_SendPacketNum > m_LastDatetimeSentPacketNum + SSU2_SEND_DATETIME_NUM_PACKETS) + { + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + payloadSize += 7; + m_LastDatetimeSentPacketNum = m_SendPacketNum; + } + payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); SendData (payload, payloadSize); } @@ -2765,10 +2872,26 @@ namespace transport else ++it; } + if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > m_LastActivityTimestamp + SSU2_DECAY_INTERVAL) + // decay + m_ReceivedI2NPMsgIDs.clear (); + else + { + // delete old received msgIDs + for (auto it = m_ReceivedI2NPMsgIDs.begin (); it != m_ReceivedI2NPMsgIDs.end ();) + { + if (ts > it->second + SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT) + it = m_ReceivedI2NPMsgIDs.erase (it); + else + ++it; + } + } if (!m_OutOfSequencePackets.empty ()) { - if (m_OutOfSequencePackets.size () > 2*SSU2_MAX_NUM_ACK_RANGES || - *m_OutOfSequencePackets.rbegin () > m_ReceivePacketNum + 255*8) + int ranges = 0; + while (ranges < 8 && !m_OutOfSequencePackets.empty () && + (m_OutOfSequencePackets.size () > 2*SSU2_MAX_NUM_ACK_RANGES || + *m_OutOfSequencePackets.rbegin () > m_ReceivePacketNum + SSU2_MAX_NUM_ACK_PACKETS)) { uint32_t packet = *m_OutOfSequencePackets.begin (); if (packet > m_ReceivePacketNum + 1) @@ -2777,9 +2900,13 @@ namespace transport packet--; m_ReceivePacketNum = packet - 1; UpdateReceivePacketNum (packet); + ranges++; } else + { LogPrint (eLogError, "SSU2: Out of sequence packet ", packet, " is less than last received ", m_ReceivePacketNum); + break; + } } if (m_OutOfSequencePackets.size () > 255*4) { @@ -2816,6 +2943,8 @@ namespace transport void SSU2Session::FlushData () { bool sent = SendQueue (); // if we have something to send + if (sent) + m_SendQueueSize = m_SendQueue.size (); if (m_IsDataReceived) { if (!sent) SendQuickAck (); diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index d12395d6..363cb982 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -39,14 +39,20 @@ namespace transport const int SSU2_RESEND_INTERVAL = 300; // in milliseconds const int SSU2_MAX_NUM_RESENDS = 5; const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check + const int SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT = 10; // in seconds + const int SSU2_DECAY_INTERVAL = 20; // in seconds const size_t SSU2_MIN_WINDOW_SIZE = 16; // in packets const size_t SSU2_MAX_WINDOW_SIZE = 256; // in packets const size_t SSU2_MIN_RTO = 100; // in milliseconds const size_t SSU2_MAX_RTO = 2500; // in milliseconds const float SSU2_kAPPA = 1.8; const size_t SSU2_MAX_OUTGOING_QUEUE_SIZE = 500; // in messages + const int SSU2_MAX_NUM_ACNT = 255; // acnt, acks or nacks + const int SSU2_MAX_NUM_ACK_PACKETS = 510; // 2*255 ack + nack const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64; + const int SSU2_SEND_DATETIME_NUM_PACKETS = 250; // flags const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01; @@ -168,15 +174,19 @@ namespace transport { uint8_t buf[SSU2_MAX_PACKET_SIZE]; size_t len; + int fragmentNum; bool isLast; + std::shared_ptr next; }; std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds - std::map > outOfSequenceFragments; + std::shared_ptr outOfSequenceFragments; // #1 and more void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); + bool ConcatOutOfSequenceFragments (); // true if message complete + void AddOutOfSequenceFragment (std::shared_ptr fragment); }; struct SSU2SentPacket @@ -245,7 +255,7 @@ namespace transport void SendI2NPMessages (const std::vector >& msgs) override; uint32_t GetRelayTag () const override { return m_RelayTag; }; size_t Resend (uint64_t ts); // return number or resent packets - bool IsEstablished () const { return m_State == eSSU2SessionStateEstablished; }; + bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; }; uint64_t GetConnID () const { return m_SourceConnID; }; SSU2SessionState GetState () const { return m_State; }; void SetState (SSU2SessionState state) { m_State = state; }; @@ -303,11 +313,11 @@ namespace transport 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); - bool ConcatOutOfSequenceFragments (std::shared_ptr m); // true if message complete void HandleRelayRequest (const uint8_t * buf, size_t len); - void HandleRelayIntro (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); + 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); @@ -335,10 +345,10 @@ namespace transport uint64_t m_DestConnID, m_SourceConnID; SSU2SessionState m_State; uint8_t m_KeyDataSend[64], m_KeyDataReceive[64]; - uint32_t m_SendPacketNum, m_ReceivePacketNum; + uint32_t m_SendPacketNum, m_ReceivePacketNum, m_LastDatetimeSentPacketNum; std::set m_OutOfSequencePackets; // packet nums > receive packet num std::map > m_SentPackets; // packetNum -> packet - std::map > m_IncompleteMessages; // I2NP + 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::list > m_SendQueue; @@ -351,6 +361,7 @@ namespace transport SSU2TerminationReason m_TerminationReason; size_t m_MaxPayloadSize; std::unique_ptr m_PathChallenge; + std::unordered_map m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds }; inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 5b4468fd..62c4acc7 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -478,11 +478,11 @@ namespace stream { if (!len) return 0; size_t ret = 0; + volatile bool done = false; std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; - std::unique_lock l(newDataReceivedMutex); AsyncReceive (boost::asio::buffer (buf, len), - [&ret, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) + [&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode == boost::asio::error::timed_out) ret = 0; @@ -490,13 +490,32 @@ namespace stream ret = bytes_transferred; std::unique_lock l(newDataReceivedMutex); newDataReceived.notify_all (); + done = true; }, timeout); - if (newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) - ret = 0; + if (!done) + { std::unique_lock l(newDataReceivedMutex); + if (!done && newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) + ret = 0; + } + if (!done) + { + // make sure that AsycReceive complete + auto s = shared_from_this(); + m_Service.post ([s]() + { + s->m_ReceiveTimer.cancel (); + }); + int i = 0; + while (!done && i < 100) // 1 sec + { + std::this_thread::sleep_for (std::chrono::milliseconds(10)); + i++; + } + } return ret; - } - + } + size_t Stream::Send (const uint8_t * buf, size_t len) { AsyncSend (buf, len, nullptr); @@ -1377,11 +1396,11 @@ namespace stream }); if (timeout) streamAccept.wait_for (l, std::chrono::seconds (timeout)); - else + else streamAccept.wait (l); return stream; - } - + } + void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 105977e8..0e1f7e88 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -186,7 +186,7 @@ namespace stream void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; size_t Receive (uint8_t * buf, size_t len, int timeout); - + void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); }; /** only call close from destination thread, use Stream::AsyncClose for other threads */ @@ -280,7 +280,7 @@ namespace stream void AcceptOnce (const Acceptor& acceptor); void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); std::shared_ptr AcceptStream (int timeout = 0); // sync - + std::shared_ptr GetOwner () const { return m_Owner; }; void SetOwner (std::shared_ptr owner) { m_Owner = owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index d898395f..72f181a2 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -80,6 +80,13 @@ namespace data { return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); } + uint8_t GetBit (int i) const + { + int pos = i >> 3; // /8 + if (pos >= (int)sz) return 0; + return m_Buf[pos] & (0x80 >> (i & 0x07)); + } + private: union // 8 bytes aligned diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index c4f3fa19..6c2c52a7 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,16 +20,21 @@ namespace i2p namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): - TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): + TunnelBase (receiveTunnelID, nextTunnelID, nextIdent), + m_LayerKey (layerKey), m_IVKey (ivKey) { - m_Encryption.SetKeys (layerKey, ivKey); } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { - m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); + if (!m_Encryption) + { + m_Encryption.reset (new i2p::crypto::TunnelEncryption); + m_Encryption->SetKeys (m_LayerKey, m_IVKey); + } + m_Encryption->Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } @@ -94,8 +99,8 @@ namespace tunnel } std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey, + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index bce90958..60e2f450 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,8 +28,8 @@ namespace tunnel public: TransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey); + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; @@ -39,7 +39,8 @@ namespace tunnel void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: - i2p::crypto::TunnelEncryption m_Encryption; + i2p::crypto::AESKey m_LayerKey, m_IVKey; + std::unique_ptr m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel @@ -47,8 +48,8 @@ namespace tunnel public: TransitTunnelParticipant (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_NumTransmittedBytes (0) {}; ~TransitTunnelParticipant (); @@ -68,8 +69,8 @@ namespace tunnel public: TransitTunnelGateway (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(this) {}; @@ -88,8 +89,8 @@ namespace tunnel public: TransitTunnelEndpoint (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound @@ -104,8 +105,8 @@ namespace tunnel }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey, + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint); } } diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 87c730f4..83b6cacd 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -74,7 +74,8 @@ namespace transport public: TransportSession (std::shared_ptr router, int terminationTimeout): - m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), + m_NumSentBytes (0), m_NumReceivedBytes (0), m_SendQueueSize (0), + m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) @@ -100,12 +101,16 @@ namespace transport size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + size_t GetSendQueueSize () const { return m_SendQueueSize; }; bool IsOutgoing () const { return m_IsOutgoing; }; int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; bool IsTerminationTimeoutExpired (uint64_t ts) const - { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; + { + return ts >= m_LastActivityTimestamp + GetTerminationTimeout () || + ts + GetTerminationTimeout () < m_LastActivityTimestamp; + }; uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers @@ -114,12 +119,12 @@ namespace transport virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; virtual bool IsEstablished () const = 0; - + protected: std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; - size_t m_NumSentBytes, m_NumReceivedBytes; + size_t m_NumSentBytes, m_NumReceivedBytes, m_SendQueueSize; bool m_IsOutgoing; int m_TerminationTimeout; uint64_t m_LastActivityTimestamp; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c571d45a..aa326a92 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -476,7 +476,7 @@ namespace transport bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { if (!peer.router) // reconnect - peer.router = netdb.FindRouter (ident); // try to get new one from netdb + peer.SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb if (peer.router) // we have RI already { if (peer.priority.empty ()) @@ -539,7 +539,8 @@ namespace transport } LogPrint (eLogInfo, "Transports: No compatible addresses available"); - i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed + if (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); m_Peers.erase (ident); @@ -598,7 +599,7 @@ namespace transport if (r) { LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); - it->second.router = r; + it->second.SetRouter (r); ConnectToPeer (ident, it->second); } else @@ -716,7 +717,8 @@ namespace transport session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore auto ts = i2p::util::GetSecondsSinceEpoch (); std::unique_lock l(m_PeersMutex); - m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts })); + auto it = m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts })).first; + it->second.sessions.push_back (session); } }); } @@ -814,18 +816,101 @@ namespace transport } } - std::shared_ptr Transports::GetRandomPeer () const + template + std::shared_ptr Transports::GetRandomPeer (Filter filter) const { - if (m_Peers.empty ()) return nullptr; + if (m_Peers.empty()) return nullptr; + bool found = false; i2p::data::IdentHash ident; { + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); std::unique_lock l(m_PeersMutex); + inds[0] %= m_Peers.size (); auto it = m_Peers.begin (); - std::advance (it, rand () % m_Peers.size ()); - if (it == m_Peers.end () || it->second.router) return nullptr; // not connected - ident = it->first; + std::advance (it, inds[0]); + // try random peer + if (it != m_Peers.end () && filter (it->second)) + { + ident = it->first; + found = true; + } + else + { + // try some peers around + auto it1 = m_Peers.begin (); + if (inds[0]) + { + // before + inds[1] %= inds[0]; + std::advance (it1, (inds[1] + inds[0])/2); + } + else + it1 = it; + auto it2 = it; + if (inds[0] < m_Peers.size () - 1) + { + // after + inds[2] %= (m_Peers.size () - 1 - inds[0]); inds[2] /= 2; + std::advance (it2, inds[2]); + } + // it1 - from, it2 - to + it = it1; + while (it != it2 && it != m_Peers.end ()) + { + if (filter (it->second)) + { + ident = it->first; + found = true; + break; + } + it++; + } + if (!found) + { + // still not found, try from the beginning + it = m_Peers.begin (); + while (it != it1 && it != m_Peers.end ()) + { + if (filter (it->second)) + { + ident = it->first; + found = true; + break; + } + it++; + } + if (!found) + { + // still not found, try to the beginning + it = it2; + while (it != m_Peers.end ()) + { + if (filter (it->second)) + { + ident = it->first; + found = true; + break; + } + it++; + } + } + } + } } - return i2p::data::netdb.FindRouter (ident); + return found ? i2p::data::netdb.FindRouter (ident) : nullptr; + } + + std::shared_ptr Transports::GetRandomPeer (bool isHighBandwidth) const + { + return GetRandomPeer ( + [isHighBandwidth](const Peer& peer)->bool + { + // connected and not overloaded + return !peer.router && !peer.sessions.empty () && + peer.sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && + (!isHighBandwidth || peer.isHighBandwidth); + }); } void Transports::RestrictRoutesToFamilies(const std::set& families) diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 4723b752..dc97a952 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -63,6 +63,7 @@ namespace transport const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds + const size_t PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE = 25; struct Peer { int numAttempts; @@ -71,11 +72,15 @@ namespace transport uint64_t creationTime, nextRouterInfoUpdateTime; std::vector > delayedMessages; std::vector priority; + bool isHighBandwidth; Peer (std::shared_ptr r, uint64_t ts): numAttempts (0), router (r), creationTime (ts), - nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL) + nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL), + isHighBandwidth (false) { + if (router) + isHighBandwidth = router->IsHighBandwidth (); } void Done () @@ -83,6 +88,13 @@ namespace transport for (auto& it: sessions) it->Done (); } + + void SetRouter (std::shared_ptr r) + { + router = r; + if (router) + isHighBandwidth = router->IsHighBandwidth (); + } }; const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds @@ -130,7 +142,7 @@ namespace transport bool IsBandwidthExceeded () const; bool IsTransitBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; - std::shared_ptr GetRandomPeer () const; + std::shared_ptr GetRandomPeer (bool isHighBandwidth) const; /** get a trusted first hop for restricted routes */ std::shared_ptr GetRestrictedPeer() const; @@ -162,6 +174,9 @@ namespace transport void DetectExternalIP (); + template + std::shared_ptr GetRandomPeer (Filter filter) const; + private: volatile bool m_IsOnline; diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index b578f6c1..372c3ff9 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -332,7 +332,8 @@ namespace tunnel Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), - m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) + m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal avarage + m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0) { } @@ -433,12 +434,16 @@ namespace tunnel } } - void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) + bool Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); else + { LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); + return false; + } + return true; } void Tunnels::Start () @@ -472,6 +477,7 @@ namespace tunnel auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { + int numMsgs = 0; uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; do @@ -515,11 +521,12 @@ namespace tunnel LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } - msg = m_Queue.Get (); + msg = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr; if (msg) { prevTunnelID = tunnelID; prevTunnel = tunnel; + numMsgs++; } else if (tunnel) tunnel->FlushTunnelDataMsgs (); @@ -530,17 +537,20 @@ namespace tunnel if (i2p::transport::transports.IsOnline()) { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= 15) // manage tunnels every 15 seconds + if (ts - lastTs >= TUNNEL_MANAGE_INTERVAL || // manage tunnels every 15 seconds + ts + TUNNEL_MANAGE_INTERVAL < lastTs) { - ManageTunnels (); + ManageTunnels (ts); lastTs = ts; } - if (ts - lastPoolsTs >= 5) // manage pools every 5 seconds + if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL || // manage pools every 5 secondsts + ts + TUNNEL_POOLS_MANAGE_INTERVAL < lastPoolsTs) { ManageTunnelPools (ts); lastPoolsTs = ts; } - if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes + if (ts - lastMemoryPoolTs >= TUNNEL_MEMORY_POOL_MANAGE_INTERVAL || + ts + TUNNEL_MEMORY_POOL_MANAGE_INTERVAL < lastMemoryPoolTs) // manage memory pool every 2 minutes { m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt (); m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); @@ -582,32 +592,32 @@ namespace tunnel tunnel->SendTunnelDataMsg (msg); } - void Tunnels::ManageTunnels () + void Tunnels::ManageTunnels (uint64_t ts) { - ManagePendingTunnels (); - ManageInboundTunnels (); - ManageOutboundTunnels (); - ManageTransitTunnels (); + ManagePendingTunnels (ts); + ManageInboundTunnels (ts); + ManageOutboundTunnels (ts); + ManageTransitTunnels (ts); } - void Tunnels::ManagePendingTunnels () + void Tunnels::ManagePendingTunnels (uint64_t ts) { - ManagePendingTunnels (m_PendingInboundTunnels); - ManagePendingTunnels (m_PendingOutboundTunnels); + ManagePendingTunnels (m_PendingInboundTunnels, ts); + ManagePendingTunnels (m_PendingOutboundTunnels, ts); } template - void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) + void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts) { // check pending tunnel. delete failed or timeout - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) { case eTunnelStatePending: - if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) + if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT || + ts + TUNNEL_CREATION_TIMEOUT < tunnel->GetCreationTime ()) { LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " timeout, deleted"); // update stats @@ -628,7 +638,7 @@ namespace tunnel } // delete it = pendingTunnels.erase (it); - m_NumFailedTunnelCreations++; + FailedTunnelCreation(); } else ++it; @@ -636,7 +646,7 @@ namespace tunnel case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); - m_NumFailedTunnelCreations++; + FailedTunnelCreation(); break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed @@ -645,46 +655,43 @@ namespace tunnel default: // success it = pendingTunnels.erase (it); - m_NumSuccesiveTunnelCreations++; + SuccesiveTunnelCreation(); } } } - void Tunnels::ManageOutboundTunnels () + void Tunnels::ManageOutboundTunnels (uint64_t ts) { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { - for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + // we don't have outbound tunnels in m_Tunnels + it = m_OutboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - // we don't have outbound tunnels in m_Tunnels - it = m_OutboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + auto pool = tunnel->GetTunnelPool (); + // let it die if the tunnel pool has been reconfigured and this is old + if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool has been reconfigured and this is old - if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateOutboundTunnel (tunnel); - } + tunnel->SetRecreated (true); + pool->RecreateOutboundTunnel (tunnel); } - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); } - ++it; + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); } + ++it; } } @@ -704,44 +711,42 @@ namespace tunnel } } - void Tunnels::ManageInboundTunnels () + void Tunnels::ManageInboundTunnels (uint64_t ts) { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { - for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { - auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + m_Tunnels.erase (tunnel->GetTunnelID ()); + it = m_InboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - m_Tunnels.erase (tunnel->GetTunnelID ()); - it = m_InboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + auto pool = tunnel->GetTunnelPool (); + // let it die if the tunnel pool was reconfigured and has different number of hops + if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool was reconfigured and has different number of hops - if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateInboundTunnel (tunnel); - } + tunnel->SetRecreated (true); + pool->RecreateInboundTunnel (tunnel); } - - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); - else // we don't need to cleanup expiring tunnels - tunnel->Cleanup (); } - it++; + + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); + else // we don't need to cleanup expiring tunnels + tunnel->Cleanup (); } + it++; } } @@ -780,13 +785,13 @@ namespace tunnel } } - void Tunnels::ManageTransitTunnels () + void Tunnels::ManageTransitTunnels (uint64_t ts) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); m_Tunnels.erase (tunnel->GetTunnelID ()); diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 503b7f9c..b690d1eb 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -40,10 +40,17 @@ namespace tunnel const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message const int MAX_NUM_RECORDS = 8; const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds + const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt + const int TUNNEL_MANAGE_INTERVAL = 15; // in seconds + const int TUNNEL_POOLS_MANAGE_INTERVAL = 5; // in seconds + const int TUNNEL_MEMORY_POOL_MANAGE_INTERVAL = 120; // in seconds const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12 const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6 + const double TCSR_SMOOTHING_CONSTANT = 0.0005; // smoothing constant in exponentially weighted moving average + const double TCSR_START_VALUE = 0.1; // start value of tunnel creation success rate + enum TunnelState { eTunnelStatePending, @@ -206,7 +213,7 @@ namespace tunnel std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); - void AddTransitTunnel (std::shared_ptr tunnel); + bool AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); @@ -234,18 +241,36 @@ namespace tunnel void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); void Run (); - void ManageTunnels (); - void ManageOutboundTunnels (); - void ManageInboundTunnels (); - void ManageTransitTunnels (); - void ManagePendingTunnels (); + void ManageTunnels (uint64_t ts); + void ManageOutboundTunnels (uint64_t ts); + void ManageInboundTunnels (uint64_t ts); + void ManageTransitTunnels (uint64_t ts); + void ManagePendingTunnels (uint64_t ts); template - void ManagePendingTunnels (PendingTunnels& pendingTunnels); + void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); void ManageTunnelPools (uint64_t ts); std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); + // Calculating of tunnel creation success rate + void SuccesiveTunnelCreation() + { + // total TCSR + m_TotalNumSuccesiveTunnelCreations++; + // A modified version of the EWMA algorithm, where alpha is increased at the beginning to accelerate similarity + double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; + m_TunnelCreationSuccessRate = alpha * 1 + (1 - alpha) * m_TunnelCreationSuccessRate; + + } + void FailedTunnelCreation() + { + m_TotalNumFailedTunnelCreations++; + + double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; + m_TunnelCreationSuccessRate = alpha * 0 + (1 - alpha) * m_TunnelCreationSuccessRate; + } + private: bool m_IsRunning; @@ -262,9 +287,10 @@ namespace tunnel i2p::util::Queue > m_Queue; i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; - - // some stats - int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; + // count of tunnels for total TCSR algorithm + int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations; + double m_TunnelCreationSuccessRate; + int m_TunnelCreationAttemptsNum; public: @@ -278,10 +304,11 @@ namespace tunnel size_t CountOutboundTunnels() const; int GetQueueSize () { return m_Queue.GetSize (); }; - int GetTunnelCreationSuccessRate () const // in percents + int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents + int GetTotalTunnelCreationSuccessRate () const // in percents { - int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; - return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; + int totalNum = m_TotalNumSuccesiveTunnelCreations + m_TotalNumFailedTunnelCreations; + return totalNum ? m_TotalNumSuccesiveTunnelCreations*100/totalNum : 0; } }; diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index 8d0edff1..d58ec2d7 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -41,7 +41,7 @@ namespace tunnel { public: - TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): + TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent): m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index b87ca048..bafc1c2d 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -282,8 +282,13 @@ namespace tunnel for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } - for (int i = num; i < m_NumOutboundTunnels; i++) - CreateOutboundTunnel (); + num = m_NumOutboundTunnels - num; + if (num > 0) + { + if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS; + for (int i = 0; i < num; i++) + CreateOutboundTunnel (); + } num = 0; { @@ -300,8 +305,13 @@ namespace tunnel if (num >= m_NumInboundTunnels) break; } } - for (int i = num; i < m_NumInboundTunnels; i++) - CreateInboundTunnel (); + num = m_NumInboundTunnels - num; + if (num > 0) + { + if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS; + for (int i = 0; i < num; i++) + CreateInboundTunnel (); + } if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately @@ -470,7 +480,7 @@ namespace tunnel return hop; } - bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) + bool TunnelPool::StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); @@ -486,7 +496,7 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - auto r = i2p::transport::transports.GetRandomPeer (); + auto r = i2p::transport::transports.GetRandomPeer (!IsExploratory ()); if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { @@ -502,7 +512,7 @@ namespace tunnel if (!hop && !i) // if no suitable peer found for first hop, try already connected { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); - hop = i2p::transport::transports.GetRandomPeer (); + hop = i2p::transport::transports.GetRandomPeer (false); if (hop && !hop->IsECIES ()) hop = nullptr; } if (!hop) diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index d8c60d69..7d952559 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,6 +30,7 @@ namespace tunnel const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds const int TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY = 16; const int TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY = 16; + const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 2; class Tunnel; class InboundTunnel; @@ -53,12 +54,9 @@ namespace tunnel virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; }; - - typedef std::function(std::shared_ptr, bool)> SelectHopFunc; - bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); - class TunnelPool: public std::enable_shared_from_this // per local destination { + typedef std::function(std::shared_ptr, bool)> SelectHopFunc; public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, @@ -115,6 +113,7 @@ namespace tunnel // for overriding tunnel peer selection std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse) const; + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); private: diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index f0e9a7c6..369f999e 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "util.h" @@ -487,6 +488,22 @@ namespace net return IsYggdrasilAddress (addr.to_v6 ().to_bytes ().data ()); } + bool IsPortInReservedRange (const uint16_t port) noexcept + { + // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers (Feb. 3, 2023) + Tor browser (9150) + static const std::unordered_set reservedPorts{ + 9119,9150,9306,9312,9389,9418,9535,9536,9695, + 9800,9899,10000,10050,10051,10110,10212, + 10933,11001,11112,11235,11371,12222,12223, + 13075,13400,13720,13721,13724,13782,13783, + 13785,13786,15345,17224,17225,17500,18104, + 19788,19812,19813,19814,19999,20000,24465, + 24554,26000,27000,27001,27002,27003,27004, + 27005,27006,27007,27008,27009,28000}; + + return (reservedPorts.find(port) != reservedPorts.end()); + } + boost::asio::ip::address_v6 GetYggdrasilAddress () { #if defined(_WIN32) diff --git a/libi2pd/util.h b/libi2pd/util.h index 248c2bad..e2037212 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -224,6 +224,7 @@ namespace util bool IsLocalAddress (const boost::asio::ip::address& addr); bool IsInReservedRange (const boost::asio::ip::address& host); bool IsYggdrasilAddress (const boost::asio::ip::address& addr); + bool IsPortInReservedRange (const uint16_t port) noexcept; } } } diff --git a/libi2pd/version.h b/libi2pd/version.h index 94d14403..b7de21a8 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,8 +16,8 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 44 -#define I2PD_VERSION_MICRO 0 +#define I2PD_VERSION_MINOR 46 +#define I2PD_VERSION_MICRO 1 #define I2PD_VERSION_PATCH 0 #ifdef GITVER #define I2PD_VERSION GITVER @@ -31,7 +31,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 56 +#define I2P_VERSION_MICRO 57 #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 07f91d2e..652dfb8d 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -833,7 +833,7 @@ namespace client } else m_Ident = addr->identHash; - // save url parts for later use + // save url parts for later use std::string dest_host = url.host; int dest_port = url.port ? url.port : 80; // try to create stream to addressbook site @@ -842,13 +842,13 @@ namespace client { LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; - } - if (m_Etag.empty() && m_LastModified.empty()) + } + if (m_Etag.empty() && m_LastModified.empty()) { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); LogPrint (eLogDebug, "Addressbook: Loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } - // create http request & send it + // create http request & send it i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); @@ -859,7 +859,7 @@ namespace client req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); - // convert url to relative + // convert url to relative url.schema = ""; url.host = ""; req.uri = url.to_string(); @@ -878,7 +878,7 @@ namespace client { response.append ((char *)recv_buf, received); if (!stream->IsOpen ()) end = true; - } + } else if (!stream->IsOpen ()) end = true; else @@ -886,12 +886,12 @@ namespace client LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; - } + } } // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); - // parse response + // parse response i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) @@ -904,7 +904,7 @@ namespace client LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } - // assert: res_head_len > 0 + // assert: res_head_len > 0 response.erase(0, res_head_len); if (res.code == 304) { @@ -927,7 +927,7 @@ namespace client LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } - // assert: res.code == 200 + // assert: res.code == 200 auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("Last-Modified"); diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 451c9c74..3e5ab595 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -238,9 +238,33 @@ namespace proxy { std::string value = params["i2paddresshelper"]; len += value.length(); b64 = i2p::http::UrlDecode(value); + // if we need update exists, request formed with update param - if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } - if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option + if (params["update"] == "true") + { + len += std::strlen("&update=true"); + confirm = true; + } + + // if helper is not only one query option and it placed after user's query + if (pos != 0 && url.query[pos-1] == '&') + { + pos--; + len++; + } + // if helper is not only one query option and it placed before user's query + else if (pos == 0 && url.query.length () > len && url.query[len] == '&') + { + // we don't touch the '?' but remove the trailing '&' + len++; + } + else + { + // there is no more query options, resetting hasquery flag + url.hasquery = false; + } + + // reset hasquery flag and remove addresshelper from URL url.query.replace(pos, len, ""); return true; } @@ -252,7 +276,6 @@ namespace proxy { req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("DNT"); // Useless DoNotTrack flag - req.RemoveHeader("X-Requested-With"); // Android Webview send this with the value set to the application ID req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); @@ -260,6 +283,18 @@ namespace proxy { /* replace headers */ req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + /** + * i2pd PR #1816: + * Android Webview send this with the value set to the application ID, so we drop it, + * but only if it does not belong to an AJAX request (*HttpRequest, like XMLHttpRequest). + */ + if(req.GetHeader("X-Requested-With") != "") { + auto h = req.GetHeader ("X-Requested-With"); + auto x = h.find("HttpRequest"); + if (x == std::string::npos) // not found + req.RemoveHeader("X-Requested-With"); + } + /** * according to i2p ticket #1862: * leave Referer if requested URL with same schema, host and port, @@ -310,7 +345,7 @@ namespace proxy { if (!m_Addresshelper) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); - GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); + GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); return true; } @@ -322,24 +357,51 @@ namespace proxy { } else if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) { + const std::string referer_raw = m_ClientRequest.GetHeader("Referer"); + i2p::http::URL referer_url; + if (!referer_raw.empty ()) + { + referer_url.parse (referer_raw); + } + if (m_RequestURL.host != referer_url.host) + { + if (m_Confirm) // Attempt to forced overwriting by link with "&update=true" from harmful URL + { + LogPrint (eLogWarning, "HTTPProxy: Address update from addresshelper rejected for ", m_RequestURL.host, " (referer is ", m_RequestURL.host.empty() ? "empty" : "harmful", ")"); + std::string full_url = m_RequestURL.to_string(); + std::stringstream ss; + ss << tr("Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", + m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); + GenericProxyInfo(tr("Addresshelper forced update rejected"), ss.str()); + } + else // Preventing unauthorized additions to the address book + { + LogPrint (eLogDebug, "HTTPProxy: Adding address from addresshelper for ", m_RequestURL.host, " (generate refer-base page)"); + std::string full_url = m_RequestURL.to_string(); + std::stringstream ss; + ss << tr("To add host %s in router's addressbook, click here: Continue.", + m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); + GenericProxyInfo(tr("Addresshelper request"), ss.str()); + } + return true; /* request processed */ + } + i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; - ss << tr("Click here to proceed:") << " " << tr("Continue") << "."; - GenericProxyInfo(tr("Addresshelper found"), ss.str()); + ss << tr("Host %s added to router's addressbook from helper. Click here to proceed: Continue.", + m_RequestURL.host.c_str(), full_url.c_str()); + GenericProxyInfo(tr("Addresshelper adding"), ss.str()); return true; /* request processed */ } else { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; - ss << tr(/* tr: The "record" means addressbook's record. That message appears when domain was already added to addressbook, but helper link is opened for it. */ "Click here to update record:" ); - ss << " " << tr("Continue") << "."; - GenericProxyInfo(tr("Addresshelper found"), ss.str()); + ss << tr("Host %s is already in router's addressbook. Click here to update record: Continue.", + m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); + GenericProxyInfo(tr("Addresshelper update"), ss.str()); return true; /* request processed */ } } @@ -352,7 +414,7 @@ namespace proxy { auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError(tr("Invalid request"), tr("invalid request uri")); + GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); return true; } else @@ -412,10 +474,10 @@ namespace proxy { if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else - GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings")); + GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); - std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled"); + std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str()); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; @@ -504,13 +566,13 @@ namespace proxy { else { /* unknown type, complain */ - GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); + GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string()); } } void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) { - if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); + if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); else handler(*it); } @@ -518,7 +580,7 @@ namespace proxy { { if(!ec) { if(m_RequestURL.host.size() > 255) { - GenericProxyError(tr("hostname too long"), m_RequestURL.host); + GenericProxyError(tr("Hostname is too long"), m_RequestURL.host); return; } uint16_t port = m_RequestURL.port; @@ -545,13 +607,13 @@ namespace proxy { reqsize += host.size(); m_socks_buf[++reqsize] = 0; boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); - } else GenericProxyError(tr("cannot connect to upstream socks proxy"), ec.message()); + } else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) { LogPrint(eLogDebug, "HTTPProxy: Upstream SOCKS handshake sent"); - if(ec) GenericProxyError(tr("Cannot negotiate with socks proxy"), ec.message()); + if(ec) GenericProxyError(tr("Cannot negotiate with SOCKS proxy"), ec.message()); else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); } @@ -593,7 +655,7 @@ namespace proxy { } else { - GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); + GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); } } @@ -604,7 +666,7 @@ namespace proxy { m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError(tr("socks proxy error"), ec.message()); + if(ec) GenericProxyError(tr("SOCKS proxy error"), ec.message()); else HandoverToUpstreamProxy(); }); } else { @@ -612,7 +674,7 @@ namespace proxy { LogPrint(eLogDebug, "HTTPProxy: Send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message()); + if(ec) GenericProxyError(tr("Failed to send request to upstream"), ec.message()); else HandoverToUpstreamProxy(); }); } @@ -630,18 +692,18 @@ namespace proxy { ss << "error code: "; ss << (int) m_socks_buf[1]; std::string msg = ss.str(); - GenericProxyError(tr("socks proxy error"), msg); + GenericProxyError(tr("SOCKS proxy error"), msg); } } - else GenericProxyError(tr("No Reply From socks proxy"), ec.message()); + else GenericProxyError(tr("No reply from SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { LogPrint(eLogDebug, "HTTPProxy: Connected to http upstream"); - GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented")); - } else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message()); + GenericProxyError(tr("Cannot connect"), tr("HTTP out proxy not implemented")); + } else GenericProxyError(tr("Cannot connect to upstream HTTP proxy"), ec.message()); } /* will be called after some data received from client */ diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index ce800ecc..1e2e8275 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -72,7 +72,7 @@ namespace client bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); - if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, + if(!pool || !pool->StandardSelectPeers(path, hops, inbound, std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, std::placeholders::_2))) return false; // more here for outbound tunnels diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 8b991802..93df59fb 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -440,18 +440,23 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - auto session = m_Owner.FindSession(m_ID); - if(session) + if (m_Socket.is_open ()) { - if (session->GetLocalDestination ()->IsReady ()) - SendSessionCreateReplyOk (); - else + auto session = m_Owner.FindSession(m_ID); + if(session) { - m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); - m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, - shared_from_this (), std::placeholders::_1)); + if (session->GetLocalDestination ()->IsReady ()) + SendSessionCreateReplyOk (); + else + { + m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); + m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, + shared_from_this (), std::placeholders::_1)); + } } } + else + Terminate ("SAM: session socket closed"); } } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 88990d7c..b6ac23a6 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,7 +30,7 @@ namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds - const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds + const int SAM_SESSION_READINESS_CHECK_INTERVAL = 3; // in seconds const char SAM_HANDSHAKE[] = "HELLO VERSION"; const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n"; const char SAM_HANDSHAKE_NOVERSION[] = "HELLO REPLY RESULT=NOVERSION\n"; diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 961ff934..ca87e22a 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -27,7 +27,7 @@ namespace proxy static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse - static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; + //static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp index 9495ddde..1e4b3d7c 100644 --- a/libi2pd_client/UDPTunnel.cpp +++ b/libi2pd_client/UDPTunnel.cpp @@ -372,6 +372,6 @@ namespace client else LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); } - + } } diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h index 749ec4d3..862ce216 100644 --- a/libi2pd_client/UDPTunnel.h +++ b/libi2pd_client/UDPTunnel.h @@ -180,7 +180,7 @@ namespace client bool isUpdated; // transient, used during reload only }; - + } } diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..90457c23 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,12 @@ +/test-http-merge_chunked +/test-http-req +/test-http-res +/test-http-url +/test-http-url_decode +/test-gost +/test-gost-sig +/test-base-64 +/test-x25519 +/test-aeadchacha20poly1305 +/test-blinding +/test-elligator diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..21daadd9 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,116 @@ +enable_testing() +find_package(Check 0.9.10 REQUIRED) +include_directories(${CHECK_INCLUDE_DIRS}) + +# Compiler flags: +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,-undefined,dynamic_lookup") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,--unresolved-symbols=ignore-in-object-files") +endif() + +set(TEST_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +include_directories( + ../libi2pd + ${Boost_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIR} +) + +set(test-http-merge_chunked_SRCS + test-http-merge_chunked.cpp +) + +set(test-http-req_SRCS + test-http-req.cpp +) + +set(test-http-res_SRCS + test-http-res.cpp +) + +set(test-http-url_decode_SRCS + test-http-url_decode.cpp +) + +set(test-http-url_SRCS + test-http-url.cpp +) + +set(test-base-64_SRCS + test-base-64.cpp +) + +set(test-gost_SRCS + test-gost.cpp +) + +set(test-gost-sig_SRCS + test-gost-sig.cpp +) + +set(test-x25519_SRCS + test-x25519.cpp +) + +set(test-aeadchacha20poly1305_SRCS + test-aeadchacha20poly1305.cpp +) + +set(test-blinding_SRCS + test-blinding.cpp +) + +SET(test-elligator_SRCS + test-elligator.cpp +) + +add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS}) +add_executable(test-http-req ${test-http-req_SRCS}) +add_executable(test-http-res ${test-http-res_SRCS}) +add_executable(test-http-url_decode ${test-http-url_decode_SRCS}) +add_executable(test-http-url ${test-http-url_SRCS}) +add_executable(test-base-64 ${test-base-64_SRCS}) +add_executable(test-gost ${test-gost_SRCS}) +add_executable(test-gost-sig ${test-gost-sig_SRCS}) +add_executable(test-x25519 ${test-x25519_SRCS}) +add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) +add_executable(test-blinding ${test-blinding_SRCS}) +add_executable(test-elligator ${test-elligator_SRCS}) + +set(LIBS + libi2pd + ${Boost_LIBRARIES} + OpenSSL::SSL + OpenSSL::Crypto + ZLIB::ZLIB + Threads::Threads + ${CHECK_LDFLAGS} + ${CMAKE_REQUIRED_LIBRARIES} +) + +target_link_libraries(test-http-merge_chunked ${LIBS}) +target_link_libraries(test-http-req ${LIBS}) +target_link_libraries(test-http-res ${LIBS}) +target_link_libraries(test-http-url_decode ${LIBS}) +target_link_libraries(test-http-url ${LIBS}) +target_link_libraries(test-base-64 ${LIBS}) +target_link_libraries(test-gost ${LIBS}) +target_link_libraries(test-gost-sig ${LIBS}) +target_link_libraries(test-x25519 ${LIBS}) +target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) +target_link_libraries(test-blinding ${LIBS}) +target_link_libraries(test-elligator ${LIBS}) + +add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked) +add_test(test-http-req ${TEST_PATH}/test-http-req) +add_test(test-http-res ${TEST_PATH}/test-http-res) +add_test(test-http-url_decode ${TEST_PATH}/test-http-url_decode) +add_test(test-http-url ${TEST_PATH}/test-http-url) +add_test(test-base-64 ${TEST_PATH}/test-base-64) +add_test(test-gost ${TEST_PATH}/test-gost) +add_test(test-gost-sig ${TEST_PATH}/test-gost-sig) +add_test(test-x25519 ${TEST_PATH}/test-x25519) +add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) +add_test(test-blinding ${TEST_PATH}/test-blinding) +add_test(test-elligator ${TEST_PATH}/test-elligator) diff --git a/tests/Makefile b/tests/Makefile index 8eb52fde..9c5711e2 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,36 +1,62 @@ -CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -pthread -Wl,--unresolved-symbols=ignore-in-object-files +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 INCFLAGS += -I../libi2pd -TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator +LIBI2PD = ../libi2pd.a + +TESTS = \ + test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \ + test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator + +ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) + CXXFLAGS += -DWIN32_LEAN_AND_MEAN + LDFLAGS += -mwindows -static + BOOST_SUFFIX = -mt + NEEDED_LDLIBS = -lwsock32 -lws2_32 -lgdi32 -liphlpapi -lole32 +endif + +LDLIBS = \ + -lboost_filesystem$(BOOST_SUFFIX) \ + -lboost_program_options$(BOOST_SUFFIX) \ + -lssl \ + -lcrypto \ + -lz \ + $(NEEDED_LDLIBS) \ + -lpthread + all: $(TESTS) run -test-http-%: ../libi2pd/HTTP.cpp test-http-%.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ +$(LIBI2PD): + @echo "Building libi2pd.a ..." && cd .. && $(MAKE) libi2pd.a -test-base-%: ../libi2pd/Base.cpp test-base-%.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ +test-http-%: test-http-%.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-gost: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp test-gost.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto +test-base-%: test-base-%.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-gost: test-gost.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/Crypto.cpp test-x25519.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-gost-sig: test-gost-sig.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-x25519: test-x25519.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-blinding: test-blinding.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +test-elligator: test-elligator.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) run: $(TESTS) - @for TEST in $(TESTS); do ./$$TEST ; done + @for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done clean: rm -f $(TESTS) diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp index de9f1db2..64a0f358 100644 --- a/tests/test-aeadchacha20poly1305.cpp +++ b/tests/test-aeadchacha20poly1305.cpp @@ -7,28 +7,28 @@ char text[] = "Ladies and Gentlemen of the class of '99: If I could offer you " "only one tip for the future, sunscreen would be it."; // 114 bytes -uint8_t key[32] = +uint8_t key[32] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f }; -uint8_t ad[12] = +uint8_t ad[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; -uint8_t nonce[12] = +uint8_t nonce[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 }; -uint8_t tag[16] = +uint8_t tag[16] = { 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 }; -uint8_t encrypted[114] = +uint8_t encrypted[114] = { 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, @@ -53,7 +53,7 @@ int main () assert (memcmp (buf1, text, 114) == 0); // test encryption of multiple buffers memcpy (buf, text, 114); - std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; + std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); assert (memcmp (buf1, text, 114) == 0); diff --git a/tests/test-blinding.cpp b/tests/test-blinding.cpp index d7c41809..10b72e4f 100644 --- a/tests/test-blinding.cpp +++ b/tests/test-blinding.cpp @@ -13,12 +13,12 @@ void BlindTest (SigningKeyType sigType) { auto keys = PrivateKeys::CreateRandomKeys (sigType); BlindedPublicKey blindedKey (keys.GetPublic ()); - auto timestamp = GetSecondsSinceEpoch (); + auto timestamp = GetSecondsSinceEpoch (); char date[9]; GetDateString (timestamp, date); - uint8_t blindedPriv[32], blindedPub[32]; + uint8_t blindedPriv[32], blindedPub[32]; auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - uint8_t blindedPub1[32]; + uint8_t blindedPub1[32]; blindedKey.GetBlindedKey (date, blindedPub1); // check if public key produced from private blinded key matches blided public key assert (!memcmp (blindedPub, blindedPub1, publicKeyLen)); @@ -26,16 +26,16 @@ void BlindTest (SigningKeyType sigType) std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); uint8_t buf[100], signature[64]; memset (buf, 1, 100); - blindedSigner->Sign (buf, 100, signature); + blindedSigner->Sign (buf, 100, signature); std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (blindedKey.GetBlindedSigType ())); blindedVerifier->SetPublicKey (blindedPub); - assert (blindedVerifier->Verify (buf, 100, signature)); + assert (blindedVerifier->Verify (buf, 100, signature)); } int main () { - // EdDSA test + // EdDSA test BlindTest (SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); - // RedDSA test + // RedDSA test BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); } diff --git a/tests/test-elligator.cpp b/tests/test-elligator.cpp index 48c9e31a..359c71c5 100644 --- a/tests/test-elligator.cpp +++ b/tests/test-elligator.cpp @@ -4,19 +4,19 @@ #include "Elligator.h" -const uint8_t key[32] = +const uint8_t key[32] = { 0x33, 0x95, 0x19, 0x64, 0x00, 0x3c, 0x94, 0x08, 0x78, 0x06, 0x3c, 0xcf, 0xd0, 0x34, 0x8a, 0xf4, 0x21, 0x50, 0xca, 0x16, 0xd2, 0x64, 0x6f, 0x2c, 0x58, 0x56, 0xe8, 0x33, 0x83, 0x77, 0xd8, 0x80 }; -const uint8_t encoded_key[32] = +const uint8_t encoded_key[32] = { 0x28, 0x20, 0xb6, 0xb2, 0x41, 0xe0, 0xf6, 0x8a, 0x6c, 0x4a, 0x7f, 0xee, 0x3d, 0x97, 0x82, 0x28, 0xef, 0x3a, 0xe4, 0x55, 0x33, 0xcd, 0x41, 0x0a, 0xa9, 0x1a, 0x41, 0x53, 0x31, 0xd8, 0x61, 0x2d }; -const uint8_t encoded_key_high_y[32] = +const uint8_t encoded_key_high_y[32] = { 0x3c, 0xfb, 0x87, 0xc4, 0x6c, 0x0b, 0x45, 0x75, 0xca, 0x81, 0x75, 0xe0, 0xed, 0x1c, 0x0a, 0xe9, 0xda, 0xe7, 0x9d, 0xb7, 0x8d, 0xf8, 0x69, 0x97, 0xc4, 0x84, 0x7b, 0x9f, 0x20, 0xb2, 0x77, 0x18 @@ -28,7 +28,7 @@ const uint8_t encoded1[32] = 0x14, 0x50, 0x95, 0x89, 0x28, 0x84, 0x57, 0x99, 0x5a, 0x2b, 0x4c, 0xa3, 0x49, 0x0a, 0xa2, 0x07 }; -const uint8_t key1[32] = +const uint8_t key1[32] = { 0x1e, 0x8a, 0xff, 0xfe, 0xd6, 0xbf, 0x53, 0xfe, 0x27, 0x1a, 0xd5, 0x72, 0x47, 0x32, 0x62, 0xde, 0xd8, 0xfa, 0xec, 0x68, 0xe5, 0xe6, 0x7e, 0xf4, 0x5e, 0xbb, 0x82, 0xee, 0xba, 0x52, 0x60, 0x4f @@ -40,7 +40,7 @@ const uint8_t encoded2[32] = 0xd9, 0x03, 0x65, 0xf2, 0x4a, 0x38, 0xaa, 0x7a, 0xef, 0x1b, 0x97, 0xe2, 0x39, 0x54, 0x10, 0x1b }; -const uint8_t key2[32] = +const uint8_t key2[32] = { 0x79, 0x4f, 0x05, 0xba, 0x3e, 0x3a, 0x72, 0x95, 0x80, 0x22, 0x46, 0x8c, 0x88, 0x98, 0x1e, 0x0b, 0xe5, 0x78, 0x2b, 0xe1, 0xe1, 0x14, 0x5c, 0xe2, 0xc3, 0xc6, 0xfd, 0xe1, 0x6d, 0xed, 0x53, 0x63 @@ -52,7 +52,7 @@ const uint8_t encoded3[32] = 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f }; -const uint8_t key3[32] = +const uint8_t key3[32] = { 0x9c, 0xdb, 0x52, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp index ba587a45..31b6a298 100644 --- a/tests/test-http-merge_chunked.cpp +++ b/tests/test-http-merge_chunked.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index c857ca24..db973a2e 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; @@ -22,13 +22,13 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->headers.size() == 3); - assert(req->headers.count("Host") == 1); - assert(req->headers.count("Accept") == 1); - assert(req->headers.count("User-Agent") == 1); - assert(req->headers.find("Host")->second == "inr.i2p"); - assert(req->headers.find("Accept")->second == "*/*"); - assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); + assert(req->GetNumHeaders () == 3); + assert(req->GetNumHeaders("Host") == 1); + assert(req->GetNumHeaders("Accept") == 1); + assert(req->GetNumHeaders("User-Agent") == 1); + assert(req->GetHeader("Host") == "inr.i2p"); + assert(req->GetHeader("Accept") == "*/*"); + assert(req->GetHeader("User-Agent") == "curl/7.26.0"); delete req; /* test: parsing request without body */ @@ -41,7 +41,7 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->headers.size() == 0); + assert(req->GetNumHeaders () == 0); delete req; /* test: parsing request without body */ @@ -74,13 +74,13 @@ int main() { assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); - assert(req->headers.size() == 3); - assert(req->headers.count("Host") == 1); - assert(req->headers.count("Accept") == 1); - assert(req->headers.count("Accept-Encoding") == 1); - assert(req->headers["Host"] == "stats.i2p"); - assert(req->headers["Accept"] == "*/*"); - assert(req->headers["Accept-Encoding"] == ""); + assert(req->GetNumHeaders () == 3); + assert(req->GetNumHeaders("Host") == 1); + assert(req->GetNumHeaders("Accept") == 1); + assert(req->GetNumHeaders("Accept-Encoding") == 1); + assert(req->GetHeader("Host") == "stats.i2p"); + assert(req->GetHeader("Accept") == "*/*"); + assert(req->GetHeader("Accept-Encoding") == ""); delete req; return 0; diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 896a4403..270f32a3 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index 37e9c45e..a5021c43 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; @@ -15,6 +15,7 @@ int main() { assert(url->host == "127.0.0.1"); assert(url->port == 7070); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "12345"); assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); delete url; @@ -27,6 +28,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "123456"); delete url; @@ -38,6 +40,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name=value"); delete url; @@ -49,6 +52,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name=value1&name=value2"); delete url; @@ -60,6 +64,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name1=value1&name2&name3=value2"); assert(url->parse_query(params)); assert(params.size() == 3); @@ -79,6 +84,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == ""); delete url; @@ -90,6 +96,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); + assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -101,6 +108,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); + assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -112,6 +120,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 84); assert(url->path == "/asdasd/@17"); + assert(url->hasquery == false); assert(url->query == ""); assert(url->frag == "frag"); delete url; diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp index f72b2c50..7f08bbc6 100644 --- a/tests/test-http-url_decode.cpp +++ b/tests/test-http-url_decode.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp index 2ab8ad6a..a1f3f424 100644 --- a/tests/test-x25519.cpp +++ b/tests/test-x25519.cpp @@ -4,21 +4,21 @@ #include "Ed25519.h" -const uint8_t k[32] = +const uint8_t k[32] = { 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4 }; -const uint8_t u[32] = +const uint8_t u[32] = { 0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c }; -uint8_t p[32] = +uint8_t p[32] = { 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, @@ -36,4 +36,3 @@ int main () assert(memcmp (buf, p, 32) == 0); #endif } -