diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..0f57a84a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# 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 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.patch] +trim_trailing_whitespace = false diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 00000000..597dc211 --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -0,0 +1,61 @@ +name: Build Debian packages + +on: + push: + branches: + - '*' + paths: + - .github/workflows/build-deb.yml + - contrib/** + - daemon/** + - debian/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.linux + tags: + - '*' + pull_request: + branches: + - '*' + +jobs: + build: + name: ${{ matrix.dist }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + dist: ['buster', 'bullseye', 'bookworm'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Commit Hash + id: commit + uses: prompt/actions-commit-hash@v3.0.0 + + - name: Build package + uses: jtdor/build-deb-action@v1 + with: + docker-image: debian:${{ matrix.dist }}-slim + buildpackage-opts: --build=binary --no-sign + before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build" + extra-build-deps: devscripts git + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: i2pd_${{ matrix.dist }} + path: debian/artifacts/i2pd_*.deb + + - name: Upload debugging symbols + uses: actions/upload-artifact@v4 + with: + name: i2pd-dbgsym_${{ matrix.dist }} + path: debian/artifacts/i2pd-dbgsym_*.deb diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml index 16953ed0..a4a7566a 100644 --- a/.github/workflows/build-freebsd.yml +++ b/.github/workflows/build-freebsd.yml @@ -1,21 +1,50 @@ name: Build on FreeBSD -on: [push, pull_request] +on: + push: + branches: + - '*' + paths: + - .github/workflows/build-freebsd.yml + - build/CMakeLists.txt + - build/cmake_modules/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.bsd + tags: + - '*' + pull_request: + branches: + - '*' jobs: build: - runs-on: macos-12 + runs-on: ubuntu-latest name: with UPnP + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + - name: Test in FreeBSD id: test - uses: vmactions/freebsd-vm@v0.2.0 + uses: vmactions/freebsd-vm@v1 with: usesh: true mem: 2048 - prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc + sync: rsync + copyback: true + prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc run: | cd build cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . gmake -j2 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: i2pd-freebsd + path: build/i2pd diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index 50672d26..31f0b90d 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -1,20 +1,45 @@ name: Build on OSX -on: [push, pull_request] +on: + push: + branches: + - '*' + paths: + - .github/workflows/build-osx.yml + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.homebrew + tags: + - '*' + pull_request: + branches: + - '*' jobs: build: name: With USE_UPNP=${{ matrix.with_upnp }} runs-on: macOS-latest + strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] + steps: - - uses: actions/checkout@v2 - - name: install packages + - name: Checkout + uses: actions/checkout@v4 + + - name: Install required formulae run: | + find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete brew update brew install boost miniupnpc openssl@1.1 - - name: build application + + - name: List installed formulae + run: brew list + + - name: Build application run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3 diff --git a/.github/workflows/build-windows-msvc.yml-disabled b/.github/workflows/build-windows-msvc.yml-disabled new file mode 100644 index 00000000..922ebd0d --- /dev/null +++ b/.github/workflows/build-windows-msvc.yml-disabled @@ -0,0 +1,80 @@ +name: Build on Windows with MSVC + +on: + push: + branches: + - '*' + paths: + - .github/workflows/build-windows-msvc.yml + - build/CMakeLists.txt + - build/cmake_modules/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Win32/** + tags: + - '*' + pull_request: + branches: + - '*' + +jobs: + build: + name: Build + runs-on: windows-latest + env: + boost_path: ${{ github.workspace }}\boost_1_83_0 + openssl_path: ${{ github.workspace }}\openssl_3_2_1 + + strategy: + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build and install zlib + run: | + powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)" + powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings + set BUILD_TYPE=Debug + ./install_zlib.bat + set BUILD_TYPE=Release + ./install_zlib.bat + del install_zlib.bat + + - name: Install Boost + run: | + powershell -Command "(Start-BitsTransfer -Source https://sourceforge.net/projects/boost/files/boost-binaries/1.83.0/boost_1_83_0-msvc-14.3-64.exe/download -Destination boost_1_83_0-msvc-14.3-64.exe)" + ./boost_1_83_0-msvc-14.3-64.exe /DIR="${{env.boost_path}}" /VERYSILENT /SUPPRESSMSGBOXES /SP- + + - name: Install OpenSSL + run: | + powershell -Command "(Start-BitsTransfer -Source https://slproweb.com/download/Win64OpenSSL-3_2_1.exe -Destination Win64OpenSSL-3_2_1.exe)" + ./Win64OpenSSL-3_2_1.exe /DIR="${{env.openssl_path}}" /TASKS="copytobin" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- + + - name: Make copy of the OpenSSL libraries for CMake + run: | + dir ${{ github.workspace }} + dir ${{env.openssl_path}}\lib\VC + dir ${{env.openssl_path}}\lib\VC\x64\ + dir ${{env.openssl_path}}\lib\VC\x64\MTd\ + xcopy /s /y "${{env.openssl_path}}\lib\VC\x64\MTd" "${{env.openssl_path}}\lib" + + - name: Configure + working-directory: build + run: cmake -DBoost_ROOT="${{env.boost_path}}" -DOPENSSL_ROOT_DIR="${{env.openssl_path}}" -DWITH_STATIC=ON . + + - name: Build + working-directory: build + run: cmake --build . --config Debug -- -m + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: i2pd-msvc + path: build/Debug/i2pd.* + diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index e7752d55..6f10e62b 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,6 +1,25 @@ name: Build on Windows -on: [push, pull_request] +on: + push: + branches: + - '*' + paths: + - .github/workflows/build-windows.yml + - build/CMakeLists.txt + - build/cmake_modules/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Win32/** + - Makefile + - Makefile.mingw + tags: + - '*' + pull_request: + branches: + - '*' defaults: run: @@ -8,68 +27,224 @@ defaults: jobs: build: - name: Building using ${{ matrix.arch }} toolchain + name: ${{ matrix.arch }} runs-on: windows-latest + strategy: - fail-fast: true + fail-fast: false matrix: include: [ - { msystem: UCRT64, arch: ucrt-x86_64, arch_short: x64-ucrt }, - { msystem: MINGW64, arch: x86_64, arch_short: x64 }, - { msystem: MINGW32, arch: i686, arch_short: x86 } + { msystem: UCRT64, arch: ucrt-x86_64, arch_short: x64-ucrt, compiler: gcc }, + { msystem: CLANG64, arch: clang-x86_64, arch_short: x64-clang, compiler: clang }, + { msystem: MINGW64, arch: x86_64, arch_short: x64, compiler: gcc }, + { msystem: MINGW32, arch: i686, arch_short: x86, compiler: gcc } ] + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} - install: base-devel mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc + install: base-devel git mingw-w64-${{ matrix.arch }}-${{ matrix.compiler }} mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc update: true + + - name: Install additional clang packages + if: ${{ matrix.msystem == 'CLANG64' }} + run: pacman --noconfirm -S mingw-w64-${{ matrix.arch }}-gcc-compat + - name: Build application run: | mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes -j3 + - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: i2pd-${{ matrix.arch_short }}.exe path: i2pd.exe - build-xp: - name: Building for Windows XP + + build-cmake: + name: CMake ${{ matrix.arch }} runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + include: [ + { msystem: UCRT64, arch: ucrt-x86_64, arch_short: x64-ucrt, compiler: gcc }, + { msystem: CLANG64, arch: clang-x86_64, arch_short: x64-clang, compiler: clang }, + { msystem: MINGW64, arch: x86_64, arch_short: x64, compiler: gcc }, + { msystem: MINGW32, arch: i686, arch_short: x86, compiler: gcc } + ] + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + install: base-devel git mingw-w64-${{ matrix.arch }}-cmake mingw-w64-${{ matrix.arch }}-ninja mingw-w64-${{ matrix.arch }}-${{ matrix.compiler }} mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc + update: true + + - name: Build application + run: | + cd build + cmake -DWITH_GIT_VERSION=ON -DWITH_STATIC=ON -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . + cmake --build . -- -j3 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: i2pd-cmake-${{ matrix.arch_short }}.exe + path: build/i2pd.exe + + build-xp: + name: XP + runs-on: windows-latest + + strategy: + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: msystem: MINGW32 install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-miniupnpc + cache: true update: true - - name: Build WinXP-capable CRT packages + + - name: Clone MinGW packages repository and revert boost to 1.85.0 run: | git clone https://github.com/msys2/MINGW-packages - pushd MINGW-packages - pushd mingw-w64-headers-git + cd MINGW-packages + git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost + cd mingw-w64-boost + sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD + + # headers + - name: Get headers package version + id: version-headers + run: | + echo "version=$(pacman -Si mingw-w64-i686-headers-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + - name: Cache headers package + uses: actions/cache@v4 + id: cache-headers + with: + path: MINGW-packages/mingw-w64-headers-git/*.zst + key: winxp-headers-${{ steps.version-headers.outputs.version }} + - name: Build WinXP-capable headers package + if: steps.cache-headers.outputs.cache-hit != 'true' + run: | + cd MINGW-packages/mingw-w64-headers-git sed -i 's/0x601/0x501/' PKGBUILD - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm - pacman --noconfirm -U mingw-w64-i686-headers-git-*-any.pkg.tar.zst - popd - pushd mingw-w64-crt-git - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm - pacman --noconfirm -U mingw-w64-i686-crt-git-*-any.pkg.tar.zst - popd - pushd mingw-w64-winpthreads-git - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm - pacman --noconfirm -U mingw-w64-i686-libwinpthread-git-*-any.pkg.tar.zst mingw-w64-i686-winpthreads-git-*-any.pkg.tar.zst - popd - popd + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Install headers package + run: pacman --noconfirm -U MINGW-packages/mingw-w64-headers-git/mingw-w64-i686-*-any.pkg.tar.zst + + # CRT + - name: Get crt package version + id: version-crt + run: | + echo "version=$(pacman -Si mingw-w64-i686-crt-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + - name: Cache crt package + uses: actions/cache@v4 + id: cache-crt + with: + path: MINGW-packages/mingw-w64-crt-git/*.zst + key: winxp-crt-${{ steps.version-crt.outputs.version }} + - name: Build WinXP-capable crt package + if: steps.cache-crt.outputs.cache-hit != 'true' + run: | + cd MINGW-packages/mingw-w64-crt-git + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Install crt package + run: pacman --noconfirm -U MINGW-packages/mingw-w64-crt-git/mingw-w64-i686-*-any.pkg.tar.zst + + # winpthreads + - name: Get winpthreads package version + id: version-winpthreads + run: | + echo "version=$(pacman -Si mingw-w64-i686-winpthreads-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + - name: Cache winpthreads package + uses: actions/cache@v4 + id: cache-winpthreads + with: + path: MINGW-packages/mingw-w64-winpthreads-git/*.zst + key: winxp-winpthreads-${{ steps.version-winpthreads.outputs.version }} + - name: Build WinXP-capable winpthreads package + if: steps.cache-winpthreads.outputs.cache-hit != 'true' + run: | + cd MINGW-packages/mingw-w64-winpthreads-git + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Install winpthreads package + run: pacman --noconfirm -U MINGW-packages/mingw-w64-winpthreads-git/mingw-w64-i686-*-any.pkg.tar.zst + + # OpenSSL + - name: Get openssl package version + id: version-openssl + run: | + echo "version=$(pacman -Si mingw-w64-i686-openssl | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + - name: Cache openssl package + uses: actions/cache@v4 + id: cache-openssl + with: + path: MINGW-packages/mingw-w64-openssl/*.zst + key: winxp-openssl-${{ steps.version-openssl.outputs.version }} + - name: Build WinXP-capable openssl package + if: steps.cache-openssl.outputs.cache-hit != 'true' + run: | + cd MINGW-packages/mingw-w64-openssl + gpg --recv-keys D894E2CE8B3D79F5 + gpg --recv-keys 216094DFD0CB81EF + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Install openssl package + run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst + + # Boost + #- name: Get boost package version + # id: version-boost + # run: | + # echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT + - name: Cache boost package + uses: actions/cache@v4 + id: cache-boost + with: + path: MINGW-packages/mingw-w64-boost/*.zst + key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }} + # Rebuild package if packages above has changed + - name: Build WinXP-capable boost package + if: steps.cache-boost.outputs.cache-hit != 'true' + run: | + cd MINGW-packages/mingw-w64-boost + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck + - name: Remove boost packages + run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs + - name: Install boost package + run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst + + # Building i2pd - name: Build application run: | mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes USE_WINXP_FLAGS=yes -j3 + - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: i2pd-xp.exe path: i2pd.exe diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index affa0b8b..0b65ec9d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,88 +1,67 @@ name: Build on Ubuntu -on: [push, pull_request] +on: + push: + branches: + - '*' + paths: + - .github/workflows/build.yml + - build/CMakeLists.txt + - build/cmake_modules/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.linux + tags: + - '*' + pull_request: + branches: + - '*' jobs: build-make: name: Make with USE_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest + strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + - name: install packages run: | - sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update - sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev + sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application run: make USE_UPNP=${{ matrix.with_upnp }} -j3 + build-cmake: name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest + strategy: fail-fast: true matrix: with_upnp: ['ON', 'OFF'] + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + - name: install packages run: | - sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update - sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev + sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application run: | cd build cmake -DWITH_UPNP=${{ matrix.with_upnp }} . make -j3 - build-deb-stretch: - name: Build package for stretch - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: change debian changelog - run: | - sudo apt-get update - sudo apt-get install devscripts - debchange -v "`git describe --tags`-stretch" -b -M --distribution stretch "trunk build" - - uses: singingwolfboy/build-dpkg-stretch@v1 - id: build - with: - args: --unsigned-source --unsigned-changes -b - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename }} - path: ${{ steps.build.outputs.filename }} - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename-dbgsym }} - path: ${{ steps.build.outputs.filename-dbgsym }} - build-deb-buster: - name: Build package for buster - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: change debian changelog - run: | - sudo apt-get update - sudo apt-get install devscripts - debchange -v "`git describe --tags`-buster" -b -M --distribution buster "trunk build" - - uses: singingwolfboy/build-dpkg-buster@v1 - id: build - with: - args: --unsigned-source --unsigned-changes -b - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename }} - path: ${{ steps.build.outputs.filename }} - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename-dbgsym }} - path: ${{ steps.build.outputs.filename-dbgsym }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1ac5b552..c6d55664 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,67 +4,137 @@ on: push: branches: - openssl + - docker + paths: + - .github/workflows/docker.yml + - contrib/docker/** + - contrib/certificates/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.linux tags: - '*' jobs: - docker: + build: + name: Building container for ${{ matrix.platform }} runs-on: ubuntu-latest permissions: packages: write contents: read + strategy: + matrix: + include: [ + { platform: 'linux/amd64', archname: 'amd64' }, + { platform: 'linux/386', archname: 'i386' }, + { platform: 'linux/arm64', archname: 'arm64' }, + { platform: 'linux/arm/v7', archname: 'armv7' }, + ] + steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Login to GitHub Container registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push trunk container - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: docker/build-push-action@v2 - with: - context: ./contrib/docker - file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 - push: true - tags: | - purplei2p/i2pd:latest - ghcr.io/purplei2p/i2pd:latest + - name: Build container for ${{ matrix.archname }} + uses: docker/build-push-action@v5 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: ${{ matrix.platform }} + push: true + tags: | + purplei2p/i2pd:latest-${{ matrix.archname }} + ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} + provenance: false - - name: Set env - if: ${{ startsWith(github.ref, 'refs/tags/') }} - run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV + push: + name: Pushing merged manifest + runs-on: ubuntu-latest - - name: Build and push release container - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: docker/build-push-action@v2 - with: - context: ./contrib/docker - file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 - push: true - tags: | - purplei2p/i2pd:latest - purplei2p/i2pd:latest-release - purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - ghcr.io/purplei2p/i2pd:latest - ghcr.io/purplei2p/i2pd:latest-release - ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + permissions: + packages: write + contents: read + + needs: build + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + 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: + inputs: purplei2p/i2pd:latest + tags: 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: + inputs: ghcr.io/purplei2p/i2pd:latest + tags: 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 to Docker Hub + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: Noelware/docker-manifest-action@master + with: + inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 + push: true + + - name: Create and push release manifest to GHCR + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: Noelware/docker-manifest-action@master + with: + inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + tags: 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 95bc2c46..23864c0e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,435 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.56.0] - 2025-02-11 +### Added +- Config params for shared local destination +- AddressBook full addresses cache +- Decline transit tunnel to duplicated router +- Recreate tunnels in random order +### Changed +- Exclude disk operations from SSU2 and NTCP2 threads +- Set minimal version for peer test to 0.9.62 +- Send ack requested flag after second SSU2 resend attempt +- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions +- Don't change datagram routing path too often if unidirectional data stream +- Reduce LeaseSet and local RouterInfo publishing confirmation intervals +- Don't delete buffer of connected routers or if an update received +- Smaller RouterInfo request timeout if sent directly +- Persist local RouterInfo in separate thread +- Don't recalculate and process ranges for every SSU2 Ack block +- Reseeds list +### Fixed +- Termination deadlock if SAM session is active +- Race condition at tunnel endpoint +- Inbound tunnel build encryption + +## [2.55.0] - 2024-12-30 +### Added +- Support boost 1.87 +- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams +- Separate thread for tunnel build requests +- Show next peer and connectivity on "Transit tunnels" page +- Tunnel name for local destination thread +- Throttle incoming ECIESx25519 sessions +- Send tunnel data to transport session directly if possible +- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy +- Random tunnel rejection when medium congestion +- Save unreachable router's endpoint to use it next time without introducers +- Recognize symmetric NAT from peer test message 7 +- Resend HolePunch and RelayResponse messages +### Changed +- Removed own implementation of AESNI and always use one from openssl +- Renamed main thread to i2pd-daemon +- Set i2p.streaming.profile=2 for shared local destination +- Reduced LeaseSet and RouterInfo lookup timeouts +- Cleanup ECIES sessions and tags more often +- Check LeaseSet expiration time +- Handle NTCP2 session handshakes in separate thread +- Limit last decline time by 1.5 hours in router's profile +- Don't handle RelayRequest and RelayIntro with same nonce twice +- Increased hole punch expiration interval +- Send peer test message 6 with delay if message 4 was received before message 5 +- Pre-calculate more x25519 keys for transports in runtime +- Don't request LeaseSet for incoming stream +- Terminate incoming stream right away if no remote LeaseSet +- Handle choked, new RTO and window size calculation and resetting algorithm for streams +### Fixed +- Empty string in addressbook subscriptions +- ECIESx25519 sessions without destination +- Missing RouterInfo buffer in NetDb +- Invalid I2PControl certificate +- Routers disappear from NetDb when offline +- Peer test message 6 sent to unknown endpoint +- Race condition with LeaseSet update +- Excessive CPU usage by streams +- Crash on shutdown + +## [2.54.0] - 2024-10-06 +### Added +- Maintain recently connected routers list to avoid false-positive peer test +- Limited connectivity mode(through proxy) +- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers +- Limit stream's inbound speed +- Periodic ack requests in ratchets session +- Set congestion cap G immediately if through proxy +- Show tunnel's routers bandwidth caps in web console +- Handle immediate ack requested flag in SSU2 data packets +- Resend and ack peer test and relay messages +- "senduseragent" HTTP proxy's param to pass through user's User-Agent +### Changed +- Exclude 'N' routers from high-bandwidth routers for client tunnels +- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers +- Removed dependency from boost::date_time and boost::filesystem +- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels +- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP +- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second +- Increase max number of generated tags to 800 per tagset +- Routing path expiration by time instead num attempts +- Save timestamp from epoch instead local time to profiles +- Update introducer's iTag if session to introducer was replaced to new one +- RTT, window size and number of NACKs calculation for streaming +- Don't select same peer for tunnel too often +- Use WinApi for data path UTF-8 conversion for Windows +### Fixed +- Jump link crash if address book is disabled +- Race condition if connect through an introducer +- "Date" header in I2PControl response +- Incomplete response from web console +- AEAD verification with LibreSSL +- Number of generated tags and new keys for follow-on tagsets +- Expired leases in LeaseSet +- Attempts to send HolePunch to 0.0.0.0 +- Incorrect options size in quick ack streaming packet +- Low bandwidth router appeared as first peer in high-bandwidth client tunnel + +## [2.53.1] - 2024-07-29 +### Changed +- I2CP performance improvement +### Fixed +- 100% CPU usage after I2CP/SAM/BOB session termination +- Incorrect client limits returned through I2CP +- Build with LibreSSL + +## [2.53.0] - 2024-07-19 +### Added +- New congestion control algorithm for streaming +- Support miniupnp-2.2.8 +- Limit stream's outbound speed +- Flood to next day closest floodfills before UTC midnight +- Recognize duplicated routers and bypass them +- Random SSU2 resend interval +### Changed +- Set minimal version to 0.9.69 for floodfills and 0.9.58 for client tunnels +- Removed openssl 1.0.2 support +- Move unsent I2NP messages to the new session if replaced +- Use mt19937 RNG instead rand() +- Update router's congestion caps before initial publishing +- Don't try introducer with invalid address +- Select newest introducers to publish +- Don't request relay tag for every session if we have enough introducers +- Update timestamp for non-reachable or hidden router +- Reset streaming routing path if duplicated SYN received +- Update LeaseSet if inbound tunnel failed +- Reseeds list +### Fixed +- Crash when a destination gets terminated +- Expired offline signature upon destination creation +- Race condition between local RouterInfo buffer creation and sending it through the transports + +## [2.52.0] - 2024-05-12 +### Added +- Separate threads for persisting RouterInfos and profiles to disk +- Give preference to address with direct connection +- Exclude addresses with incorrect static or intro key +- Avoid two firewalled routers in the row in tunnel +- Drop unsolicited database search replies +### Changed +- Increase number of hashes to 16 in exploratory lookup reply +- Reduce number of a RouterInfo lookup attempts to 5 +- Reset stream RTO if outbound tunnel was changed +- Insert previously excluded floodfill back when successfully connected +- Increase maximum stream resend attempts to 9 +- Reply to exploratory lookups with only confirmed routers if low tunnel build rate +- Don't accept too old RouterInfo +- Build client tunnels through confirmed routers only if low tunnel build rate +- Manage netDb requests more frequently +- Don't reply with closer than us only floodfills for lookup +### Fixed +- Crash on router lookup if exploratory pool is not ready +- Race condition in excluded peers for next lookup +- Excessive number of lookups for same destination +- Race condition with transport peers during shutdown +- Corrupted RouterInfo files + +## [2.51.0] - 2024-04-06 +### Added +- Non-blocking mode for UDP sockets +- Set SSU2 socket buffer size based on bandwidth limit +- Encrypted tunnel tests +- Support for multiple UDP server tunnels on one destination +- Publish medium congestion indication +- Local domain sockets for SOCKS proxy upstream +- Tunnel status "declined" in web console +- SAM error reply "Incompatible crypto" if remote destination has incompatible crypto +- Reduce amount of traffic by handling local message drops +- Keep SSU2 socket open even if it fails to bind +- Lower SSU2 resend traffic spikes +- Expiration for messages in SSU2 send queue +- Use EWMA for stream RTT estimation +- Request choking delay if too many NACKs in stream +- Allow 0ms latency for tunnel +- Randomize tunnels selection for tests +### Changed +- Upstream SOCKS proxy from SOCKS4 to SOCKS5 +- Transit tunnels limit to 4 bytes. Default value to 10K +- Reply CANT_REACH_PEER if connect to ourselves in SAM +- Don't send already expired I2NP messages +- Use monotonic timer to measure tunnel test latency +- Standard NTCP2 frame doesn't exceed 16K +- Always send request through tunnels in case of restricted routes +- Don't delete connected routers from NetDb +- Send lookup reply directly to reply tunnel gateway if possible +- Reduce unreachable router ban interval to 8 minutes +- Don't request banned routers / don't try to connect to unreachable router +- Consider 'M' routers as low bandwidth +- Limit minimal received SSU2 packet size to 40 bytes +- Bob picks peer test session only if Charlie's address supports peer testing +- Reject peer test msg 2 if peer testing is not supported +- Don't request termination if SSU2 session was not established +- Set maximum SSU2 queue size depending on RTT value +- New streaming RTT calculation algorithm +- Don't double initial RTO for streams when changing tunnels +- Restore failed tunnel if test or data for inbound tunnel received +- Don't fail last remaining tunnel in pool +- Publish LeasetSet again if local destination was not ready or no tunnels +- Make more attempts to pick high bandwidth hop for client tunnel +- Reduced SSU2 session termination timeout to 165 seconds +- Reseeds list +### Fixed +- ECIESx25519 symmetric key tagset early expiration +- Encrypted LeaseSet lookup +- Outbound tunnel build fails if it's endpoint is the same as reply tunnel gateway +- I2PControl RouterManager returns invalid JSON when unknown params are passed +- Mix of data between different UDP sessions on the same server +- TARGET_OS_SIMULATOR check +- Handling of "reservedrange" param +- New NTCP2 session gets teminated upon termination of old one +- New SSU2 session gets teminated upon termination of old one +- Peer test to non-supporting router +- Streaming ackThrough off 1 if number of NACKs exceeds 255 +- Race condition in ECIESx25519 tags table +- Good tunnel becomes failed +- Crash when packet comes to terminated stream +- Stream hangs during LeaseSet update + +## [2.50.2] - 2024-01-06 +###Fixed +- Crash with OpenSSL 3.2.0 +- False positive clock skew detection + +## [2.50.1] - 2023-12-23 +###Fixed +- Support for new EdDSA usage behavior in OpenSSL 3.2.0 + +## [2.50.0] - 2023-12-18 +### Added +- Support of concurrent ACCEPTs on SAM 3.1 +- Haiku OS support +- Low bandwidth and far routers can expire before 1 hour +### Changed +- Don't pick too active peer for first hop +- Try peer test again if status is Unknown +- Send peer tests with random delay +- Reseeds list +### Fixed +- XSS vulnerability in addresshelper +- Publishing NAT64 ipv6 addresses +- Deadlock in AsyncSend callback + +## [2.49.0] - 2023-09-18 +### Added +- Handle SOCK5 authorization with empty user/password +- Drop incoming transport sessions from too old or from future routers +- Memory pool for router profiles +- Allow 0 hops in explicitPeers +### Changed +- Separate network and testing status +- Remove AVX code +- Improve NTCP2 transport session logging +- Select router with ipv4 for tunnel endpoint +- Consider all addresses non-published for U and H routers even if they have host/port +- Don't pick completely unreachable routers for tunnels +- Exclude SSU1 introducers from SSU2 addresses +- Don't create paired inbound tunnel if length is different +- Remove introducer from RouterInfo after 60 minutes +- Reduce SSU2 keep alive interval and add keep alive interval variance +- Don't pick too old sessions for introducer +### Fixed +- Version of the subnegotiation in user/password SOCKS5 response +- Send keepalive for existing session with introducer +- Buffer offset for EVP_EncryptFinal_ex() to include outlen +- Termination block size processing for transport sessions +- Crash if deleted BOB destination was shared between few BOB sessions +- Introducers with zero tag +- Padding for SSU2 path response + +## [2.48.0] - 2023-06-12 +### Added +- Allow user/password authentication method for SOCK5 proxy +- Publish reject all congestion cap 'G' if transit is not accepted +- 'critical' log level +- Print b32 on webconsole destination page +- Webconsole button to drop a remote LeaseSet +- limits.zombies param - minimum percentage of successfully created tunnels for routers cleanup +- Recognize real routers if successfully connected or responded to tunnel build request +### Changed +- Bypass slow transport sessions for first hop selection +- Limit AESNI inline asm to x86/x64 +- Create smaller I2NP packets if possible +- Make router unreachable if AEAD tag verification fails in SessionCreated +- Don't include a router to floodfills list until it's confirmed as real +- Drop LeaseSet store request if not floodfill +- Bypass medium congestion('D') routers for client tunnels +- Publish encrypted RouterInfo through tunnels +- Check if s is valid x25519 public key +- Check if socket is open before sending data in SSU2 +### Fixed +- Webconsole empty page if destination is not found +- i2p.streaming.answerPings param +- Reload tunnels +- Address caps for unspecified ipv6 address +- Incomplete HTTP headers in I2P tunnels +- SSU2 socket network exceptions on Windows +- Use of 'server' type tunnel port as inport (#1936) + +## [2.47.0] - 2023-03-11 +### Added +- Congestion caps +- SAM UDP port parameter +- Support domain addresses for yggdrasil reseeds +### Changed +- DHT for floodfills instead plain list +- Process router's messages in separate thread +- Don't publish non-reachable router +- Send and check target destination in first streaming SYN packet +- Reseeds list +### Fixed +- Memory leak in windows network state detection +- Reseed attempts from invalid address + +## [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 +- Localization to Italian and Spanish +- SSU2 through SOCKS5 UDP proxy +- Reload tunnels through web console +- SSU2 send immediate ack request flag +- SSU2 send and verify path challenge +- Configurable ssu2.mtu4 and ssu2.mtu6 +### Changed +- 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 +- Remove DoNotTrack flag from HTTP Request header +- Skip addresshelper page if destination was not changed +- SSU2 allow different ports from RelayReponse and HolePunch +- SSU2 resend PeerTest msg 1 and msg 2 +- SSU2 Send Retry instead SessionCreated if clock skew detected +### Fixed +- Long HTTP headers for HTTP proxy and HTTP server tunnel +- SSU2 resends and resend limits +- Crash at startup if addressbook is disabled +- NTCP2 ipv6 connection through SOCKS5 proxy +- SSU2 SessionRequest with zero token +- SSU2 MTU less than 1280 +- SSU2 port=1 +- Incorrect addresses from network interfaces +- Definitions for Darwin PPC; do not use pthread_setname_np + ## [2.43.0] - 2022-08-22 ### Added - Complete SSU2 implementation @@ -411,7 +840,7 @@ ### Added - Client auth flag for b33 address ### Changed -- Remove incoming NTCP2 session from pending list when established +- Remove incoming NTCP2 session from pending list when established - Handle errors for NTCP2 SessionConfrimed send ### Fixed - Failure to start on Windows XP @@ -715,7 +1144,7 @@ ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels -- Configurable list of reseed servers and initial addressbook +- Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support diff --git a/LICENSE b/LICENSE index 9a1e4527..f59491f5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2020, The PurpleI2P Project +Copyright (c) 2013-2025, The PurpleI2P Project All rights reserved. diff --git a/Makefile b/Makefile index 1fca8ddc..0d4ca48c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,6 @@ DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk -USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) USE_UPNP := $(or $(USE_UPNP),no) DEBUG := $(or $(DEBUG),yes) @@ -47,6 +46,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) @@ -54,26 +57,34 @@ ifneq (, $(findstring darwin, $(SYS))) else include Makefile.osx endif +else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) + DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp + include Makefile.mingw else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.linux else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd -else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) - DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp - include Makefile.mingw +else ifneq (, $(findstring haiku, $(SYS))) + DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp + include Makefile.haiku +else ifneq (, $(findstring solaris, $(SYS))) + DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp + include Makefile.solaris else # not supported $(error Not supported platform) endif +INCFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) +DEFINES += -DOPENSSL_SUPPRESS_DEPRECATED +NEEDED_CXXFLAGS += -MMD -MP + ifeq ($(USE_GIT_VERSION),yes) GIT_VERSION := $(shell git describe --tags) - NEEDED_CXXFLAGS += -DGITVER=\"$(GIT_VERSION)\" + DEFINES += -DGITVER=$(GIT_VERSION) endif -NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) -DOPENSSL_SUPPRESS_DEPRECATED - LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) @@ -106,17 +117,17 @@ wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## custom FLAGS to work at build-time. obj/%.o: %.cpp | mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) - $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) + $(CXX) $(DEFINES) $(LDFLAGS) -o $@ $^ $(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.bsd b/Makefile.bsd index 39e5651a..1c911802 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -1,12 +1,22 @@ CXX = clang++ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation +DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1 +INCFLAGS = -I/usr/include/ -I/usr/local/include/ +LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib +LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options + ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread +CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2) +ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1 + $(error Compiler too old) +else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19 + NEEDED_CXXFLAGS = -std=c++20 +else + NEEDED_CXXFLAGS = -std=c++17 +endif + diff --git a/Makefile.haiku b/Makefile.haiku new file mode 100644 index 00000000..eb56a207 --- /dev/null +++ b/Makefile.haiku @@ -0,0 +1,14 @@ +ifeq ($(shell $(CXX) -dumpmachine | cut -c 1-4), i586) +CXX = g++-x86 +else +CXX = g++ +endif +CXXFLAGS := -Wall -std=c++20 +INCFLAGS = -I/system/develop/headers +DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE +LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_program_options -lpthread + +ifeq ($(USE_UPNP),yes) + DEFINES += -DUSE_UPNP + LDLIBS += -lminiupnpc +endif diff --git a/Makefile.homebrew b/Makefile.homebrew index 88b2a9e2..706f9811 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -1,41 +1,33 @@ # root directory holding homebrew -BREWROOT = /usr/local +BREWROOT = /opt/homebrew BOOSTROOT = ${BREWROOT}/opt/boost SSLROOT = ${BREWROOT}/opt/openssl@1.1 UPNPROOT = ${BREWROOT}/opt/miniupnpc -CXXFLAGS = ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual -INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include -LDFLAGS = ${LD_DEBUG} -ifndef TRAVIS - CXX = clang++ -endif +CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual +NEEDED_CXXFLAGS ?= -std=c++17 +INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include +LDFLAGS ?= ${LD_DEBUG} +DEFINES += -DMAC_OSX ifeq ($(USE_STATIC),yes) - LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread + LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a +ifeq ($(USE_UPNP),yes) + LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a +endif + LDLIBS += -lpthread -ldl else LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib - LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread + LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread +ifeq ($(USE_UPNP),yes) + LDFLAGS += -L${UPNPROOT}/lib + LDLIBS += -lminiupnpc +endif endif ifeq ($(USE_UPNP),yes) - LDFLAGS += -ldl - CXXFLAGS += -DUSE_UPNP + DEFINES += -DUSE_UPNP INCFLAGS += -I${UPNPROOT}/include - ifeq ($(USE_STATIC),yes) - LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a - else - LDFLAGS += -L${UPNPROOT}/lib - LDLIBS += -lminiupnpc - endif -endif - -# OSX Notes -# http://www.hutsby.net/2011/08/macs-with-aes-ni.html -# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 -# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic -ifeq ($(USE_AESNI),yes) - CXXFLAGS += -D__AES__ -maes endif install: all diff --git a/Makefile.linux b/Makefile.linux index 28334082..4ea39e22 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -9,20 +9,17 @@ LDFLAGS ?= ${LD_DEBUG} ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FDLAGS to work at build-time. -# detect proper flag for c++11 support by compilers +# detect proper flag for c++17 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match $(CXX) 'clang'),5) - NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 - NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 - NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 - NEEDED_CXXFLAGS += -std=c++11 - LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -latomic +else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9 + NEEDED_CXXFLAGS += -std=c++17 + LDLIBS = -lboost_system -lstdc++fs +else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12 + NEEDED_CXXFLAGS += -std=c++17 +else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+ + NEEDED_CXXFLAGS += -std=c++20 else # not supported $(error Compiler too old) endif @@ -34,9 +31,6 @@ ifeq ($(USE_STATIC),yes) # Using 'getaddrinfo' in statically linked applications requires at runtime # the shared libraries from the glibc version used for linking LIBDIR := /usr/lib/$(SYS) - LDLIBS += $(LIBDIR)/libboost_system.a - LDLIBS += $(LIBDIR)/libboost_date_time.a - LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_program_options.a LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a @@ -46,7 +40,7 @@ ifeq ($(USE_UPNP),yes) endif LDLIBS += -lpthread -ldl else - LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread + LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif @@ -54,13 +48,7 @@ endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) - NEEDED_CXXFLAGS += -DUSE_UPNP -endif - -ifeq ($(USE_AESNI),yes) -ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that - NEEDED_CXXFLAGS += -D__AES__ -maes -endif + DEFINES += -DUSE_UPNP endif install: all diff --git a/Makefile.mingw b/Makefile.mingw index a1a861e6..32d60764 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -4,54 +4,47 @@ USE_WIN32_APP := yes WINDRES = windres CXXFLAGS := $(CXX_DEBUG) -fPIC -msse -INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -static +INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32 +LDFLAGS := ${LD_DEBUG} -static -fPIC -msse -NEEDED_CXXFLAGS += -std=c++17 -DWIN32_LEAN_AND_MEAN - -# Boost libraries suffix -BOOST_SUFFIX = -mt +NEEDED_CXXFLAGS += -std=c++20 +DEFINES += -DWIN32_LEAN_AND_MEAN # UPNP Support ifeq ($(USE_UPNP),yes) - CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB + DEFINES += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -lminiupnpc endif +ifeq ($(USE_WINXP_FLAGS), yes) + DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 +endif + LDLIBS += \ - -lboost_system$(BOOST_SUFFIX) \ - -lboost_date_time$(BOOST_SUFFIX) \ - -lboost_filesystem$(BOOST_SUFFIX) \ - -lboost_program_options$(BOOST_SUFFIX) \ - -lssl \ - -lcrypto \ - -lz \ + $(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \ + $(MINGW_PREFIX)/lib/libboost_program_options-mt.a \ + $(MINGW_PREFIX)/lib/libssl.a \ + $(MINGW_PREFIX)/lib/libcrypto.a \ + $(MINGW_PREFIX)/lib/libz.a \ -lwsock32 \ -lws2_32 \ - -lgdi32 \ -liphlpapi \ + -lcrypt32 \ + -lgdi32 \ -lole32 \ -luuid \ -lpthread ifeq ($(USE_WIN32_APP), yes) - NEEDED_CXXFLAGS += -DWIN32_APP + DEFINES += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_WINXP_FLAGS), yes) - NEEDED_CXXFLAGS += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -endif - -ifeq ($(USE_AESNI),yes) - NEEDED_CXXFLAGS += -D__AES__ -maes -endif - ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif obj/%.o : %.rc | mk_obj_dir - $(WINDRES) -i $< -o $@ + $(WINDRES) $(DEFINES) $(INCFLAGS) --preprocessor-arg=-MMD --preprocessor-arg=-MP --preprocessor-arg=-MF$@.d -i $< -o $@ diff --git a/Makefile.osx b/Makefile.osx index 2e52585e..52282307 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,20 +1,20 @@ CXX = clang++ -CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX +CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17 INCFLAGS = -I/usr/local/include +DEFINES := -DMAC_OSX LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDFLAGS += -Wl,-dead_strip LDFLAGS += -Wl,-dead_strip_dylibs -LDFLAGS += -Wl,-bind_at_load ifeq ($(USE_STATIC),yes) - LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread + LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread else - LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread + LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl - CXXFLAGS += -DUSE_UPNP + DEFINES += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else @@ -22,8 +22,8 @@ ifeq ($(USE_UPNP),yes) endif endif -ifeq ($(USE_AESNI),yes) - CXXFLAGS += -D__AES__ -maes -else +OSARCH = $(shell uname -p) + +ifneq ($(OSARCH),powerpc) CXXFLAGS += -msse endif diff --git a/Makefile.solaris b/Makefile.solaris new file mode 100644 index 00000000..77d34114 --- /dev/null +++ b/Makefile.solaris @@ -0,0 +1,9 @@ +CXX = g++ +INCFLAGS = -I/usr/openssl/3/include +CXXFLAGS := -Wall -std=c++20 +LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket + +ifeq ($(USE_UPNP),yes) + DEFINES += -DUSE_UPNP + LDLIBS += -lminiupnpc +endif \ No newline at end of file diff --git a/README.md b/README.md index 6f2d23ec..ce25c8f2 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) @@ -99,13 +99,23 @@ Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](http Donations --------- -BTC: 3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f -LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 -ETH: 0x9e5bac70d20d1079ceaa111127f4fb3bccce379d -DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF -ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ -GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG -XMR: 497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH +**E-Mail**: ```i2porignal at yandex.com``` + +**BTC**: ```3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f``` + +**LTC**: ```LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59``` + +**ETH**: ```0x9e5bac70d20d1079ceaa111127f4fb3bccce379d``` + +**GST**: ```GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG``` + +**DASH**: ```Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF``` + +**ZEC**: ```t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ``` + +**ANC**: ```AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z``` + +**XMR**: ```497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH``` License ------- diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 0badf802..48f65c27 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) @@ -47,7 +47,7 @@ namespace util I2PService service((PSTR)SERVICE_NAME); if (!I2PService::Run(service)) { - LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + LogPrint(eLogCritical, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); return false; } return false; @@ -61,10 +61,10 @@ 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; + if (!i2p::win32::StartWin32App (isDaemon)) return false; #endif bool ret = Daemon_Singleton::start(); if (ret && i2p::log::Logger().GetLogType() == eLogFile) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index 5d394d1a..c9266b08 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -1,36 +1,36 @@ -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END -#endif // APSTUDIO_INVOKED - -MAINICON ICON "mask.ico" -#endif // English (United States) resources - -#ifndef APSTUDIO_INVOKED -#include "Resource.rc2" -#endif // not APSTUDIO_INVOKED - +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END +#endif // APSTUDIO_INVOKED + +MAINICON ICON "mask.ico" +#endif // English (United States) resources + +#ifndef APSTUDIO_INVOKED +#include "Resource.rc2" +#endif // not APSTUDIO_INVOKED + diff --git a/Win32/Resource.rc2 b/Win32/Resource.rc2 index 873d6402..32744584 100644 --- a/Win32/Resource.rc2 +++ b/Win32/Resource.rc2 @@ -2,7 +2,7 @@ #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED -#include "../libi2pd/version.h" +#include "version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH @@ -25,7 +25,7 @@ BEGIN VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME - VALUE "LegalCopyright", "Copyright (C) 2013-2022, The PurpleI2P Project" + VALUE "LegalCopyright", "Copyright (C) 2013-2023, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 377e1076..0e29c517 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,6 +45,7 @@ namespace i2p namespace win32 { DWORD g_GracefulShutdownEndtime = 0; + bool g_isWinService; static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { @@ -144,35 +145,40 @@ namespace win32 s << bytes << " Bytes\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) { switch (status) { case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Test"; break; case eRouterStatusFirewalled: s << "FW"; break; case eRouterStatusUnknown: s << "Unk"; break; case eRouterStatusProxy: s << "Proxy"; break; case eRouterStatusMesh: s << "Mesh"; break; - case eRouterStatusError: - { - s << "Err"; - switch (i2p::context.GetError ()) - { - case eRouterErrorClockSkew: - s << " - Clock skew"; - break; - case eRouterErrorOffline: - s << " - Offline"; - break; - case eRouterErrorSymmetricNAT: - s << " - Symmetric NAT"; - break; - default: ; - } - break; - } default: s << "Unk"; + }; + if (testing) + s << " (Test)"; + if (error != eRouterErrorNone) + { + switch (error) + { + case eRouterErrorClockSkew: + s << " - " << tr("Clock skew"); + break; + case eRouterErrorOffline: + s << " - " << tr("Offline"); + break; + case eRouterErrorSymmetricNAT: + s << " - " << tr("Symmetric NAT"); + break; + case eRouterErrorFullConeNAT: + s << " - " << tr("Full cone NAT"); + break; + case eRouterErrorNoDescriptors: + s << " - " << tr("No Descriptors"); + break; + default: ; + } } } @@ -180,11 +186,11 @@ namespace win32 { s << "\n"; s << "Status: "; - ShowNetworkStatus (s, i2p::context.GetStatus ()); + ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); if (i2p::context.SupportsV6 ()) { s << " / "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); + ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; @@ -307,7 +313,7 @@ namespace win32 } case ID_DATADIR: { - std::string datadir(i2p::fs::GetUTF8DataDir()); + std::string datadir(i2p::fs::GetDataDir()); ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); return 0; } @@ -349,6 +355,7 @@ namespace win32 } } } + [[fallthrough]]; } case WM_TRAYICON: { @@ -418,8 +425,9 @@ namespace win32 return DefWindowProc( hWnd, uMsg, wParam, lParam); } - bool StartWin32App () + bool StartWin32App (bool isWinService) { + g_isWinService = isWinService; if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); @@ -448,7 +456,9 @@ namespace win32 MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } - SubscribeToEvents(); + // COM requires message loop to work, which is not implemented in service mode + if (!g_isWinService) + SubscribeToEvents(); return true; } @@ -468,7 +478,8 @@ namespace win32 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app + else if(!g_isWinService) + UnSubscribeFromEvents(); UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } diff --git a/Win32/Win32App.h b/Win32/Win32App.h index ebe49efd..614de738 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -17,7 +17,7 @@ namespace win32 { extern DWORD g_GracefulShutdownEndtime; - bool StartWin32App (); + bool StartWin32App (bool isWinService); void StopWin32App (); int RunWin32App (); bool GracefulShutdown (); diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp index 5c56711d..4ef768c8 100644 --- a/Win32/Win32NetState.cpp +++ b/Win32/Win32NetState.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ IUnknown *pUnknown = nullptr; INetworkListManager *pNetworkListManager = nullptr; IConnectionPointContainer *pCPContainer = nullptr; IConnectionPoint *pConnectPoint = nullptr; +CNetworkListManagerEvent *pNetEvent = nullptr; DWORD Cookie = 0; void SubscribeToEvents() @@ -29,7 +30,11 @@ void SubscribeToEvents() if (SUCCEEDED(Result)) { VARIANT_BOOL IsConnect = VARIANT_FALSE; +#if defined(_MSC_VER) + Result = pNetworkListManager->get_IsConnectedToInternet(&IsConnect); +#else Result = pNetworkListManager->IsConnectedToInternet(&IsConnect); +#endif if (SUCCEEDED(Result)) { i2p::transport::transports.SetOnline (true); LogPrint(eLogInfo, "NetState: Current state: ", IsConnect == VARIANT_TRUE ? "connected" : "disconnected"); @@ -41,8 +46,8 @@ void SubscribeToEvents() Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); if(SUCCEEDED(Result)) { - CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; - Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); + pNetEvent = new CNetworkListManagerEvent; + Result = pConnectPoint->Advise((IUnknown *)pNetEvent, &Cookie); if (SUCCEEDED(Result)) LogPrint(eLogInfo, "NetState: Successfully subscribed to NetworkListManagerEvent messages"); else @@ -59,6 +64,7 @@ void SubscribeToEvents() void UnSubscribeFromEvents() { + LogPrint(eLogInfo, "NetState: Unsubscribing from NetworkListManagerEvents"); try { if (pConnectPoint) { @@ -66,14 +72,25 @@ void UnSubscribeFromEvents() pConnectPoint->Release(); } + if (pNetEvent) + { + pNetEvent->Release(); + } + if (pCPContainer) + { pCPContainer->Release(); + } if (pNetworkListManager) + { pNetworkListManager->Release(); + } if (pUnknown) + { pUnknown->Release(); + } CoUninitialize(); } diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h index c2ddaee4..c1f47a24 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,7 @@ #include "Log.h" #include "Transports.h" -class CNetworkListManagerEvent : public INetworkListManagerEvents +class CNetworkListManagerEvent final : public INetworkListManagerEvents { public: CNetworkListManagerEvent() : m_ref(1) { } @@ -23,17 +23,15 @@ public: HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { - HRESULT Result = S_OK; if (IsEqualIID(riid, IID_IUnknown)) { *ppvObject = (IUnknown *)this; } else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) { *ppvObject = (INetworkListManagerEvents *)this; } else { - Result = E_NOINTERFACE; + return E_NOINTERFACE; } AddRef(); - - return Result; + return S_OK; } ULONG STDMETHODCALLTYPE AddRef() diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index d4ba0d76..057a1edd 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -21,7 +21,7 @@ BOOL I2PService::isService() HWINSTA hWinStation = GetProcessWindowStation(); if (hWinStation != NULL) { - USEROBJECTFLAGS uof = { 0 }; + USEROBJECTFLAGS uof = { FALSE, FALSE, 0 }; if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0)) { bIsService = TRUE; @@ -119,12 +119,12 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service: Start error: ", dwError); + LogPrint(eLogCritical, "Win32Service: Start error: ", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { - LogPrint(eLogError, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_STOPPED); } } @@ -162,7 +162,7 @@ void I2PService::Stop() } catch (...) { - LogPrint(eLogError, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } @@ -191,12 +191,12 @@ void I2PService::Pause() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service: Pause error: ", dwError); + LogPrint(eLogCritical, "Win32Service: Pause error: ", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { - LogPrint(eLogError, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } } @@ -215,12 +215,12 @@ void I2PService::Continue() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service: Continue error: ", dwError); + LogPrint(eLogCritical, "Win32Service: Continue error: ", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { - LogPrint(eLogError, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } } @@ -238,11 +238,11 @@ void I2PService::Shutdown() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service: Shutdown error: ", dwError); + LogPrint(eLogCritical, "Win32Service: Shutdown error: ", dwError); } catch (...) { - LogPrint(eLogError, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); } } diff --git a/Win32/mask.bmp b/Win32/mask.bmp deleted file mode 100644 index cc2aeda7..00000000 Binary files a/Win32/mask.bmp and /dev/null differ diff --git a/build/.gitignore b/build/.gitignore index 872332c5..39b8094c 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,6 +1,13 @@ # Various generated files /CMakeFiles/ +/Testing/ +/tests/ +/.ninja_* +/arch.c +/build.ninja /i2pd +/i2pd.exe +/i2pd.exe.debug /libi2pd.a /libi2pdclient.a /libi2pdlang.a @@ -8,8 +15,13 @@ /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake +/CTestTestfile.cmake /install_manifest.txt -/arch.c +/Makefile # windows build script i2pd*.zip build*.log +# MVS project files +*.vcxproj +*.vcxproj.filters +*.sln diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d35bd526..bc936e18 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,17 +1,34 @@ cmake_minimum_required(VERSION 3.7) -cmake_policy(VERSION 3.7) -project("i2pd") + +if(${CMAKE_VERSION} VERSION_LESS 3.22) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.22) +endif() # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) -# Win32 build with cmake is not supported -if(WIN32 OR MSVC OR MSYS OR MINGW) - message(SEND_ERROR "cmake build for windows is not supported. Please use MSYS2 with makefiles in project root.") -endif() +# paths +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") +set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") + +set(LIBI2PD_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd) +set(LIBI2PD_CLIENT_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd_client) +set(LANG_SRC_DIR ${CMAKE_SOURCE_DIR}/i18n) +set(DAEMON_SRC_DIR ${CMAKE_SOURCE_DIR}/daemon) + +include(Version) +set_version("${LIBI2PD_SRC_DIR}/version.h" PROJECT_VERSION) + +project( + i2pd + VERSION ${PROJECT_VERSION} + HOMEPAGE_URL "https://i2pd.website/" + LANGUAGES C CXX +) # configurable options -option(WITH_AESNI "Use AES-NI instructions set" ON) option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) @@ -20,28 +37,28 @@ 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) -# paths -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") -set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") +IF(BUILD_TESTING) + enable_testing() +ENDIF() -#Handle paths nicely +# Handle paths nicely include(GNUInstallDirs) -# architecture +# Architecture include(TargetArch) target_architecture(ARCHITECTURE) -set(LIBI2PD_SRC_DIR ../libi2pd) -set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) -set(LANG_SRC_DIR ../i18n) -set(DAEMON_SRC_DIR ../daemon) +include(CheckAtomic) + +if(WITH_STATIC) + if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() include_directories(${LIBI2PD_SRC_DIR}) -include_directories(${LIBI2PD_CLIENT_SRC_DIR}) -include_directories(${LANG_SRC_DIR}) -include_directories(${DAEMON_SRC_DIR}) - FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") @@ -52,11 +69,9 @@ if(WITH_LIBRARY) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) -# TODO Make libi2pd available to 3rd party projects via CMake as imported target -# FIXME This pulls stdafx -# install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() +include_directories(${LIBI2PD_CLIENT_SRC_DIR}) FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) add_library(libi2pdclient ${CLIENT_SRC}) set_target_properties(libi2pdclient PROPERTIES PREFIX "") @@ -69,6 +84,7 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() +include_directories(${LANG_SRC_DIR}) FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) add_library(libi2pdlang ${LANG_SRC}) set_target_properties(libi2pdlang PROPERTIES PREFIX "") @@ -81,14 +97,33 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() +include_directories(${DAEMON_SRC_DIR}) + set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" + "${DAEMON_SRC_DIR}/I2PControlHandlers.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) +if(WIN32) + set(WIN32_SRC_DIR ${CMAKE_SOURCE_DIR}/Win32) + include_directories(${WIN32_SRC_DIR}) + + list(APPEND DAEMON_SRC + "${WIN32_SRC_DIR}/DaemonWin32.cpp" + "${WIN32_SRC_DIR}/Win32App.cpp" + "${WIN32_SRC_DIR}/Win32Service.cpp" + "${WIN32_SRC_DIR}/Win32NetState.cpp" + ) + + file(GLOB WIN32_RC ${WIN32_SRC_DIR}/*.rc) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_APP -DWIN32_LEAN_AND_MEAN -DNOMINMAX") + +endif() + if(WITH_UPNP) add_definitions(-DUSE_UPNP) endif() @@ -96,30 +131,30 @@ endif() if(WITH_GIT_VERSION) include(GetGitRevisionDescription) git_describe(GIT_VERSION) - add_definitions(-DGITVER="${GIT_VERSION}") + add_definitions(-DGITVER=${GIT_VERSION}) endif() if(APPLE) add_definitions(-DMAC_OSX) endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") -# TODO: The following is incompatible with static build and enabled hardening for OpenWRT. -# Multiple definitions of __stack_chk_fail(libssp & libc) -set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections") -set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above +if(HAIKU) + add_definitions(-D_DEFAULT_SOURCE -D_GNU_SOURCE) +endif() -# check for c++17 & c++11 support -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) -CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) -if(CXX17_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") -elseif(CXX11_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +if(MSVC) + add_definitions(-DWINVER=0x0600) + add_definitions(-D_WIN32_WINNT=0x0600) else() - message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter -Wno-uninitialized") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") + # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. + # Multiple definitions of __stack_chk_fail(libssp & libc) + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s") + endif() + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections") + set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -149,13 +184,6 @@ if(UNIX) endif() endif() -# Note: AES-NI and AVX is available on x86-based CPU's. -# Here also ARM64 implementation, but currently we don't support it. -if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386")) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") - add_definitions(-D__AES__) -endif() - if(WITH_ADDRSANITIZER) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") @@ -170,16 +198,52 @@ if(WITH_THREADSANITIZER) endif() endif() +if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 + list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") +endif() + +# Use std::atomic instead of GCC builtins on macOS PowerPC: +# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 +# This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. +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) if(WITH_STATIC) + if(NOT MSVC) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + endif() + set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_STATIC_RUNTIME ON) + if(MSVC) + set(Boost_USE_STATIC_RUNTIME ON) + else() + set(Boost_USE_STATIC_RUNTIME OFF) + endif() + + if(MSVC) + set(OPENSSL_MSVC_STATIC_RT ON) + endif() set(OPENSSL_USE_STATIC_LIBS ON) + + set(ZLIB_USE_STATIC_LIBS ON) + if(MSVC) + set(ZLIB_NAMES zlibstatic zlibstat) + else() + set(ZLIB_NAMES libz zlibstatic zlibstat zlib z) + endif() + + if(WITH_UPNP) + set(MINIUPNPC_USE_STATIC_LIBS ON) + add_definitions(-DMINIUPNP_STATICLIB) + endif() + set(BUILD_SHARED_LIBS OFF) + if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") # set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive") @@ -189,17 +253,23 @@ else() # TODO: Consider separate compilation for LIBI2PD_SRC for library. # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) + if(NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + endif() + add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK) + if(WIN32) + set(Boost_USE_STATIC_LIBS OFF) + set(Boost_USE_STATIC_RUNTIME OFF) + endif() endif() -find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) -if(NOT DEFINED Boost_INCLUDE_DIRS) +find_package(Boost REQUIRED COMPONENTS system filesystem program_options) +if(NOT DEFINED Boost_FOUND) message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!") endif() find_package(OpenSSL REQUIRED) -if(NOT DEFINED OPENSSL_INCLUDE_DIR) +if(NOT DEFINED OPENSSL_FOUND) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() @@ -221,11 +291,29 @@ if(ZLIB_FOUND) link_directories(${ZLIB_ROOT}/lib) endif() +# C++ standard to use, based on compiler and version of boost +if(NOT MSVC) +# check for c++20 & c++17 support + include(CheckCXXCompilerFlag) + + if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20 + CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED) + endif() + CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) + + + if(CXX20_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") + elseif(CXX17_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + else() + message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?") + endif() +endif() + # load includes include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) -include(CheckAtomic) - # show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") @@ -233,24 +321,42 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Architecture : ${ARCHITECTURE}") +message(STATUS "Compiler flags : ${CMAKE_CXX_FLAGS}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") -message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") +if(WITH_GIT_VERSION) +message(STATUS " GIT VERSION : ${WITH_GIT_VERSION} (${GIT_VERSION})") +else() message(STATUS " GIT VERSION : ${WITH_GIT_VERSION}") +endif() message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") if(WITH_BINARY) - add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) + if(WIN32) + add_executable("${PROJECT_NAME}" WIN32 ${DAEMON_SRC} ${WIN32_RC}) + else() + add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) + endif() + + if(WIN32) + list(APPEND MINGW_EXTRA "wsock32" "ws2_32" "iphlpapi") + # OpenSSL may require Crypt32 library on MSVC build, which is not added by CMake lesser than 3.21 + if(MSVC AND ${CMAKE_VERSION} VERSION_LESS 3.21) + list(APPEND MINGW_EXTRA "crypt32") + endif() + endif() if(WITH_STATIC) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") + if(NOT MSVC) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") + endif() endif() if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -263,13 +369,24 @@ if(WITH_BINARY) list(REMOVE_AT Boost_LIBRARIES -1) endif() + # synchronization library is incompatible with Windows 7 + if(WIN32) + get_target_property(BOOSTFSLIBS Boost::filesystem INTERFACE_LINK_LIBRARIES) + list(REMOVE_ITEM BOOSTFSLIBS synchronization) + set_target_properties(Boost::filesystem PROPERTIES INTERFACE_LINK_LIBRARIES "${BOOSTFSLIBS}") + endif() + if(WITH_STATIC) 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 ${MINGW_EXTRA} ${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..4954e3e5 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++17") 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}) @@ -20,13 +25,14 @@ endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include 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/GetGitRevisionDescription.cmake b/build/cmake_modules/GetGitRevisionDescription.cmake index 4fbd90db..a08895c6 100644 --- a/build/cmake_modules/GetGitRevisionDescription.cmake +++ b/build/cmake_modules/GetGitRevisionDescription.cmake @@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) # function returns an empty string via _git_dir_var. # # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and -# neither foo nor bar contain a file/directory .git. This wil return +# neither foo nor bar contain a file/directory .git. This will return # C:/bla/.git # function(_git_find_closest_git_dir _start_dir _git_dir_var) diff --git a/build/cmake_modules/TargetArch.cmake b/build/cmake_modules/TargetArch.cmake index d9ebe7c9..e611c4f7 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 @@ -18,7 +18,7 @@ set(archdetect_c_code " || defined(_M_ARM64) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 8) #error cmake_ARCH arm64 - #if defined(__ARM_ARCH_7__) \\ + #elif defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ @@ -61,7 +61,7 @@ set(archdetect_c_code " #else #error cmake_ARCH mips #endif -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) || defined(__POWERPC__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) @@ -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/build/cmake_modules/Version.cmake b/build/cmake_modules/Version.cmake new file mode 100644 index 00000000..cb9551db --- /dev/null +++ b/build/cmake_modules/Version.cmake @@ -0,0 +1,16 @@ +# read version + +function(set_version version_file output_var) + file(READ "${version_file}" version_data) + + string(REGEX MATCH "I2PD_VERSION_MAJOR ([0-9]*)" _ ${version_data}) + set(version_major ${CMAKE_MATCH_1}) + + string(REGEX MATCH "I2PD_VERSION_MINOR ([0-9]*)" _ ${version_data}) + set(version_minor ${CMAKE_MATCH_1}) + + string(REGEX MATCH "I2PD_VERSION_MICRO ([0-9]*)" _ ${version_data}) + set(version_micro ${CMAKE_MATCH_1}) + + set(${output_var} "${version_major}.${version_minor}.${version_micro}" PARENT_SCOPE) +endfunction() diff --git a/build/win_installer.iss b/build/win_installer.iss index cfeff812..a4b67ad2 100644 --- a/build/win_installer.iss +++ b/build/win_installer.iss @@ -24,7 +24,7 @@ ExtraDiskSpaceRequired=15 AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppVerName={#I2Pd_AppName} -AppCopyright=Copyright (c) 2013-2022, The PurpleI2P Project +AppCopyright=Copyright (c) 2013-2024, The PurpleI2P Project AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases diff --git a/contrib/apparmor/docker-i2pd b/contrib/apparmor/docker-i2pd new file mode 100644 index 00000000..8e34298a --- /dev/null +++ b/contrib/apparmor/docker-i2pd @@ -0,0 +1,42 @@ +# _________________________________________ +# / Copy this file to the right location \ +# | then load with: | +# | | +# | apparmor_parser -r -W | +# | /etc/apparmor.d/docker-i2pd | +# | | +# | docker run --security-opt | +# | "apparmor=docker-i2pd" ... | +# | purplei2p/i2pd | +# | | +# \ And "aa-status" to verify it's loaded. / +# ----------------------------------------- +# \ ^__^ +# \ (oo)\_______ +# (__)\ )\/\ +# ||----w | +# || || + +#include + +profile docker-i2pd flags=(attach_disconnected,mediate_deleted) { + #include + #include + #include + + /bin/busybox ix, + /usr/local/bin/i2pd ix, + /entrypoint.sh ixr, + + /i2pd_certificates/** r, + + /home/i2pd/data/** rw, + + /home/i2pd/data/i2pd.pid k, + + deny /home/i2pd/data/i2pd.conf w, + deny /home/i2pd/data/tunnels.conf w, + deny /home/i2pd/data/tunnels.d/** w, + deny /home/i2pd/data/certificates/** w, + deny /home/i2pd/data/i2pd.log r, +} diff --git a/contrib/apparmor/usr.sbin.i2pd b/contrib/apparmor/usr.bin.i2pd similarity index 85% rename from contrib/apparmor/usr.sbin.i2pd rename to contrib/apparmor/usr.bin.i2pd index 1e47cd74..4d370f3c 100644 --- a/contrib/apparmor/usr.sbin.i2pd +++ b/contrib/apparmor/usr.bin.i2pd @@ -4,7 +4,7 @@ # #include -profile i2pd /{usr/,}sbin/i2pd { +profile i2pd /{usr/,}bin/i2pd { #include #include #include @@ -14,12 +14,12 @@ profile i2pd /{usr/,}sbin/i2pd { /var/lib/i2pd/** rw, /var/log/i2pd/i2pd.log w, /{var/,}run/i2pd/i2pd.pid rwk, - /{usr/,}sbin/i2pd mr, + /{usr/,}bin/i2pd mr, @{system_share_dirs}/i2pd/** r, # user homedir (if started not by init.d or systemd) owner @{HOME}/.i2pd/ rw, owner @{HOME}/.i2pd/** rwk, - #include if exists + #include if exists } diff --git a/contrib/certificates/reseed/admin_at_stormycloud.org.crt b/contrib/certificates/reseed/admin_at_stormycloud.org.crt new file mode 100644 index 00000000..ae44521b --- /dev/null +++ b/contrib/certificates/reseed/admin_at_stormycloud.org.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1zCCA7+gAwIBAgIRAMDqFR09Xuj8ZUu+oetSvAEwDQYJKoZIhvcNAQELBQAw +dTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE +ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxHjAcBgNVBAMM +FWFkbWluQHN0b3JteWNsb3VkLm9yZzAeFw0yNDAxMjUxNDE1MzBaFw0zNDAxMjUx +NDE1MzBaMHUxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx +HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR4w +HAYDVQQDDBVhZG1pbkBzdG9ybXljbG91ZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDbGX+GikPzQXr9zvkrhfO9g0l49KHLNQhUKYqd6T+PfnGo +Fm0d3ZZVVQZ045vWgroOXDGGZZWxUIlb2inRaR2DF1TxN3pPYt59RgY9ZQ9+TL7o +isY91krCRygY8EcAmHIjlfZQ9dBVcL7CfyT0MYZA5Efee9+NDHSewTfQP9T2faIE +83Fcyd93a2mIHYjKUbJnojng/wgsy8srbsEuuTok4MIQmDj+B5nz+za2FgI0/ydh +srlMt4aGJF4/DIem9z9d0zBCOkwrmtFIzjNF1mOSA8ES4m5YnKA/y9rZlRidLPGu +prbXhPVnqHeOnHMz2QCw1wbVo504kl0bMqyEz2tVWsO9ep7iZoQs2xkFAEaegYNT +QLUpwVGlyuq3wXXwopFRffOSimGSazICwWI6j+K0pOtgefNJaWrqKYvtkj1SbK2L +LBNUIENz6VnB7KPRckuX6zxC8PpOiBK9BcftfO+xAz/wC6qq3riBPw30KKSym0nC +Zp5KciDn4Phtw9PGq8Bkl8SyWl0jtFnfTB1tzJkisf2qKcNHaFTEe2JW763YLbh/ +AU+8X8evFu40qLgvOgKoyy5DLy6i8zetX+3t9K0Fxt9+Vzzq6lm5V/RS8iIPPn+M +q1/3Z5kD0KQBG9h/Gl8BH+lB71ZxPAOZ3SMu8DJZcxBLVmDWqQPCr5CKnoz0swID +AQABo2IwYDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG +AQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHgYDVR0OBBcEFWFkbWluQHN0b3JteWNs +b3VkLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEARWOJ69vTHMneSXYscha+4Ytjg0RM +faewJNEGj8qy/Qvh9si2bWYNPRK6BlbHFS7pRYBLAnhaeLBGVv1CCR6GUMMe74zQ +UuMeAoWU6qMDmB3GfYoZJh8sIxpwHqyJeTdeccRbZ4sX4F6u3IHPXYiU/AgbYqH7 +pYXQg2lCjXZYaDFAlEf5SlYUDOhhXe5kR8Edhlrsu32/JzA1DQK0JjxKCBp+DQmA +ltdOpQtAg03fHP4ssdj7VvjIDl28iIlATwBvHrdNm7T0tYWn6TWhvxbRqvfTxfaH +MvxnPdIJwNP4/9TyQkwjwHb1h+ucho3CnxI/AxspdOvT1ElMhP6Ce6rcS9pk11Rl +x0ChsqpWwDg7KYpg0qZFSKCTBp4zBq9xoMJ6BQcgMfyl736WbsCzFTEyfifp8beg +NxUa/Qk7w7cuSPGyMIKNOmOR7FLlFbtocy8sXVsUQdqnp/edelufdNe39U9uNtY6 +yoXI9//Tc6NgOwy2Oyia0slZ5qHRkB7e4USXMRzJ3p4q9eCVKjAJs81Utp7O2U+9 +vhbhwWP8CAnNTT1E5WS6EKtfrdqF7wjkV+noPGLDGmrXi01J1fSMAjMfVO+7/LOL +UN+G4ybKWnEhhOO27yidN8Xx6UrCS23DBlPPQAeA74dTsTExiOxf1o1EXzcQiMyO +LAj3/Ojbi1xkWhI= +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt b/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt new file mode 100644 index 00000000..9068afb9 --- /dev/null +++ b/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQIHQPtSoFU+cUpYD8PZaWZjANBgkqhkiG9w0BAQsFADB2 +MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK +ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW +YXJuYXZiaGF0dDI4OEBtYWlsLmkycDAeFw0yMzAxMjUxODUzNDFaFw0zMzAxMjUx +ODUzNDFaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx +HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w +HQYDVQQDDBZhcm5hdmJoYXR0Mjg4QG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAtwG73sC0jYd3fgEzZh0SveAdUd5yD35nINJRrdPSrSwY +n3i1qGe3fNLj877PvUDU+qiHH0fFZfyFkXTaq3TUp1u4YkmvaoPHy6FZlojB08lK +FBm+iJ1hifQ7MFmvIKUGv+cjlN6xSoQ0U6B2QOy6iZnBgFZ/7jbRY4iZOIj7VJtY +aodeHfy0bWe447VJovbkUi7NJPFZQS65LMcAIWcWTxrC0Gj8SmdxL3a5+hxpmmg0 ++KCQvWQDdxAQjsc16sgUCdUc6cWYO4yw9H6fgdq9GJX+LnXR9OB58GsAjjlLlFoI +CZxdARDpoqcIj6AoKIanALf8yfbIyrqqJE47cuaqV9bht5MWKnXbwHplEkT4ZNkh +PnRDia7B5HY3uwbt39CBm264PEWXvWG2sozTWKQqBjmMN2cj/NFDUEqKv6BggMY1 +HcqxWFKRcgKCtRvrmTmfp5l0/ou+OtUaFUg0a6Qhtb93Hj10vK6wZzidBqj0ggzB +eJDI95b89u8JgzRoOBriuMKTc91WTkOvBLkB3dgUbUpx2p8KHjvf/pppBH9u0oxp +qJFFK840DbnJydEvjKezeVe5Ax6YRSRxyEdKzRoWdvKVxb3qBBKMdCKTYEPxHPBu +JMEQVUCXJMti++1KEiQGhcfWvLyT7OewbcIZNk9XWNrxlKcGrTp9AOwaaNC5m1kC +AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr +BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZhcm5hdmJoYXR0Mjg4 +QG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAHiK0ld/1PF9DIhutD660/bzBg +mF2Z76hcBqDZ8tnQai/u/RXYrH9wso9BYyrVsvk3fr6tpGT49Ian0MVpPOxMoTU2 +oBEmQlYrfclQLFsOLmA0y2r1ggXzIrt69jB710Vhwdnz09oOE8rS4E2T5oDD8Wvy +Kony+AarRceqtkOlzyquc42KjzdrbHsosF7G2iGhNI6t+T3BfWJ+Q+d5sj3OIh6e +gSfvHL44E4vZt6dtofRN3MAZ60kNLF5YWyaUo3Snv9Lso1IwIz3AVr5ehv+8sFL/ +KxaXdkZ5Yn2YUX7p1t4VQd+eXVPYjf1befg4PvrwSkylu3Jpee3fllZSKXeSVx9x +jpJiq5vIakqk22pnWb1Vn7xzSW1vtEG7QLjobOr1WrcGiwdv+HKiWcXJXDzKoWXs +h3VEfr51Kap8cIJv+D6lJIG9IcIhiQ6CXWBmtjWJvbdVwFBy1/3Fhaou9liHi+gK +4Yh5a5OGCzc7xjtpGaTmoLEz7NzDNOdd/r840qRDOh70izzmFZd5Gwq4hoVcPJcS +EAySwtgqK0/4d0zDd2Wg9ASJV9DnDf8QuSmHZgZ9Efs47XcWz9TvkWUS1E66AJsN +mmI1NDQ3mv3dv5+WPq+dqqYFsnx3xWL1g5Z3buk0opeuXMzoHwM7UfN8h7Q1M5+t ++XBgkaYA4iEwYKqlCQ== +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt b/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt deleted file mode 100644 index a332805a..00000000 --- a/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFgTCCA2mgAwIBAgIETWAY1DANBgkqhkiG9w0BAQ0FADBxMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBAbWFp -bC5pMnAwHhcNMjExMjEzMTU0MDI3WhcNMzExMjExMTU0MDI3WjBxMQswCQYDVQQG -EwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5v -bnltb3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBA -bWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXnjJ8UQ0f -lHHpfPMiHofBPSuL4sbOJY6fOXwPhSg/h6THh9DS/ZWmJXQ3qRD0glDVtv4/Dr/9 -ldGQ5eltF9iCFXCQlMEy2HjQrBKq0nsl7RpYK12cyMaod0kkzCUk9ITLi9CmHM3Z -gQZcmG8TWjFEpDR+idx/QkQt2pcO4vzWlDit3Vh4ivnbX5jGQHbsVjQEMQWxr+pX -dsS+YQpjZ6RBmrooGTPO8QDOOeYLAn0lCjmffc/kzIH9E/p4/O0rOpyhVYbdxUD1 -5wkqN9l4yrtxmORG/PudnRQQ0r4TUq8vsxfGY0Euo9IbhgXF2Parel1ZhDxB1WZV -VwWtgLIh9jGA1UMa8SYKnEfp8LWNZ3b3mUUnZb3kMrLk6jGYRWNsHmamhd4mC7AZ -qf/8lOkEIw3bPd3YguCDRVcLui5BwIEZmqXg8uoESxfO/sW3pBrN/8M7MkTex9kN -vjitGDDXvenK27qmNgZxbBlX72yTSfys7XTYTLnxZC8AwdAo2Wz9Z6HhGiPonf2h -vZkc9ZxuE0jFIrsbJra4X7iyjXgi4vV4ARNg/9Ft6F4/OIbECgeDcBQqq4TlT2bZ -EfWVrBbqXoj5vNsLigIkd+AyUNwPYEcB5IFSiiOh98pC7BH3pg0m8U5YBjxe1i+9 -EQOOG0Qtx+JigXZHu6bGE0Twy9zy+UzoKQIDAQABoyEwHzAdBgNVHQ4EFgQUGK1b -0DkL6aLalcfBc/Uj/SF08C0wDQYJKoZIhvcNAQENBQADggIBAMpXM82bJDpH1TlH -TvhU3Z7nfZdvEhOQfujaFUYiuNripuEKcFGn948+DvAG0FUN+uNlJoqOVs8D7InD -gWlA9zpqw5Cl5Hij/Wns9QbXuAHJeA23fVUoaM2A6v9ifcIQ1A+rDuRQAo6/64KW -ChTg2e99RBpfGOyqgeh7tLLe0lPPekVpKHFuXabokaKRDuBcVHcUL4tWXe3dcyqa -Ej/PJrrS+nWL0EGZ4q80CEd2LPuDzPxNGCJt/R7ZfadENWajcgcXGceh1QBzozrB -SL/Ya6wF9SrsB7V/r5wX0LM4ZdDaLWbtmUe5Op0h/ZMH25Sa8xAXVz+O9L6sWSoO -FaiYTOvAiyyPz+nsxKa3xYryDHno7eKSt+hGOcaurhxbdZaEFY/CegEc73tCt9xK -e9qF8O/WkDLmixuErw3f5en4IfzGR7p3lJAwW/8WD8C6HS39h/eE7dVZNaWgtQnZ -SgGjgZMTJqTcQ3aZmfuCZefxGFok8w6AIkdbnd1pdMBRjYu8aXgl2hQSB9ZADDE9 -R5d3rXi0PkSFLIvsNjVa5KXrZk/tB0Hpfmepq7CufBqjP/LG9TieRoXzLYUKFF74 -QRwjP+y7AJ+VDUTpY1NV1P+k+2raubU2bOnLF3zL5DtyoyieGPhyeMMvp0fRIxdg -bSl5VHgPXHNM8mcnndMAuzvl7jEK ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/hottuna_at_mail.i2p.crt b/contrib/certificates/reseed/hottuna_at_mail.i2p.crt deleted file mode 100644 index d0ff7c33..00000000 --- a/contrib/certificates/reseed/hottuna_at_mail.i2p.crt +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFxzCCA6+gAwIBAgIQZfqn0yiJL3dGgCjeOeWS6DANBgkqhkiG9w0BAQsFADBw -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ -aG90dHVuYUBtYWlsLmkycDAeFw0xNjExMDkwMzE1MzJaFw0yNjExMDkwMzE1MzJa -MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV -BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD -DBBob3R0dW5hQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA21Bfgcc9VVH4l2u1YvYlTw2OPUyQb16X2IOW0PzdsUO5W78Loueu974BkiKi -84lQZanLr0OwEopdfutGc6gegSLmwaWx5YCG5uwpLOPkDiObfX+nptH6As/B1cn+ -mzejYdVKRnWd7EtHW0iseSsILBK1YbGw4AGpXJ8k18DJSzUt2+spOkpBW6XqectN -8y2JDSTns8yiNxietVeRN/clolDXT9ZwWHkd+QMHTKhgl3Uz1knOffU0L9l4ij4E -oFgPfQo8NL63kLM24hF1hM/At7XvE4iOlObFwPXE+H5EGZpT5+A7Oezepvd/VMzM -tCJ49hM0OlR393tKFONye5GCYeSDJGdPEB6+rBptpRrlch63tG9ktpCRrg2wQWgC -e3aOE1xVRrmwiTZ+jpfsOCbZrrSA/C4Bmp6AfGchyHuDGGkRU/FJwa1YLJe0dkWG -ITLWeh4zeVuAS5mctdv9NQ5wflSGz9S8HjsPBS5+CDOFHh4cexXRG3ITfk6aLhuY -KTMlkIO4SHKmnwAvy1sFlsqj6PbfVjpHPLg625fdNxBpe57TLxtIdBB3C7ccQSRW -+UG6Cmbcmh80PbsSR132NLMlzLhbaOjxeCWWJRo6cLuHBptAFMNwqsXt8xVf9M0N -NdJoKUmblyvjnq0N8aMEqtQ1uGMTaCB39cutHQq+reD/uzsCAwEAAaNdMFswDgYD -VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV -HRMBAf8EBTADAQH/MBkGA1UdDgQSBBBob3R0dW5hQG1haWwuaTJwMA0GCSqGSIb3 -DQEBCwUAA4ICAQCibFV8t4pajP176u3jx31x1kgqX6Nd+0YFARPZQjq99kUyoZer -GyHGsMWgM281RxiZkveHxR7Hm7pEd1nkhG3rm+d7GdJ2p2hujr9xUvl0zEqAAqtm -lkYI6uJ13WBjFc9/QuRIdeIeSUN+eazSXNg2nJhoV4pF9n2Q2xDc9dH4GWO93cMX -JPKVGujT3s0b7LWsEguZBPdaPW7wwZd902Cg/M5fE1hZQ8/SIAGUtylb/ZilVeTS -spxWP1gX3NT1SSvv0s6oL7eADCgtggWaMxEjZhi6WMnPUeeFY8X+6trkTlnF9+r/ -HiVvvzQKrPPtB3j1xfQCAF6gUKN4iY+2AOExv4rl/l+JJbPhpd/FuvD8AVkLMZ8X -uPe0Ew2xv30cc8JjGDzQvoSpBmVTra4f+xqH+w8UEmxnx97Ye2aUCtnPykACnFte -oT97K5052B1zq+4fu4xaHZnEzPYVK5POzOufNLPgciJsWrR5GDWtHd+ht/ZD37+b -+j1BXpeBWUBQgluFv+lNMVNPJxc2OMELR1EtEwXD7mTuuUEtF5Pi63IerQ5LzD3G -KBvXhMB0XhpE6WG6pBwAvkGf5zVv/CxClJH4BQbdZwj9HYddfEQlPl0z/XFR2M0+ -9/8nBfGSPYIt6KeHBCeyQWTdE9gqSzMwTMFsennXmaT8gyc7eKqKF6adqw== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/igor_at_novg.net.crt b/contrib/certificates/reseed/igor_at_novg.net.crt index 12ce7a61..c6a05e8b 100644 --- a/contrib/certificates/reseed/igor_at_novg.net.crt +++ b/contrib/certificates/reseed/igor_at_novg.net.crt @@ -1,33 +1,33 @@ -----BEGIN CERTIFICATE----- -MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt +MIIFvjCCA6agAwIBAgIQBnsUOmOu2oZZIwHBmQc1BDANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN -aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x +aWdvckBub3ZnLm5ldDAeFw0yMzAxMjgxNDM4MzFaFw0zMzAxMjgxNDM4MzFaMG0x CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p -Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxst4 -cam3YibBtQHGPCPX13uRQti56U3XZytSZntaKrUFmJxjt41Q/mOy3KYo+lBvhfDF -x3tWKjgP9LJOJ28zvddFhZVNxqZRjcnAoPuSOVCw88g01D9OAasKF11hCfdxZP6h -vGm8WCnjD8KPcYFxJC4HJUiFeProAwuTzEAESTRk4CAQe3Ie91JspuqoLUc5Qxlm -w5QpjnjfZY4kaVHmZDKGIZDgNIt5v85bu4pWwZ6O+o90xQqjxvjyz/xccIec3sHw -MHJ8h8ZKMokCKEJTaRWBvdeNXki7nf3gUy/3GjYQlzo0Nxk/Hw4svPcA+eL0AYiy -Jn83bIB5VToW2zYUdV4u3qHeAhEg8Y7HI0kKcSUGm9AQXzbzP8YCHxi0sbb0GAJy -f1Xf3XzoPfT64giD8ReUHhwKpyMB6uvG/NfWSZAzeAO/NT7DAwXpKIVQdkVdqy8b -mvHvjf9/kWKOirA2Nygf3r79Vbg2mqbYC/b63XI9hheU689+O7qyhTEhNz+11X0d -Zax7UPrLrwOeB9TNfEnztsmrHNdv2n+KcOO2o11Wvz2nHP9g+dgwoZSD1ZEpFzWP -0sD5knKLwAL/64qLlAQ1feqW7hMr80IADcKjLSODkIDIIGm0ksXqEzTjz1JzbRDq -jUjq7EAlkw3G69rv1gHxIntllJRQidAqecyWHOMCAwEAAaNaMFgwDgYDVR0PAQH/ +Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvLkf +bM3uiYfp9m0vgdoftyXtk2/9bHf3u5iaM0WfoJIsw1iizo/mxJl+Iy7SxLC16nV0 +v5FpncVv+Z8x9dgoAYVuLq9zKfsAbpj6kuxAqw6vJMlD1TiIL3nSODV9BJLk47X5 +tmvoOSj9BgvemYThTE3nj+DbuJRW5q90KyBV/LdLrQJX3k5R3FFL5tTad2LKFNZ4 +vEOcYwwx6mvrkJ2lly6bAQUCtfc648Jyq+NO3Rba1fmn7gcP9zXXc5KYsj/ovyY2 +OaocSF5wMhzBuPxO+M2HqbYLMAkc6/GesGds8Rm8wofuhJoI5YtqJuLKZm6nQXSc +fx6PKgbKcTIUWNFMsxyfghz9hpbg0rkvC7PtfAjtV0yaDtUum1eZeNEx1HbRWN2n +TQNCVuv0yaKC41qxqzhEybkdjL9JlgUh7VuskaCelB0lz+kgYjGu8ezOa0ua2iKq +4FC/1MbPulxN8NOt4pmbGqqoxmCdShp38wdnOBM3DsAS9f0JaQZd4CDyY4DCSfVn +xPdWk31+VXVt3Ixh1EUqZWYTRSsZApkCyYzkiZ/qPGG6FR9Hq2SuhC5o4P44k7eo +6wwBWD8a5RjsZhvr05E5yBrKXh/PjLwmtG73QC+ouR54/5xtedvdTwNS94FnNctX +FT6QGZnRwCkhPaRe1oQMzP+88pGoCfO33GBAuwUCAwEAAaNaMFgwDgYDVR0PAQH/ BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC -AQADyPaec28qc1HQtAV5dscJr47k92RTfvan+GEgIwyQDHZQm38eyTb05xipQCdk -5ruUDFXLB5qXXFJKUbQM6IpaktmWDJqk4Zn+1nGbtFEbKgrF55pd63+NQer5QW9o -3+dGj0eZJa3HX5EBkd2r7j2LFuB6uxv3r/xiTeHaaflCnsmyDLfb7axvYhyEzHQS -AUi1bR+ln+dXewdtuojqc1+YmVGDgzWZK2T0oOz2E21CpZUDiP3wv9QfMaotLEal -zECnbhS++q889inN3GB4kIoN6WpPpeYtTV+/r7FLv9+KUOV1s2z6mxIqC5wBFhZs -0Sr1kVo8hB/EW/YYhDp99LoAOjIO6nn1h+qttfzBYr6C16j+8lGK2A12REJ4LiUQ -cQI/0zTjt2C8Ns6ueNzMLQN1Mvmlg1Z8wIB7Az7jsIbY2zFJ0M5qR5VJveTj33K4 -4WSbC/zMWOBYHTVBvGmc6JGhu5ZUTZ+mWP7QfimGu+tdhvtrybFjE9ROIE/4yFr6 -GkxEyt0UY87TeKXJ/3KygvkMwdvqGWiZhItb807iy99+cySujtbGfF2ZXYGjBXVW -dJOVRbyGQkHh6lrWHQM4ntBv4x+5QA+OAan5PBF3tcDx1vefPx+asYslbOXpzII5 -qhvoQxuRs6j5jsVFG6RdsKNeQAt87Mb2u2zK2ZakMdyD1w== +AQCteAb5/bqhHr/i5CJbDzlofprXFC826c19GxQ/9Hw0kA52l0J9Q8Vz8Vy7VQyP +QNa8MCv6FeNy8a/wXp6cafyFsBtvehVQO8lFlpCgMEl2Bma43+GaCwkrM6bFNXeW +iQ9h4e1KjsUZ8cQDNEcamiJ80+xbMhBrj5bAZwKmZs8MoGEMyXKEZmcmwA+/fy1c +cx4izsOsmRXmEHXsvB9ydJHZZeKW8+r0DAtgPslwXuXHG6MuBQo7dKCqn+iMxHXV +Jxriq3yvNffdGx4maSLJrjQ1ealt/UMzql7huVSItnVFWoYf7GAELXNJ/PmqVyaK +q11LQ8W/Aud6s/bblaJrFJnK8PbPpaw4RvHoWVLYaZYmQnV2msWs5EuESBlEADbv +UklQXLMc2f9HKWPA5678nvYPrmu8IL5pMkAxgGRqmd+7vCz4lU9M5z3HObU+WRBt +qEMYyXywV8o3tbmnlDS5S5Xxf+tLZn1cxz3ZrmcHPHDbLBNdvszF3CTJH/R2sQvD +bizvYJM+p5F+GWM5mt6w0HrOut5MRlpOws/NRrkbijuVA/A45nzTtKplIFYE3qe8 +q5SAbwYLc8cJcZCN3PxtWwbEv81V33abMt5QcjnWGLH5t2+1Z2KLCgKLSCQTxM8s +zBPHtUe8qtSQaElnNLILYbtJ1w67dPnGYTphHihC+CXjBg== -----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt b/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt new file mode 100644 index 00000000..c94d319e --- /dev/null +++ b/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQVpTNnJZlUTDqmZiHRU4wCjANBgkqhkiG9w0BAQsFADB2 +MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK +ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW +dW5peGVub0BjdWJpY2NoYW9zLm5ldDAeFw0yNTAzMDQxODU5NDZaFw0zNTAzMDQx +ODU5NDZaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx +HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w +HQYDVQQDDBZ1bml4ZW5vQGN1YmljY2hhb3MubmV0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAr/JoAzLDtHXoAc7QcP4IxO+xNTeiYs78Wlg/Sl/sa6qz +gJoGaKH/X++z4Xe9lBSZalXCamnO4QMTgsWOIeoMy6XVbGzNTXPl8JUcblTIXwkP +pv848b1nxLfgLHzPRz1mJMpMikBugJ3Iz1sQzDVlUdye2fgbGChWliz9P4ClEODv +A/4+7i6uvJgEZ7A+jx3vBCXhiJppE3wTuz5D9BQqG8NuEwwjwBTBByoCC4oxOe0h +Qu1k7kEr+n4qpSEg/1eJ/RYSm+I8BftK1RUfykTwxlfmyEmKsfLBQWczE8Ca9nUB +5V34UH2bRy1cvavJYcNW3EPsGNf4naRs+Gy8XIFrb315GgWC1Z6+tzk+QFli9YeF +0DgtYEZciqu/407o8ZEURTnPjB7GhLDDp1LAQ7CQRhzaraXjHj0hyO+6rFpFdD0D +mXhvI/Eph3QIldsgnQc7nPhU2csN8Vi6bNDgm0HZ8cdmIBpI2Uxn/acZX/9G40oj +czrhsCBEecu/BluLJsfaWCYg90rvONP8Fc4edHAMonzYZR4r0q4hbv7AM8GmDRDN +J9/DZFi+Qs9NAe06jJC3jSsj7IdIs8TMhw8FX3xWOlXwjmVETAgY/dta/MpLJ6tJ +i+E+TH/Ndntj/D6WUwdQq+LyCR6gqHUWR6rl6EDQz+08DWb7j/72JSLb/DaXrDUC +AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr +BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZ1bml4ZW5vQGN1Ymlj +Y2hhb3MubmV0MA0GCSqGSIb3DQEBCwUAA4ICAQBBVoPeuOkmJDUdzIrzmxTmjMyz +gpfrZnjirTQKWhbv53sAWNeJ3kZ9l9m+0YpqEtFDrZPL5LTBXcSci5gGuxPkp+i/ +f/axsdcFMKbI9B/M53fyXLLJY0EM4mdhNAWtph1kTowFPhhReefCdqxYIy9uk2pL +gfb6NYJf+w9//fKYFZXb9SsiRchfv81+lbN+PIprnCpV3cTZWmpLRi2hN86pMW20 +3rh7rqJ4dPnA/NoyM+JstL10IU/4StqInweEvoo4W44+cC0zYGvfkkrKL4LB8w5S +6DKebtk7NQDtzuw2QnA9Ms4bmqWQpbL6/7uGaauS0+nmF+2gkqi9hcv0W5ZoBb/D +IVRauySnCtp9PbYM7pIJP9a1U6naLj7L1VixqsJGfPQ8V9TCOOi5bDc3RTetI/DX +bXHhAqHYzboakptylCp+Ao5h2hu0+w4rqnG63HwsHDJWcETbdVFQfzlzUmbx53yV +GnBsUxDgMOiHTZdKLkEnH4Q/XI76uc0ntTRlK9ktKWZPSISUlHrFnFl6I5UdeBMy +6vpB9sJO5L5RPRi4945K5Xdennywdi508mNXtMMmNCqrk1SMYbwaY6ZtIvXEGam9 +uHQTiTEX9LED/VXzFGqzdyDbG43HgS0PksgzedelHWfVAEnc06U3JX2lqUyihYHa +N4jAXWQ7s5p4GYaf4Q== +-----END CERTIFICATE----- diff --git a/contrib/debian/README b/contrib/debian/README index cccbc4de..7dc3a61f 100644 --- a/contrib/debian/README +++ b/contrib/debian/README @@ -1,2 +1,5 @@ -This forder contain systemd unit files. -To use systemd daemon control, place files from this directory to debian folder before building package. +This forder contain files required for building debian packages. + +The trunk repository is contains the packaging files for the latest stable version of Debian (if we not forgot to update them). + +Files in subdirectories contains fixes to make possible to build package on specific versions of Debian/Ubuntu. They are used when building the release package. diff --git a/contrib/debian/bionic/compat b/contrib/debian/bionic/compat new file mode 100644 index 00000000..b4de3947 --- /dev/null +++ b/contrib/debian/bionic/compat @@ -0,0 +1 @@ +11 diff --git a/contrib/debian/bionic/control b/contrib/debian/bionic/control new file mode 100644 index 00000000..d872881c --- /dev/null +++ b/contrib/debian/bionic/control @@ -0,0 +1,18 @@ +Source: i2pd +Section: net +Priority: optional +Maintainer: r4sas +Build-Depends: debhelper (>= 11~), 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.2.0 +Homepage: http://i2pd.website/ +Vcs-Git: git://github.com/PurpleI2P/i2pd.git +Vcs-Browser: https://github.com/PurpleI2P/i2pd + +Package: i2pd +Architecture: any +Pre-Depends: ${misc:Pre-Depends}, adduser +Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, +Description: Full-featured C++ implementation of I2P client. + I2P (Invisible Internet Protocol) is a universal anonymous network layer. All + communications over I2P are anonymous and end-to-end encrypted, participants + don't reveal their real IP addresses. diff --git a/contrib/debian/trusty/compat b/contrib/debian/trusty/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/contrib/debian/trusty/compat @@ -0,0 +1 @@ +9 diff --git a/contrib/debian/trusty/control b/contrib/debian/trusty/control new file mode 100644 index 00000000..fc618ddc --- /dev/null +++ b/contrib/debian/trusty/control @@ -0,0 +1,18 @@ +Source: i2pd +Section: net +Priority: optional +Maintainer: r4sas +Build-Depends: debhelper (>= 9), 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 +Homepage: http://i2pd.website/ +Vcs-Git: git://github.com/PurpleI2P/i2pd.git +Vcs-Browser: https://github.com/PurpleI2P/i2pd + +Package: i2pd +Architecture: any +Pre-Depends: ${misc:Pre-Depends}, adduser +Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, +Description: Full-featured C++ implementation of I2P client. + I2P (Invisible Internet Protocol) is a universal anonymous network layer. All + communications over I2P are anonymous and end-to-end encrypted, participants + don't reveal their real IP addresses. diff --git a/debian/patches/02-upnp.patch b/contrib/debian/trusty/patches/01-upnp.patch similarity index 80% rename from debian/patches/02-upnp.patch rename to contrib/debian/trusty/patches/01-upnp.patch index bec8f2b0..74d36c06 100644 --- a/debian/patches/02-upnp.patch +++ b/contrib/debian/trusty/patches/01-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2022-03-23 +Last-Update: 2024-12-30 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ include filelist.mk +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) diff --git a/contrib/debian/trusty/patches/02-service.patch b/contrib/debian/trusty/patches/02-service.patch new file mode 100644 index 00000000..546e252f --- /dev/null +++ b/contrib/debian/trusty/patches/02-service.patch @@ -0,0 +1,19 @@ +Description: Disable LogsDirectory and LogsDirectoryMode options in service +Author: r4sas + +Reviewed-By: r4sas +Last-Update: 2024-07-19 + +--- a/contrib/i2pd.service ++++ b/contrib/i2pd.service +@@ -8,8 +8,8 @@ User=i2pd + Group=i2pd + RuntimeDirectory=i2pd + RuntimeDirectoryMode=0700 +-LogsDirectory=i2pd +-LogsDirectoryMode=0700 ++#LogsDirectory=i2pd ++#LogsDirectoryMode=0700 + Type=forking + ExecStart=/usr/bin/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/contrib/debian/trusty/patches/series b/contrib/debian/trusty/patches/series new file mode 100644 index 00000000..d8caec41 --- /dev/null +++ b/contrib/debian/trusty/patches/series @@ -0,0 +1,2 @@ +01-upnp.patch +02-service.patch diff --git a/contrib/debian/trusty/rules b/contrib/debian/trusty/rules new file mode 100755 index 00000000..97a4e008 --- /dev/null +++ b/contrib/debian/trusty/rules @@ -0,0 +1,18 @@ +#!/usr/bin/make -f +#export DH_VERBOSE=1 + +export DEB_BUILD_MAINT_OPTIONS=hardening=+all + +include /usr/share/dpkg/architecture.mk + +ifeq ($(DEB_HOST_ARCH),i386) + export DEB_BUILD_OPTIONS=parallel=1 +endif + +export DEB_CXXFLAGS_MAINT_APPEND=-Wall -pedantic +export DEB_LDFLAGS_MAINT_APPEND= + +%: + dh $@ --parallel + +override_dh_auto_install: diff --git a/contrib/debian/xenial/compat b/contrib/debian/xenial/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/contrib/debian/xenial/compat @@ -0,0 +1 @@ +9 diff --git a/contrib/debian/xenial/control b/contrib/debian/xenial/control new file mode 100644 index 00000000..fc618ddc --- /dev/null +++ b/contrib/debian/xenial/control @@ -0,0 +1,18 @@ +Source: i2pd +Section: net +Priority: optional +Maintainer: r4sas +Build-Depends: debhelper (>= 9), 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 +Homepage: http://i2pd.website/ +Vcs-Git: git://github.com/PurpleI2P/i2pd.git +Vcs-Browser: https://github.com/PurpleI2P/i2pd + +Package: i2pd +Architecture: any +Pre-Depends: ${misc:Pre-Depends}, adduser +Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, +Description: Full-featured C++ implementation of I2P client. + I2P (Invisible Internet Protocol) is a universal anonymous network layer. All + communications over I2P are anonymous and end-to-end encrypted, participants + don't reveal their real IP addresses. diff --git a/contrib/debian/xenial/patches/01-upnp.patch b/contrib/debian/xenial/patches/01-upnp.patch new file mode 100644 index 00000000..74d36c06 --- /dev/null +++ b/contrib/debian/xenial/patches/01-upnp.patch @@ -0,0 +1,17 @@ +Description: Enable UPnP usage in package +Author: r4sas + +Reviewed-By: r4sas +Last-Update: 2024-12-30 + +--- i2pd.orig/Makefile ++++ i2pd/Makefile +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk + + USE_STATIC := $(or $(USE_STATIC),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) + + # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string diff --git a/contrib/debian/xenial/patches/02-service.patch b/contrib/debian/xenial/patches/02-service.patch new file mode 100644 index 00000000..546e252f --- /dev/null +++ b/contrib/debian/xenial/patches/02-service.patch @@ -0,0 +1,19 @@ +Description: Disable LogsDirectory and LogsDirectoryMode options in service +Author: r4sas + +Reviewed-By: r4sas +Last-Update: 2024-07-19 + +--- a/contrib/i2pd.service ++++ b/contrib/i2pd.service +@@ -8,8 +8,8 @@ User=i2pd + Group=i2pd + RuntimeDirectory=i2pd + RuntimeDirectoryMode=0700 +-LogsDirectory=i2pd +-LogsDirectoryMode=0700 ++#LogsDirectory=i2pd ++#LogsDirectoryMode=0700 + Type=forking + ExecStart=/usr/bin/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/contrib/debian/xenial/patches/series b/contrib/debian/xenial/patches/series new file mode 100644 index 00000000..d8caec41 --- /dev/null +++ b/contrib/debian/xenial/patches/series @@ -0,0 +1,2 @@ +01-upnp.patch +02-service.patch diff --git a/contrib/debian/xenial/rules b/contrib/debian/xenial/rules new file mode 100755 index 00000000..11791d9b --- /dev/null +++ b/contrib/debian/xenial/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +#export DH_VERBOSE=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +include /usr/share/dpkg/architecture.mk + +export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic +export DEB_LDFLAGS_MAINT_APPEND = + +%: + dh $@ --parallel + +override_dh_auto_install: diff --git a/contrib/dinit/i2pd b/contrib/dinit/i2pd new file mode 100644 index 00000000..63d877c1 --- /dev/null +++ b/contrib/dinit/i2pd @@ -0,0 +1,8 @@ +type = bgprocess +run-as = i2pd +command = /usr/bin/i2pd --conf=/var/lib/i2pd/i2pd.conf --pidfile=/var/lib/i2pd/i2pd.pid --daemon --service +smooth-recovery = true +depends-on = ntpd +# uncomment if you want to use i2pd with yggdrasil +# depends-on = yggdrasil +pid-file = /var/lib/i2pd/i2pd.pid diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 71af141e..ead21f10 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,5 +1,18 @@ +# +# Copyright (c) 2017-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 +# + FROM alpine:latest -LABEL authors "Mikal Villa , Darknet Villain " +LABEL authors="Mikal Villa , Darknet Villain " +LABEL maintainer="R4SAS " + +LABEL org.opencontainers.image.source=https://github.com/PurpleI2P/i2pd +LABEL org.opencontainers.image.documentation=https://i2pd.readthedocs.io/en/latest/ +LABEL org.opencontainers.image.licenses=BSD3 # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="openssl" @@ -11,27 +24,28 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" -ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" +ENV DEFAULT_ARGS=" --datadir=$DATA_DIR" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" -# -# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the -# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. -# -# 1. install deps, clone and build. -# 2. strip binaries. -# 3. Purge all dependencies and other unrelated packages, including build directory. +# 1. Building binary +# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the +# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. +# +# 1. install deps, clone and build. +# 2. strip binaries. +# 3. Purge all dependencies and other unrelated packages, including build directory. + RUN apk update \ && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && make USE_UPNP=yes \ + && make -j$(nproc) USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ @@ -45,6 +59,9 @@ RUN apk update \ # 2. Adding required libraries to run i2pd to ensure it will run. 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 "$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/docker/i2pd-docker.conf b/contrib/docker/i2pd-docker.conf new file mode 100644 index 00000000..3b91a235 --- /dev/null +++ b/contrib/docker/i2pd-docker.conf @@ -0,0 +1,52 @@ +## Preconfigured i2pd configuration file for a Docker container +## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ +## for more options you can use in this file. + +## Note that for exposing ports outside of container you need to bind all services to 0.0.0.0 + +log = file +loglevel = none + +ipv4 = true +ipv6 = false + +# bandwidth = L +# notransit = false +# floodfill = false + +[ntcp2] +enabled = true +published = true + +[ssu2] +enabled = true +published = true + +[http] +enabled = true +address = 0.0.0.0 +port = 7070 + +[httpproxy] +enabled = true +address = 0.0.0.0 +port = 4444 + +[socksproxy] +enabled = true +address = 0.0.0.0 +port = 4447 + +[sam] +enabled = true +address = 0.0.0.0 +port = 7656 + +[upnp] +enabled = false + +[reseed] +verify = true + +[limits] +# transittunnels = 2500 diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index c9ded966..7782187f 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-06-10 01:25\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -18,66 +18,73 @@ 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:107 +#, 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:111 +#, 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:115 +#, 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:118 +#, 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:126 +#, c-format +msgid "%.2f KiB" msgstr "" -#. tr: Mebibit -#: daemon/HTTPServer.cpp:129 -msgid "MiB" +#. tr: Mebibyte +#: daemon/HTTPServer.cpp:128 +#, c-format +msgid "%.2f MiB" msgstr "" -#. tr: Gibibit -#: daemon/HTTPServer.cpp:131 -msgid "GiB" +#. tr: Gibibyte +#: daemon/HTTPServer.cpp:130 +#, c-format +msgid "%.2f GiB" msgstr "" -#: daemon/HTTPServer.cpp:148 +#: daemon/HTTPServer.cpp:147 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:149 +#: daemon/HTTPServer.cpp:148 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:150 +#: daemon/HTTPServer.cpp:149 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:151 +#: daemon/HTTPServer.cpp:150 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:152 +#: daemon/HTTPServer.cpp:151 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:154 +#: daemon/HTTPServer.cpp:153 msgid "exploratory" msgstr "" @@ -94,32 +101,32 @@ msgstr "" msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:700 +#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:742 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:382 -#: daemon/HTTPServer.cpp:394 +#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:395 +#: daemon/HTTPServer.cpp:407 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:197 daemon/HTTPServer.cpp:365 +#: daemon/HTTPServer.cpp:454 daemon/HTTPServer.cpp:474 +#: daemon/HTTPServer.cpp:636 daemon/HTTPServer.cpp:682 +#: daemon/HTTPServer.cpp:686 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:650 +#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:692 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:359 -#: daemon/HTTPServer.cpp:770 daemon/HTTPServer.cpp:786 +#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:372 +#: daemon/HTTPServer.cpp:813 daemon/HTTPServer.cpp:830 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:839 +#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:898 msgid "Transports" msgstr "" @@ -127,15 +134,16 @@ msgstr "" msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:908 -#: daemon/HTTPServer.cpp:918 +#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:927 +#: daemon/HTTPServer.cpp:937 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:222 daemon/HTTPServer.cpp:1329 +#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1335 +#: daemon/HTTPServer.cpp:1362 daemon/HTTPServer.cpp:1365 +#: daemon/HTTPServer.cpp:1379 daemon/HTTPServer.cpp:1424 +#: daemon/HTTPServer.cpp:1427 daemon/HTTPServer.cpp:1430 msgid "ERROR" msgstr "" @@ -151,14 +159,14 @@ msgstr "" msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:253 -#: daemon/HTTPServer.cpp:325 +#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:235 +#: daemon/HTTPServer.cpp:336 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:233 daemon/HTTPServer.cpp:382 +#: daemon/HTTPServer.cpp:383 daemon/HTTPServer.cpp:1003 +#: daemon/HTTPServer.cpp:1011 msgid "Proxy" msgstr "" @@ -166,564 +174,610 @@ msgstr "" msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:237 -msgid "Error" -msgstr "" - -#: daemon/HTTPServer.cpp:241 +#: daemon/HTTPServer.cpp:242 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:244 +#: daemon/HTTPServer.cpp:245 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:247 +#: daemon/HTTPServer.cpp:248 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:259 +#: daemon/HTTPServer.cpp:251 +msgid "Full cone NAT" +msgstr "" + +#: daemon/HTTPServer.cpp:254 +msgid "No Descriptors" +msgstr "" + +#: daemon/HTTPServer.cpp:263 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:262 +#: daemon/HTTPServer.cpp:266 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:267 +#: daemon/HTTPServer.cpp:271 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:280 +#: daemon/HTTPServer.cpp:277 daemon/HTTPServer.cpp:284 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:287 +#: daemon/HTTPServer.cpp:291 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:288 +#: daemon/HTTPServer.cpp:292 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:289 -msgid "Received" -msgstr "" - -#. tr: Kibibit/s -#: daemon/HTTPServer.cpp:291 daemon/HTTPServer.cpp:294 -#: daemon/HTTPServer.cpp:297 -msgid "KiB/s" -msgstr "" - -#: daemon/HTTPServer.cpp:292 -msgid "Sent" -msgstr "" - -#: daemon/HTTPServer.cpp:295 -msgid "Transit" +#: daemon/HTTPServer.cpp:296 +msgid "Total tunnel creation success rate" msgstr "" #: daemon/HTTPServer.cpp:298 -msgid "Data path" +msgid "Received" +msgstr "" + +#. tr: Kibibyte/s +#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:303 +#: daemon/HTTPServer.cpp:306 +#, c-format +msgid "%.2f KiB/s" msgstr "" #: daemon/HTTPServer.cpp:301 -msgid "Hidden content. Press on text to see." +msgid "Sent" msgstr "" #: daemon/HTTPServer.cpp:304 -msgid "Router Ident" -msgstr "" - -#: daemon/HTTPServer.cpp:306 -msgid "Router Family" +msgid "Transit" msgstr "" #: daemon/HTTPServer.cpp:307 +msgid "Data path" +msgstr "" + +#: daemon/HTTPServer.cpp:310 +msgid "Hidden content. Press on text to see." +msgstr "" + +#: daemon/HTTPServer.cpp:314 +msgid "Router Ident" +msgstr "" + +#: daemon/HTTPServer.cpp:316 +msgid "Router Family" +msgstr "" + +#: daemon/HTTPServer.cpp:317 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:308 +#: daemon/HTTPServer.cpp:318 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:309 +#: daemon/HTTPServer.cpp:319 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:337 +#. tr: Shown when router doesn't publish itself and have "Firewalled" state +#: daemon/HTTPServer.cpp:349 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:350 +#: daemon/HTTPServer.cpp:363 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:351 +#: daemon/HTTPServer.cpp:364 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:962 +#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:987 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:368 +#: daemon/HTTPServer.cpp:381 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:382 daemon/HTTPServer.cpp:383 +#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 +#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 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:382 daemon/HTTPServer.cpp:383 +#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 +#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 msgid "Disabled" msgstr "" -#: daemon/HTTPServer.cpp:417 +#: daemon/HTTPServer.cpp:434 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:426 +#: daemon/HTTPServer.cpp:442 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:431 +#: daemon/HTTPServer.cpp:447 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:432 +#: daemon/HTTPServer.cpp:448 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:433 +#: daemon/HTTPServer.cpp:449 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:457 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:439 +#: daemon/HTTPServer.cpp:459 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:439 +#: daemon/HTTPServer.cpp:460 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:449 daemon/HTTPServer.cpp:655 +#: daemon/HTTPServer.cpp:467 +msgid "Expire LeaseSet" +msgstr "" + +#: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:697 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:494 daemon/HTTPServer.cpp:514 +#: daemon/HTTPServer.cpp:711 daemon/HTTPServer.cpp:731 +#, c-format +msgid "%dms" msgstr "" -#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:674 +#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:716 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:491 +#: daemon/HTTPServer.cpp:521 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:491 +#: daemon/HTTPServer.cpp:522 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:498 daemon/HTTPServer.cpp:501 +#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:535 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:515 +#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:551 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:499 +#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:814 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:506 +#: daemon/HTTPServer.cpp:540 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:514 daemon/HTTPServer.cpp:517 +#: daemon/HTTPServer.cpp:548 daemon/HTTPServer.cpp:554 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:515 +#: daemon/HTTPServer.cpp:551 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:524 daemon/HTTPServer.cpp:582 +#: daemon/HTTPServer.cpp:561 daemon/HTTPServer.cpp:621 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:941 +#: daemon/HTTPServer.cpp:572 daemon/HTTPServer.cpp:960 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:558 +#: daemon/HTTPServer.cpp:595 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:587 +#: daemon/HTTPServer.cpp:613 daemon/HTTPServer.cpp:1430 +msgid "Such destination is not found" +msgstr "" + +#: daemon/HTTPServer.cpp:626 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:590 +#: daemon/HTTPServer.cpp:629 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:616 +#: daemon/HTTPServer.cpp:658 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:619 +#: daemon/HTTPServer.cpp:661 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:620 +#: daemon/HTTPServer.cpp:662 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:625 +#: daemon/HTTPServer.cpp:667 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:628 +#: daemon/HTTPServer.cpp:670 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:629 +#: daemon/HTTPServer.cpp:671 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:630 +#: daemon/HTTPServer.cpp:672 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:640 -msgid "not floodfill" +#: daemon/HTTPServer.cpp:682 +msgid "floodfill mode is disabled" msgstr "" -#: daemon/HTTPServer.cpp:651 +#: daemon/HTTPServer.cpp:693 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:701 +#: daemon/HTTPServer.cpp:743 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:706 +#: daemon/HTTPServer.cpp:744 +msgid "Reload tunnels configuration" +msgstr "" + +#: daemon/HTTPServer.cpp:747 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:708 +#: daemon/HTTPServer.cpp:749 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:712 daemon/HTTPServer.cpp:717 +#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:758 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:714 daemon/HTTPServer.cpp:719 +#: daemon/HTTPServer.cpp:755 daemon/HTTPServer.cpp:760 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:722 +#: daemon/HTTPServer.cpp:763 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:723 +#: daemon/HTTPServer.cpp:764 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:726 +#: daemon/HTTPServer.cpp:767 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:728 +#: daemon/HTTPServer.cpp:770 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:736 +#: daemon/HTTPServer.cpp:779 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:741 daemon/HTTPServer.cpp:760 +#: daemon/HTTPServer.cpp:784 daemon/HTTPServer.cpp:803 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:748 +#: daemon/HTTPServer.cpp:791 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:786 +#: daemon/HTTPServer.cpp:830 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:902 daemon/HTTPServer.cpp:925 +#: daemon/HTTPServer.cpp:921 daemon/HTTPServer.cpp:944 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:918 +#: daemon/HTTPServer.cpp:937 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:931 +#: daemon/HTTPServer.cpp:950 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:936 +#: daemon/HTTPServer.cpp:955 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:993 +#: daemon/HTTPServer.cpp:1020 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:1009 +#: daemon/HTTPServer.cpp:1036 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1023 +#: daemon/HTTPServer.cpp:1050 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1223 +#: daemon/HTTPServer.cpp:1250 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1242 +#: daemon/HTTPServer.cpp:1269 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1300 daemon/HTTPServer.cpp:1357 -#: daemon/HTTPServer.cpp:1397 +#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1359 +#: daemon/HTTPServer.cpp:1414 daemon/HTTPServer.cpp:1454 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1300 +#: daemon/HTTPServer.cpp:1327 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1302 +#: daemon/HTTPServer.cpp:1329 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1305 +#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1365 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1308 +#: daemon/HTTPServer.cpp:1335 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1310 daemon/HTTPServer.cpp:1375 +#: daemon/HTTPServer.cpp:1337 daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1432 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" -msgstr "" - -#: daemon/HTTPServer.cpp:1322 -msgid "Transit tunnels count must not exceed 65535" -msgstr "" - -#: daemon/HTTPServer.cpp:1323 daemon/HTTPServer.cpp:1398 -msgid "Back to commands list" +#: daemon/HTTPServer.cpp:1338 daemon/HTTPServer.cpp:1368 +#: daemon/HTTPServer.cpp:1381 daemon/HTTPServer.cpp:1456 +#, c-format +msgid "You will be redirected in %d seconds" msgstr "" #: daemon/HTTPServer.cpp:1359 +msgid "LeaseSet expiration time updated" +msgstr "" + +#: daemon/HTTPServer.cpp:1362 +msgid "LeaseSet is not found or already expired" +msgstr "" + +#: daemon/HTTPServer.cpp:1379 +#, c-format +msgid "Transit tunnels count must not exceed %d" +msgstr "" + +#: daemon/HTTPServer.cpp:1380 daemon/HTTPServer.cpp:1455 +msgid "Back to commands list" +msgstr "" + +#: daemon/HTTPServer.cpp:1416 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1360 +#: daemon/HTTPServer.cpp:1417 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1360 +#: daemon/HTTPServer.cpp:1417 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1418 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1424 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1370 +#: daemon/HTTPServer.cpp:1427 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1373 -msgid "Such destination is not found" -msgstr "" - -#: daemon/HTTPServer.cpp:1393 +#: daemon/HTTPServer.cpp:1450 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1397 +#: daemon/HTTPServer.cpp:1454 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:333 libi2pd_client/HTTPProxy.cpp:348 +#: libi2pd_client/HTTPProxy.cpp:417 libi2pd_client/HTTPProxy.cpp:460 msgid "Invalid request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:282 +#: libi2pd_client/HTTPProxy.cpp:333 msgid "Proxy unable to parse your request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:297 -msgid "addresshelper is not supported" +#: libi2pd_client/HTTPProxy.cpp:348 +msgid "Addresshelper is not supported" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:306 libi2pd_client/HTTPProxy.cpp:315 -#: libi2pd_client/HTTPProxy.cpp:392 -msgid "Host" +#: libi2pd_client/HTTPProxy.cpp:373 +#, 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:306 -msgid "added to router's addressbook from helper" +#: libi2pd_client/HTTPProxy.cpp:375 +msgid "Addresshelper forced update rejected" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:307 -msgid "Click here to proceed:" +#: libi2pd_client/HTTPProxy.cpp:382 +#, c-format +msgid "" +"To add host %s in router's addressbook, click here: Continue." msgstr "" -#: libi2pd_client/HTTPProxy.cpp:307 libi2pd_client/HTTPProxy.cpp:317 -msgid "Continue" +#: libi2pd_client/HTTPProxy.cpp:384 +msgid "Addresshelper request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:308 libi2pd_client/HTTPProxy.cpp:318 -msgid "Addresshelper found" +#: libi2pd_client/HTTPProxy.cpp:393 +#, c-format +msgid "" +"Host %s added to router's addressbook from helper. Click here to proceed: Continue." msgstr "" -#: libi2pd_client/HTTPProxy.cpp:315 -msgid "already in router's addressbook" +#: libi2pd_client/HTTPProxy.cpp:395 +msgid "Addresshelper adding" 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:" +#: libi2pd_client/HTTPProxy.cpp:402 +#, c-format +msgid "" +"Host %s is already in router's addressbook. Click " +"here to update record: Continue." msgstr "" -#: libi2pd_client/HTTPProxy.cpp:331 -msgid "invalid request uri" +#: libi2pd_client/HTTPProxy.cpp:404 +msgid "Addresshelper update" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:372 +#: libi2pd_client/HTTPProxy.cpp:417 +msgid "Invalid request URI" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:460 msgid "Can't detect destination host from request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:389 libi2pd_client/HTTPProxy.cpp:393 +#: libi2pd_client/HTTPProxy.cpp:477 libi2pd_client/HTTPProxy.cpp:481 msgid "Outproxy failure" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:389 -msgid "bad outproxy settings" +#: libi2pd_client/HTTPProxy.cpp:477 +msgid "Bad outproxy settings" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:392 -msgid "not inside I2P network, but outproxy is not enabled" +#: libi2pd_client/HTTPProxy.cpp:480 +#, 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:569 +msgid "Unknown outproxy URL" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:490 -msgid "cannot resolve upstream proxy" +#: libi2pd_client/HTTPProxy.cpp:575 +msgid "Cannot resolve upstream proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:498 -msgid "hostname too long" +#: libi2pd_client/HTTPProxy.cpp:583 +msgid "Hostname is too long" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:525 -msgid "cannot connect to upstream socks proxy" +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "Cannot connect to upstream SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:531 -msgid "Cannot negotiate with socks proxy" +#: libi2pd_client/HTTPProxy.cpp:616 +msgid "Cannot negotiate with SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:573 +#: libi2pd_client/HTTPProxy.cpp:658 msgid "CONNECT error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:573 -msgid "Failed to Connect" +#: libi2pd_client/HTTPProxy.cpp:658 +msgid "Failed to connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:584 libi2pd_client/HTTPProxy.cpp:610 -msgid "socks proxy error" +#: libi2pd_client/HTTPProxy.cpp:669 libi2pd_client/HTTPProxy.cpp:695 +msgid "SOCKS proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:592 -msgid "failed to send request to upstream" +#: libi2pd_client/HTTPProxy.cpp:677 +msgid "Failed to send request to upstream" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:613 -msgid "No Reply From socks proxy" +#: libi2pd_client/HTTPProxy.cpp:698 +msgid "No reply from SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:620 -msgid "cannot connect" +#: libi2pd_client/HTTPProxy.cpp:705 +msgid "Cannot connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:620 -msgid "http out proxy not implemented" +#: libi2pd_client/HTTPProxy.cpp:705 +msgid "HTTP out proxy not implemented" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:621 -msgid "cannot connect to upstream http proxy" +#: libi2pd_client/HTTPProxy.cpp:706 +msgid "Cannot connect to upstream HTTP proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:654 +#: libi2pd_client/HTTPProxy.cpp:739 msgid "Host is down" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:654 +#: libi2pd_client/HTTPProxy.cpp:739 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 4efc26d0..ce775ecb 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -1,29 +1,30 @@ -`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", "$4", "$6", "$8", "$10"}},\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 -kntr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +``` + +Regex for transforming gettext translations to our format: +--- + +``` +in: ^(\"|#[:.,]|msgctxt)(.*)$\n +out: +``` + +``` +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\n +out: \n +``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 1c4d814c..c26f8af0 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -19,7 +19,7 @@ ## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates # certsdir = /var/lib/i2pd/certificates -## Where to write pidfile (default: i2pd.pid, not used in Windows) +## Where to write pidfile (default: /run/i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid ## Logging configuration section @@ -31,15 +31,16 @@ ## * file - log entries to a file ## * syslog - use syslog, see man 3 syslog # log = file -## Path to logfile (default - autodetect) +## Path to logfile (default: autodetect) # logfile = /var/log/i2pd/i2pd.log -## Log messages above this level (debug, info, *warn, error, none) +## Log messages above this level (debug, info, *warn, error, critical, none) ## If you set it to none, logging will be disabled # loglevel = warn ## Write full CLF-formatted date and time to log (default: write only time) # logclftime = true ## Daemon mode. Router will go to background after start. Ignored on Windows +## (default: true) # daemon = true ## Specify a family, router belongs to (default - none) @@ -70,90 +71,97 @@ ## don't just uncomment this # port = 4567 -## Enable communication through ipv4 +## Enable communication through ipv4 (default: true) ipv4 = true -## Enable communication through ipv6 +## Enable communication through ipv6 (default: false) ipv6 = false -## Enable SSU transport (default = true) -ssu = false - ## Bandwidth configuration -## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, +## L limit bandwidth to 32 KB/sec, O - to 256 KB/sec, P - to 2048 KB/sec, ## X - unlimited -## Default is L (regular node) and X if floodfill mode enabled. If you want to -## share more bandwidth without floodfill mode, uncomment that line and adjust -## value to your possibilities +## Default is L (regular node) and X if floodfill mode enabled. +## If you want to share more bandwidth without floodfill mode, uncomment +## that line and adjust value to your possibilities. Value can be set to +## integer in kilobytes, it will apply that limit and flag will be used +## from next upper limit (example: if you set 4096 flag will be X, but real +## limit will be 4096 KB/s). Same can be done when floodfill mode is used, +## but keep in mind that low values may be negatively evaluated by Java +## router algorithms. # bandwidth = L -## Max % of bandwidth limit for transit. 0-100. 100 by default +## Max % of bandwidth limit for transit. 0-100 (default: 100) # share = 100 ## Router will not accept transit tunnels, disabling transit traffic completely -## (default = false) +## (default: false) # notransit = true -## Router will be floodfill +## Router will be floodfill (default: false) ## Note: that mode uses much more network connections and CPU! # floodfill = true [ntcp2] -## Enable NTCP2 transport (default = true) +## Enable NTCP2 transport (default: true) # enabled = true -## Publish address in RouterInfo (default = true) +## Publish address in RouterInfo (default: true) # published = true ## Port for incoming connections (default is global port option value) # port = 4567 [ssu2] -## Enable SSU2 transport (default = false for 2.43.0) -enabled = true -## Publish address in RouterInfo (default = false for 2.43.0) -published = true -## Port for incoming connections (default is global port option value or port + 1 if SSU is enabled) +## Enable SSU2 transport (default: true) +# enabled = true +## Publish address in RouterInfo (default: true) +# published = true +## Port for incoming connections (default is global port option value) # port = 4567 [http] ## Web Console settings -## Uncomment and set to 'false' to disable Web Console +## Enable the Web Console (default: true) # enabled = true -## Address and port service will listen on -address = 127.0.0.1 -port = 7070 -## Path to web console, default "/" +## Address and port service will listen on (default: 127.0.0.1:7070) +# address = 127.0.0.1 +# port = 7070 +## Path to web console (default: /) # webroot = / -## Uncomment following lines to enable Web Console authentication +## Enable Web Console authentication (default: false) +## 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, russian, 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] -## Uncomment and set to 'false' to disable HTTP Proxy +## Enable the HTTP proxy (default: true) # enabled = true -## Address and port service will listen on -address = 127.0.0.1 -port = 4444 -## Optional keys file for proxy local destination +## Address and port service will listen on (default: 127.0.0.1:4444) +# address = 127.0.0.1 +# port = 4444 +## Optional keys file for proxy local destination (default: http-proxy-keys.dat) # 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 ## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. [socksproxy] -## Uncomment and set to 'false' to disable SOCKS Proxy +## Enable the SOCKS proxy (default: true) # enabled = true -## Address and port service will listen on -address = 127.0.0.1 -port = 4447 -## Optional keys file for proxy local destination +## Address and port service will listen on (default: 127.0.0.1:4447) +# address = 127.0.0.1 +# port = 4447 +## Optional keys file for proxy local destination (default: socks-proxy-keys.dat) # keys = socks-proxy-keys.dat ## Socks outproxy. Example below is set to use Tor for all connections except i2p -## Uncomment and set to 'true' to enable using of SOCKS outproxy +## Enable using of SOCKS outproxy (works only with SOCKS4, default: false) # outproxy.enabled = false ## Address and port of outproxy # outproxy = 127.0.0.1 @@ -161,33 +169,34 @@ port = 4447 ## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. [sam] -## Comment or set to 'false' to disable SAM Bridge -enabled = true -## Address and port service will listen on +## Enable the SAM bridge (default: true) +# enabled = false +## Address and ports service will listen on (default: 127.0.0.1:7656, udp: 7655) # address = 127.0.0.1 # port = 7656 +# portudp = 7655 [bob] -## Uncomment and set to 'true' to enable BOB command channel +## Enable the BOB command channel (default: false) # enabled = false -## Address and port service will listen on +## Address and port service will listen on (default: 127.0.0.1:2827) # address = 127.0.0.1 # port = 2827 [i2cp] -## Uncomment and set to 'true' to enable I2CP protocol +## Enable the I2CP protocol (default: false) # enabled = false -## Address and port service will listen on +## Address and port service will listen on (default: 127.0.0.1:7654) # address = 127.0.0.1 # port = 7654 [i2pcontrol] -## Uncomment and set to 'true' to enable I2PControl protocol +## Enable the I2PControl protocol (default: false) # enabled = false -## Address and port service will listen on +## Address and port service will listen on (default: 127.0.0.1:7650) # address = 127.0.0.1 # port = 7650 -## Authentication password. "itoopie" by default +## Authentication password (default: itoopie) # password = itoopie [precomputation] @@ -198,11 +207,11 @@ enabled = true [upnp] ## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) # enabled = false -## Name i2pd appears in UPnP forwardings list (default = I2Pd) +## Name i2pd appears in UPnP forwardings list (default: I2Pd) # name = I2Pd [meshnets] -## Enable connectivity over the Yggdrasil network +## Enable connectivity over the Yggdrasil network (default: false) # yggdrasil = false ## You can bind address from your Yggdrasil subnet 300::/64 ## The address must first be added to the network interface @@ -210,13 +219,13 @@ enabled = true [reseed] ## Options for bootstrapping into I2P network, aka reseeding -## Enable or disable reseed data verification. +## Enable reseed data verification (default: true) verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ ## Reseed URLs through the Yggdrasil, separated by comma -# yggurls = http://[324:9de3:fea4:f6ac::ace]:7070/ +# yggurls = http://[324:71e:281a:9ed3::ace]:7070/ ## Path to local reseed data file (.su3) for manual reseeding # file = /path/to/i2pseeds.su3 ## or HTTPS URL to reseed from @@ -226,7 +235,7 @@ verify = true ## If you run i2pd behind a proxy server, set proxy server for reseeding here ## Should be http://address:port or socks://address:port # proxy = http://127.0.0.1:8118 -## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default +## Minimum number of known routers, below which i2pd triggers reseeding (default: 25) # threshold = 25 [addressbook] @@ -234,24 +243,25 @@ verify = true ## Default: reg.i2p at "mainline" I2P Network # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt ## Optional subscriptions URLs, separated by comma -# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt +# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt [limits] -## Maximum active transit sessions (default: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) # coresize = 0 [trust] -## Enable explicit trust options. false by default +## Enable explicit trust options. (default: false) # enabled = true ## Make direct I2P connections only to routers in specified Family. # family = MyFamily ## Make direct I2P connections only to routers specified here. Comma separated list of base64 identities. # routers = -## Should we hide our router from other routers? false by default +## Should we hide our router from other routers? (default: false) # hidden = true [exploratory] @@ -267,11 +277,3 @@ verify = true ## Save full addresses on disk (default: true) # addressbook = true -[cpuext] -## Use CPU AES-NI instructions set when work with cryptography when available (default: true) -# aesni = true -## Use CPU AVX instructions set when work with cryptography when available (default: true) -# avx = true -## Force usage of CPU instructions set, even if they not found -## DO NOT TOUCH that option if you really don't know what are you doing! -# force = false diff --git a/contrib/i2pd.service b/contrib/i2pd.service index 45fe4cc6..1ab46979 100644 --- a/contrib/i2pd.service +++ b/contrib/i2pd.service @@ -1,7 +1,8 @@ [Unit] Description=I2P Router written in C++ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ -After=network.target +Wants=network.target +After=network.target network-online.target [Service] User=i2pd @@ -11,7 +12,7 @@ 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 +ExecStart=/usr/bin/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" PIDFile=/run/i2pd/i2pd.pid ### Uncomment, if auto restart needed @@ -29,7 +30,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/openrc/i2pd.openrc b/contrib/openrc/i2pd.openrc index deca4625..0233eed8 100644 --- a/contrib/openrc/i2pd.openrc +++ b/contrib/openrc/i2pd.openrc @@ -7,7 +7,7 @@ tunconf="/etc/i2pd/tunnels.conf" tundir="/etc/i2pd/tunnels.conf.d" name="i2pd" -command="/usr/sbin/i2pd" +command="/usr/bin/i2pd" command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --tunnelsdir=$tundir --pidfile=$pidfile" description="i2p router written in C++" required_dirs="/var/lib/i2pd" diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 95a64402..2083ba18 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.43.0 +Version: 2.56.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -24,13 +24,19 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units +%if 0%{?fedora} == 41 +BuildRequires: openssl-devel-engine +%endif + Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd + %description C++ implementation of I2P. + %prep %setup -q -n i2pd-openssl @@ -38,76 +44,60 @@ C++ implementation of I2P. %build cd build %if 0%{?rhel} == 7 -%cmake3 \ + %cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else -%cmake \ + %cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -%if 0%{?fedora} > 29 + %if 0%{?fedora} > 29 -DBUILD_SHARED_LIBS:BOOL=OFF \ . -%else + %else -DBUILD_SHARED_LIBS:BOOL=OFF -%endif + %endif %endif - -%if 0%{?rhel} == 9 -pushd redhat-linux-build -%endif - -%if 0%{?fedora} >= 35 -pushd redhat-linux-build +%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} + pushd redhat-linux-build %else -%if 0%{?fedora} >= 33 -pushd %{_target_platform} -%endif -%endif + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif -%if 0%{?mageia} > 7 -pushd build + %if 0%{?mageia} > 7 + pushd build + %endif %endif make %{?_smp_mflags} -%if 0%{?rhel} == 9 -popd +%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 + popd %endif -%if 0%{?fedora} >= 33 -popd -%endif - -%if 0%{?mageia} > 7 -popd -%endif %install pushd build -%if 0%{?rhel} == 9 -pushd redhat-linux-build -%endif - -%if 0%{?fedora} >= 35 -pushd redhat-linux-build +%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} + pushd redhat-linux-build %else -%if 0%{?fedora} >= 33 -pushd %{_target_platform} -%endif -%endif + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif -%if 0%{?mageia} -pushd build + %if 0%{?mageia} + pushd build + %endif %endif chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd +%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd @@ -143,7 +133,7 @@ getent passwd i2pd >/dev/null || \ %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_sbindir}/i2pd +%{_bindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt @@ -158,6 +148,60 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 11 2025 orignal - 2.56.0 +- update to 2.56.0 + +* Mon Dec 30 2024 orignal - 2.55.0 +- update to 2.55.0 + +* Sun Oct 6 2024 orignal - 2.54.0 +- update to 2.54.0 + +* Tue Jul 30 2024 orignal - 2.53.1 +- update to 2.53.1 + +* Fri Jul 19 2024 orignal - 2.53.0 +- update to 2.53.0 + +* Sun May 12 2024 orignal - 2.52.0 +- update to 2.52.0 + +* Sat Apr 06 2024 orignal - 2.51.0 +- update to 2.51.0 + +* Sat Jan 06 2024 orignal - 2.50.2 +- update to 2.50.2 + +* Sat Dec 23 2023 r4sas - 2.50.1 +- update to 2.50.1 + +* Mon Dec 18 2023 orignal - 2.50.0 +- update to 2.50.0 + +* Mon Sep 18 2023 orignal - 2.49.0 +- update to 2.49.0 + +* Mon Jun 12 2023 orignal - 2.48.0 +- update to 2.48.0 + +* Sat Mar 11 2023 orignal - 2.47.0 +- update to 2.47.0 + +* 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 + * Mon Aug 22 2022 orignal - 2.43.0 - update to 2.43.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index eda2914e..4eb558ba 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.43.0 +Version: 2.56.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -22,13 +22,19 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units +%if 0%{?fedora} == 41 +BuildRequires: openssl-devel-engine +%endif + Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd + %description C++ implementation of I2P. + %prep %setup -q @@ -36,75 +42,60 @@ C++ implementation of I2P. %build cd build %if 0%{?rhel} == 7 -%cmake3 \ + %cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else -%cmake \ + %cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -%if 0%{?fedora} > 29 + %if 0%{?fedora} > 29 -DBUILD_SHARED_LIBS:BOOL=OFF \ . -%else + %else -DBUILD_SHARED_LIBS:BOOL=OFF -%endif + %endif %endif -%if 0%{?rhel} == 9 -pushd redhat-linux-build -%endif - -%if 0%{?fedora} >= 35 -pushd redhat-linux-build +%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} + pushd redhat-linux-build %else -%if 0%{?fedora} >= 33 -pushd %{_target_platform} -%endif -%endif + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif -%if 0%{?mageia} > 7 -pushd build + %if 0%{?mageia} > 7 + pushd build + %endif %endif make %{?_smp_mflags} -%if 0%{?rhel} == 9 -popd +%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 + popd %endif -%if 0%{?fedora} >= 33 -popd -%endif - -%if 0%{?mageia} > 7 -popd -%endif %install pushd build -%if 0%{?rhel} == 9 -pushd redhat-linux-build -%endif - -%if 0%{?fedora} >= 35 -pushd redhat-linux-build +%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} + pushd redhat-linux-build %else -%if 0%{?fedora} >= 33 -pushd %{_target_platform} -%endif -%endif + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif -%if 0%{?mageia} -pushd build + %if 0%{?mageia} + pushd build + %endif %endif chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd +%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd @@ -140,7 +131,7 @@ getent passwd i2pd >/dev/null || \ %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_sbindir}/i2pd +%{_bindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt @@ -155,6 +146,60 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Feb 11 2025 orignal - 2.56.0 +- update to 2.56.0 + +* Mon Dec 30 2024 orignal - 2.55.0 +- update to 2.55.0 + +* Sun Oct 6 2024 orignal - 2.54.0 +- update to 2.54.0 + +* Tue Jul 30 2024 orignal - 2.53.1 +- update to 2.53.1 + +* Fri Jul 19 2024 orignal - 2.53.0 +- update to 2.53.0 + +* Sun May 12 2024 orignal - 2.52.0 +- update to 2.52.0 + +* Sat Apr 06 2024 orignal - 2.51.0 +- update to 2.51.0 + +* Sat Jan 06 2024 orignal - 2.50.2 +- update to 2.50.2 + +* Sat Dec 23 2023 r4sas - 2.50.1 +- update to 2.50.1 + +* Mon Dec 18 2023 orignal - 2.50.0 +- update to 2.50.0 + +* Mon Sep 18 2023 orignal - 2.49.0 +- update to 2.49.0 + +* Mon Jun 12 2023 orignal - 2.48.0 +- update to 2.48.0 + +* Sat Mar 11 2023 orignal - 2.47.0 +- update to 2.47.0 + +* 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 + * Mon Aug 22 2022 orignal - 2.43.0 - update to 2.43.0 diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index 55723c43..fc455e79 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -5,6 +5,7 @@ port = 6668 destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat +i2p.streaming.profile=2 #[IRC-IRC2P] #type = client diff --git a/contrib/upstart/i2pd.upstart b/contrib/upstart/i2pd.upstart index 19b58958..d2cd4d5e 100644 --- a/contrib/upstart/i2pd.upstart +++ b/contrib/upstart/i2pd.upstart @@ -8,4 +8,4 @@ env LOGFILE="/var/log/i2pd/i2pd.log" expect fork -exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE +exec /usr/bin/i2pd --daemon --service --log=file --logfile=$LOGFILE 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 0b69f511..e2fdf2d4 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,7 +31,6 @@ #include "Crypto.h" #include "UPnP.h" #include "Timestamp.h" -#include "util.h" #include "I18N.h" namespace i2p @@ -150,142 +149,44 @@ namespace util LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); - bool avx; i2p::config::GetOption("cpuext.avx", avx); - bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); bool ssu; i2p::config::GetOption("ssu", ssu); if (!ssu && i2p::config::IsDefault ("precomputation.elgamal")) precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly - i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); + i2p::crypto::InitCrypto (precomputation); + + i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); + + bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); + i2p::transport::transports.SetCheckReserved(checkReserved); + i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - - // ifname -> address - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ipv4 && i2p::config::IsDefault ("address4")) - { - std::string ifname4; i2p::config::GetOption("ifname4", ifname4); - if (!ifname4.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 - else if (!ifname.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 - } - if (ipv6 && i2p::config::IsDefault ("address6")) - { - std::string ifname6; i2p::config::GetOption("ifname6", ifname6); - if (!ifname6.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 - else if (!ifname.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 - } - - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - boost::asio::ip::address_v6 yggaddr; - if (ygg) - { - std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); - if (!yggaddress.empty ()) - { - yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); - if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || - !i2p::util::net::IsLocalAddress (yggaddr)) - { - LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); - ygg = false; - } - } - else - { - yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (yggaddr.is_unspecified ()) - { - LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); - ygg = false; - } - } - } - - uint16_t port; i2p::config::GetOption("port", port); - if (!i2p::config::IsDefault("port")) - { - LogPrint(eLogInfo, "Daemon: Accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); - } - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - i2p::context.SetSupportsMesh (ygg, yggaddr); - - i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - if (!ntcp2proxy.empty ()) published = false; - } - if (published) - { - uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); - if (!ntcp2port) ntcp2port = port; // use standard port - i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish - if (ipv6) - { - std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); - auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); - if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) - i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured - } - } - else - i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish - } - if (ygg) - { - i2p::context.PublishNTCP2Address (port, true, false, false, true); - i2p::context.UpdateNTCP2V6Address (yggaddr); - if (!ipv4 && !ipv6) - i2p::context.SetStatus (eRouterStatusMesh); - } - if (!ssu) i2p::context.RemoveSSUAddress (); // TODO: remove later - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) - { - uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); - if (!ssu2port) ssu2port = ssu ? (port + 1) : port; - bool published; i2p::config::GetOption("ssu2.published", published); - if (published) - i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish - else - i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish - } - - bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetAcceptsTunnels (!transit); - uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); - SetMaxNumTransitTunnels (transitTunnels); + i2p::transport::InitTransports (); 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); + uint32_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 + i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels); /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); if (bandwidth.length () > 0) { - if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') + if (bandwidth.length () == 1 && ((bandwidth[0] >= 'K' && bandwidth[0] <= 'P') || bandwidth[0] == 'X' )) { i2p::context.SetBandwidth (bandwidth[0]); LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); @@ -369,7 +270,7 @@ namespace util if (hidden) { LogPrint(eLogInfo, "Daemon: Hidden mode enabled"); - i2p::data::netdb.SetHidden(true); + i2p::context.SetHidden(true); } std::string httpLang; i2p::config::GetOption("http.lang", httpLang); @@ -399,19 +300,16 @@ namespace util bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - bool ssu; i2p::config::GetOption("ssu", ssu); - bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); LogPrint(eLogInfo, "Daemon: Starting Transports"); - if(!ssu) LogPrint(eLogInfo, "Daemon: SSU disabled"); + if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled"); if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled"); - i2p::transport::transports.SetCheckReserved(checkInReserved); - i2p::transport::transports.Start(ntcp2, ssu, ssu2); - if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2()) + i2p::transport::transports.Start(ntcp2, ssu2); + if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); else { - LogPrint(eLogError, "Daemon: Failed to start Transports"); + LogPrint(eLogCritical, "Daemon: Failed to start Transports"); /** shut down netdb right away */ i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); @@ -430,15 +328,17 @@ namespace util } catch (std::exception& ex) { - LogPrint (eLogError, "Daemon: Failed to start Webconsole: ", ex.what ()); + LogPrint (eLogCritical, "Daemon: Failed to start Webconsole: ", ex.what ()); ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); } } - LogPrint(eLogInfo, "Daemon: Starting Tunnels"); i2p::tunnel::tunnels.Start(); + LogPrint(eLogInfo, "Daemon: Starting Router context"); + i2p::context.Start(); + LogPrint(eLogInfo, "Daemon: Starting Client"); i2p::client::context.Start (); @@ -455,7 +355,7 @@ namespace util } catch (std::exception& ex) { - LogPrint (eLogError, "Daemon: Failed to start I2PControl: ", ex.what ()); + LogPrint (eLogCritical, "Daemon: Failed to start I2PControl: ", ex.what ()); ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); } } @@ -467,6 +367,8 @@ namespace util LogPrint(eLogInfo, "Daemon: Shutting down"); LogPrint(eLogInfo, "Daemon: Stopping Client"); i2p::client::context.Stop(); + LogPrint(eLogInfo, "Daemon: Stopping Router context"); + i2p::context.Stop(); LogPrint(eLogInfo, "Daemon: Stopping Tunnels"); i2p::tunnel::tunnels.Stop(); diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index d236ca3d..dca545fe 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -80,15 +80,14 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; + const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; 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"; + const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; static std::string ConvertTime (uint64_t time) { @@ -105,18 +104,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,40 +123,38 @@ 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) { - std::string state, stateText; - switch (eState) { + std::string state; + std::string_view stateText; + switch (eState) + { case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStatePending : state = "building"; break; - case i2p::tunnel::eTunnelStateBuildFailed : - case i2p::tunnel::eTunnelStateTestFailed : + case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; + case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } - - if (state == "building") stateText = tr("building"); - else if (state == "failed") stateText = tr("failed"); - else if (state == "expiring") stateText = tr("expiring"); - else if (state == "established") stateText = tr("established"); - else stateText = tr("unknown"); - - s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; - s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; + if (stateText.empty ()) stateText = tr(state); + + s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: + ShowTraffic(s, bytes); + s << "\r\n"; } static void SetLogLevel (const std::string& level) { - if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") + if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); @@ -200,7 +197,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"; @@ -217,41 +214,46 @@ namespace http { "\r\n"; } - static void ShowError(std::stringstream& s, const std::string& string) + static void ShowError(std::stringstream& s, std::string_view string) { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, RouterError error) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) { switch (status) { case eRouterStatusOK: s << tr("OK"); break; - case eRouterStatusTesting: s << tr("Testing"); break; case eRouterStatusFirewalled: s << tr("Firewalled"); break; case eRouterStatusUnknown: s << tr("Unknown"); break; case eRouterStatusProxy: s << tr("Proxy"); break; case eRouterStatusMesh: s << tr("Mesh"); break; - case eRouterStatusError: - { - s << tr("Error"); - switch (error) - { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); - break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - default: ; - } - break; - } default: s << tr("Unknown"); } + if (testing) + s << " (" << tr("Testing") << ")"; + if (error != eRouterErrorNone) + { + switch (error) + { + case eRouterErrorClockSkew: + s << " - " << tr("Clock skew"); + break; + case eRouterErrorOffline: + s << " - " << tr("Offline"); + break; + case eRouterErrorSymmetricNAT: + s << " - " << tr("Symmetric NAT"); + break; + case eRouterErrorFullConeNAT: + s << " - " << tr("Full cone NAT"); + break; + case eRouterErrorNoDescriptors: + s << " - " << tr("No Descriptors"); + break; + default: ; + } + } } void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) @@ -260,12 +262,12 @@ namespace http { ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetError ()); + ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); s << "
\r\n"; if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetErrorV6 ()); + ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); s << "
\r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) @@ -286,60 +288,69 @@ 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.GetInBandwidth () / 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.GetOutBandwidth () / 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.GetTransitBandwidth () / 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()) s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; s << "" << tr("Version") << ": " VERSION "
\r\n"; s << ""<< tr("Our external address") << ":" << "
\r\n\r\n"; - for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) + auto addresses = i2p::context.GetRouterInfo().GetAddresses (); + if (addresses) { - s << "\r\n\r\n"; - if (address->published) - s << "\r\n"; - else - { - s << "\r\n\r\n"; + if (address->published) + s << "\r\n"; + else + { + s << "\r\n"; + } + s << "\r\n"; } - s << "\r\n"; } s << "
"; - switch (address->transportStyle) + for (const auto& address : *addresses) { - case i2p::data::RouterInfo::eTransportNTCP: - s << "NTCP2"; - break; - case i2p::data::RouterInfo::eTransportSSU: - s << "SSU"; - break; - case i2p::data::RouterInfo::eTransportSSU2: - s << "SSU2"; - break; - default: - s << tr("Unknown"); - } - if (address->IsV6 ()) - { - if (address->IsV4 ()) s << "v4"; - s << "v6"; - } - s << "" << address->host.to_string() << ":" << address->port << "" << tr("supported"); - if (address->port) - s << " :" << address->port; + if (!address) continue; + s << "
"; + switch (address->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + s << "NTCP2"; + break; + case i2p::data::RouterInfo::eTransportSSU2: + s << "SSU2"; + break; + default: + s << tr("Unknown"); + } + bool v6 = address->IsV6 (); + if (v6) + { + if (address->IsV4 ()) s << "v4"; + s << "v6"; + } s << "" << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":") << address->port << "" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported"); + if (address->port) + s << " :" << address->port; + s << "
\r\n"; } @@ -407,10 +418,23 @@ namespace http { } } + static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident) + { + auto identHash = ident.GetIdentHash(); + auto router = i2p::data::netdb.FindRouter(identHash); + s << i2p::data::GetIdentHashAbbreviation(identHash); + if (router) + s << " " << router->GetBandwidthCap() << ""; + } + static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) { + s << "Base32:
\r\n
\r\n
\r\n"; + s << "Base64:
\r\n
\r\n
\r\n"; + if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); @@ -422,12 +446,11 @@ namespace http { if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - auto base32 = dest->GetIdentHash ().ToBase32 (); s << "
\r\n\r\n
\r\n" "
\r\n" " \r\n" " \r\n" - " \r\n" + " GetIdentHash ().ToBase32 () << "\">\r\n" " " << tr("Domain") << ":\r\n\r\n" " \r\n" "
\r\n" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n
\r\n
\r\n
\r\n"; @@ -436,9 +459,23 @@ namespace http { if (dest->GetNumRemoteLeaseSets()) { s << "
\r\n\r\n
\r\n"; + << "\r\n\r\n
\r\n" + << "
"<< tr("Address") << "" << tr("Type") << "" << tr("EncType") << "
" + << "" + << "" // LeaseSet expiration button column + << "" + << "" + << ""; for(auto& it: dest->GetLeaseSets ()) - s << "\r\n"; + { + s << "" + << "" + << "" + << "" + << "" + << "\r\n"; + } s << "
" << tr("Address") << " " << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n
\r\n
\r\n
\r\n"; } else s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; @@ -455,13 +492,15 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; } ); } 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"; } @@ -476,27 +515,33 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; } ); } 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"; @@ -511,8 +556,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"; @@ -535,7 +583,7 @@ namespace http { << tr("Streams") << "\r\n\r\n" << "StreamID" - << "" // Stream closing button column + << " " // Stream closing button column << "Destination" << "Sent" << "Received" @@ -572,6 +620,8 @@ namespace http { } s << "\r\n"; } + else + ShowError(s, tr("Such destination is not found")); } void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) @@ -606,7 +656,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()) @@ -637,7 +690,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 { @@ -649,6 +702,7 @@ namespace http { { s << "" << tr("Tunnels") << ":
\r\n"; s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n
\r\n"; + s << "" << tr("TBM Queue size") << ": " << i2p::tunnel::tunnels.GetTBMQueueSize () << "
\r\n
\r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); @@ -660,13 +714,15 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; } ); } 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"; } @@ -681,12 +737,14 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; } ); } 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"; } @@ -699,8 +757,7 @@ namespace http { s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; s << " " << tr("Run peer test") << "
\r\n"; - - // s << " Reload config
\r\n"; + s << " " << tr("Reload tunnels configuration") << "
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
\r\n"; @@ -725,19 +782,21 @@ 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 << " critical \r\n"; + s << " error \r\n"; + s << " warn \r\n"; + s << " info \r\n"; + s << " debug
\r\n
\r\n"; - uint16_t maxTunnels = GetMaxNumTransitTunnels (); + uint32_t maxTunnels = i2p::tunnel::tunnels.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"; @@ -767,56 +826,71 @@ 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") << "" << tr("Next") << "
" << 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 << "" << it->GetNextPeerName () << "
\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 () << "]"; + if (it->IsSlow ()) tmp_s << " [slow]"; 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++; } @@ -845,46 +919,6 @@ namespace http { if (!sessions.empty ()) ShowTransportSessions (s, sessions, "NTCP2"); } - auto ssuServer = i2p::transport::transports.GetSSUServer (); - if (ssuServer) - { - auto sessions = ssuServer->GetSessions (); - if (!sessions.empty ()) - { - s << "
\r\n\r\n
"; - for (const auto& it: sessions) - { - s << "
\r\n"; - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - } - auto sessions6 = ssuServer->GetSessionsV6 (); - if (!sessions6.empty ()) - { - s << "
\r\n\r\n
"; - for (const auto& it: sessions6) - { - s << "
\r\n"; - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - } - } auto ssu2Server = i2p::transport::transports.GetSSU2Server (); if (ssu2Server) { @@ -916,7 +950,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) @@ -960,34 +994,42 @@ namespace http { void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } + + auto& clientTunnels = i2p::client::context.GetClientTunnels (); auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "HTTP " << tr("Proxy") << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) + if (!clientTunnels.empty () || httpProxy || socksProxy) { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "SOCKS " << tr("Proxy") << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; + s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; + if (!clientTunnels.empty ()) + { + for (auto& it: clientTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + } + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "HTTP " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "SOCKS " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\n"; } - s << "
\r\n"; auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { @@ -1025,7 +1067,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; @@ -1221,7 +1263,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, tr("Unknown page") + ": " + page); + ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO return; } } @@ -1235,7 +1277,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 ()) @@ -1247,7 +1289,7 @@ namespace http { std::string cmd = params["cmd"]; if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); - else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) + else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); @@ -1309,20 +1351,50 @@ 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_EXPIRELEASE) + { + std::string b32 = params["b32"]; + std::string lease = params["lease"]; + + i2p::data::IdentHash ident, leaseident; + ident.FromBase32 (b32); + leaseident.FromBase32 (lease); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (dest) + { + auto leaseset = dest->FindLeaseSet (leaseident); + if (leaseset) + { + leaseset->ExpireLease (); + s << "" << tr("SUCCESS") << ": " << tr("LeaseSet expiration time updated") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("LeaseSet is not found or already expired") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; + + s << "" << tr("Return to destination page") << "
\r\n"; + 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) - SetMaxNumTransitTunnels (limit); + if (limit > 0 && limit <= TRANSIT_TUNNELS_LIMIT) + i2p::tunnel::tunnels.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; } @@ -1347,13 +1419,11 @@ namespace http { { auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); uint8_t * signature = new uint8_t[signatureLen]; - char * sig = new char[signatureLen*2]; std::stringstream out; out << name << "=" << dest->GetIdentity ()->ToBase64 (); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); - auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); - sig[len] = 0; + auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); out << "#!sig=" << sig; s << "" << tr("SUCCESS") << ":
\r\n

\r\n" "\r\n
\r\n
\r\n" @@ -1362,7 +1432,6 @@ namespace http { "\r\n" "
\r\n
\r\n"; delete[] signature; - delete[] sig; } else s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
\r\n
\r\n"; @@ -1391,13 +1460,13 @@ namespace http { else { res.code = 400; - ShowError(s, tr("Unknown command") + ": " + cmd); + ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO return; } 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()); } @@ -1410,13 +1479,13 @@ namespace http { reply.body = content; m_SendBuffer = reply.to_string(); - boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), + boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } HTTPServer::HTTPServer (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)), + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)), m_Hostname(address) { } diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 8646253f..38b790d4 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 = 1000000; class HTTPConnection: public std::enable_shared_from_this { @@ -81,8 +83,8 @@ namespace http bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_service m_Service; - boost::asio::io_service::work m_Work; + boost::asio::io_context m_Service; + boost::asio::executor_work_guard m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; std::string m_Hostname; }; 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/I2PControl.cpp b/daemon/I2PControl.cpp index 46a219bf..6261a14c 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,31 +8,20 @@ #include #include +#include #include #include // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS - -#include -#include -#include -#include #include -#include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" -#include "RouterContext.h" -#include "Daemon.h" #include "Tunnel.h" -#include "Timestamp.h" -#include "Transports.h" -#include "version.h" -#include "util.h" -#include "ClientContext.h" +#include "Daemon.h" #include "I2PControl.h" namespace i2p @@ -40,11 +29,24 @@ namespace i2p namespace client { I2PControlService::I2PControlService (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), + m_IsRunning (false), m_SSLContext (boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { + if (port) + m_Acceptor = std::make_unique(m_Service, + boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)); + else +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + { + std::remove (address.c_str ()); // just in case + m_LocalAcceptor = std::make_unique(m_Service, + boost::asio::local::stream_protocol::endpoint(address)); + } +#else + LogPrint(eLogError, "I2PControl: Local sockets are not supported"); +#endif + i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate / keys @@ -55,58 +57,46 @@ namespace client i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt); if (i2pcp_key.at(0) != '/') i2pcp_key = i2p::fs::DataDirPath(i2pcp_key); - if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) { + if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) + { LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); - } else { + } + else LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt); - } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); - m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem); - m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); + boost::system::error_code ec; + m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec); + if (!ec) + m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec); + if (ec) + { + LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating"); + CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); + m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec); + if (!ec) + m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec); + if (ec) + // give up + LogPrint (eLogError, "I2PControl: Can't load certificates"); + } // handlers m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlHandlers::RouterInfoHandler; m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; + m_MethodHandlers["NetworkSetting"] = &I2PControlHandlers::NetworkSettingHandler; + m_MethodHandlers["ClientServicesInfo"] = &I2PControlHandlers::ClientServicesInfoHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; - // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; - m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; - m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; - // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; - - // NetworkSetting - m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; - m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; - - // ClientServicesInfo - m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; - m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () @@ -120,7 +110,7 @@ namespace client { Accept (); m_IsRunning = true; - m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); + m_Thread = std::make_unique(std::bind (&I2PControlService::Run, this)); } } @@ -129,12 +119,19 @@ namespace client if (m_IsRunning) { m_IsRunning = false; - m_Acceptor.cancel (); + if (m_Acceptor) m_Acceptor->cancel (); +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + if (m_LocalAcceptor) + { + auto path = m_LocalAcceptor->local_endpoint().path(); + m_LocalAcceptor->cancel (); + std::remove (path.c_str ()); + } +#endif m_Service.stop (); if (m_Thread) { m_Thread->join (); - delete m_Thread; m_Thread = nullptr; } } @@ -156,40 +153,60 @@ namespace client void I2PControlService::Accept () { - auto newSocket = std::make_shared (m_Service, m_SSLContext); - m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, - std::placeholders::_1, newSocket)); + if (m_Acceptor) + { + auto newSocket = std::make_shared > (m_Service, m_SSLContext); + m_Acceptor->async_accept (newSocket->lowest_layer(), + [this, newSocket](const boost::system::error_code& ecode) + { + HandleAccepted (ecode, newSocket); + }); + } +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + else if (m_LocalAcceptor) + { + auto newSocket = std::make_shared > (m_Service, m_SSLContext); + m_LocalAcceptor->async_accept (newSocket->lowest_layer(), + [this, newSocket](const boost::system::error_code& ecode) + { + HandleAccepted (ecode, newSocket); + }); + } +#endif } - void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + template + void I2PControlService::HandleAccepted (const boost::system::error_code& ecode, + std::shared_ptr newSocket) { if (ecode != boost::asio::error::operation_aborted) Accept (); - if (ecode) { + if (ecode) + { LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); return; } - LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ()); - Handshake (socket); - } - + LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ()); + Handshake (newSocket); + } + + template void I2PControlService::Handshake (std::shared_ptr socket) { socket->async_handshake(boost::asio::ssl::stream_base::server, - std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket)); - } - - void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) - { - if (ecode) { - LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); - return; - } - //std::this_thread::sleep_for (std::chrono::milliseconds(5)); - ReadRequest (socket); + [this, socket](const boost::system::error_code& ecode) + { + if (ecode) + { + LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); + return; + } + ReadRequest (socket); + }); } + template void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); @@ -199,10 +216,13 @@ namespace client #else boost::asio::buffer (request->data (), request->size ()), #endif - std::bind(&I2PControlService::HandleRequestReceived, this, - std::placeholders::_1, std::placeholders::_2, socket, request)); + [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) + { + HandleRequestReceived (ecode, bytes_transferred, socket, request); + }); } + template void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) @@ -280,37 +300,7 @@ namespace client } } - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const - { - ss << "\"" << name << "\":" << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const - { - ss << "\"" << name << "\":"; - if (value.length () > 0) - { - if (quotes) - ss << "\"" << value << "\""; - else - ss << value; - } - else - ss << "null"; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const - { - ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const - { - std::ostringstream buf; - boost::property_tree::write_json (buf, value, false); - ss << "\"" << name << "\":" << buf.str(); - } - + template void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -320,12 +310,12 @@ namespace client std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; - header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; + header << "Content-Length: " << std::to_string(len) << "\r\n"; header << "Content-Type: application/json\r\n"; header << "Date: "; - auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); - header.imbue(std::locale (header.getloc(), facet)); - header << boost::posix_time::second_clock::local_time() << "\r\n"; + std::time_t t = std::time (nullptr); + std::tm tm = *std::gmtime (&t); + header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); @@ -333,16 +323,11 @@ namespace client memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), - std::bind(&I2PControlService::HandleResponseSent, this, - std::placeholders::_1, std::placeholders::_2, socket, buf)); - } - - void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf) - { - if (ecode) { - LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); - } + [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); + }); } // handlers @@ -396,91 +381,6 @@ namespace client m_Tokens.clear (); } -// RouterInfo - - void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - bool first = true; - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); - auto it1 = m_RouterInfoHandlers.find (it->first); - if (it1 != m_RouterInfoHandlers.end ()) - { - if (!first) results << ","; - else first = false; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); - } - } - - void I2PControlService::UptimeHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); - } - - void I2PControlService::VersionHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.version", VERSION); - } - - void I2PControlService::StatusHandler (std::ostringstream& results) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); - } - - void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); - } - - void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); - } - - void I2PControlService::NetStatusHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); - } - - void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) - { - int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); - InsertParam (results, "i2p.router.net.tunnels.participating", transit); - } - - void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) - { - int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); - InsertParam (results, "i2p.router.net.tunnels.successrate", rate); - } - - void I2PControlService::InboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth (); - InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); - } - - void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth (); - InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); - } - - void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); - } - - void I2PControlService::NetTotalSentBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); - } - // RouterManager @@ -488,10 +388,11 @@ namespace client { for (auto it = params.begin (); it != params.end (); it++) { - if (it != params.begin ()) results << ","; LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); auto it1 = m_RouterManagerHandlers.find (it->first); - if (it1 != m_RouterManagerHandlers.end ()) { + if (it1 != m_RouterManagerHandlers.end ()) + { + if (it != params.begin ()) results << ","; (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); @@ -532,37 +433,6 @@ namespace client i2p::data::netdb.Reseed (); } -// network setting - void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); - auto it1 = m_NetworkSettingHandlers.find (it->first); - if (it1 != m_NetworkSettingHandlers.end ()) { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(it->second.data (), results); - } else - LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); - } - } - - void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.in", bw); - } - - void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.out", bw); - } - // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { @@ -585,7 +455,7 @@ namespace client X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves - X509_sign (x509, pkey, EVP_sha1 ()); // sign + X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA // save cert if ((f = fopen (crt_path, "wb")) != NULL) { @@ -611,178 +481,5 @@ namespace client } EVP_PKEY_free (pkey); } - -// ClientServicesInfo - - void I2PControlService::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); - auto it1 = m_ClientServicesInfoHandlers.find (it->first); - if (it1 != m_ClientServicesInfoHandlers.end ()) - { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); - } - } - - void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - boost::property_tree::ptree client_tunnels, server_tunnels; - - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - - auto& serverTunnels = i2p::client::context.GetServerTunnels (); - if (!serverTunnels.empty ()) { - for (auto& it: serverTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - st.put("port", it.second->GetLocalPort ()); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - auto& clientForwards = i2p::client::context.GetClientForwards (); - if (!clientForwards.empty ()) - { - for (auto& it: clientForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - } - - auto& serverForwards = i2p::client::context.GetServerForwards (); - if (!serverForwards.empty ()) - { - for (auto& it: serverForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - pt.add_child("client", client_tunnels); - pt.add_child("server", server_tunnels); - - InsertParam (results, "I2PTunnel", pt); - } - - void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "HTTPProxy", pt); - } - - void I2PControlService::SOCKSInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) - { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "SOCKS", pt); - } - - void I2PControlService::SAMInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - pt.put("enabled", true); - boost::property_tree::ptree sam_sessions; - for (auto& it: sam->GetSessions ()) - { - boost::property_tree::ptree sam_session, sam_session_sockets; - auto& name = it.second->GetLocalDestination ()->GetNickname (); - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - sam_session.put("name", name); - sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - - for (const auto& socket: sam->ListSockets(it.first)) - { - boost::property_tree::ptree stream; - stream.put("type", socket->GetSocketType ()); - stream.put("peer", socket->GetSocket ().remote_endpoint()); - - sam_session_sockets.push_back(std::make_pair("", stream)); - } - sam_session.add_child("sockets", sam_session_sockets); - sam_sessions.add_child(it.first, sam_session); - } - - pt.add_child("sessions", sam_sessions); - } - else - pt.put("enabled", false); - - InsertParam (results, "SAM", pt); - } - - void I2PControlService::BOBInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto bob = i2p::client::context.GetBOBCommandChannel (); - if (bob) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "BOB", pt); - } - - void I2PControlService::I2CPInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto i2cp = i2p::client::context.GetI2CPServer (); - if (i2cp) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "I2CP", pt); - } } } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index 32b8933c..83dd6549 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,6 +20,7 @@ #include #include #include +#include "I2PControlHandlers.h" namespace i2p { @@ -32,10 +33,8 @@ namespace client const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; - class I2PControlService + class I2PControlService: public I2PControlHandlers { - typedef boost::asio::ssl::stream ssl_socket; - public: I2PControlService (const std::string& address, int port); @@ -48,94 +47,59 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + template + void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr newSocket); + template void Handshake (std::shared_ptr socket); - void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); + template void ReadRequest (std::shared_ptr socket); + template void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + template void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); - void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); private: - void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes = true) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; - // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); - // RouterInfo - typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); - void UptimeHandler (std::ostringstream& results); - void VersionHandler (std::ostringstream& results); - void StatusHandler (std::ostringstream& results); - void NetDbKnownPeersHandler (std::ostringstream& results); - void NetDbActivePeersHandler (std::ostringstream& results); - void NetStatusHandler (std::ostringstream& results); - void TunnelsParticipatingHandler (std::ostringstream& results); - void TunnelsSuccessRateHandler (std::ostringstream& results); - void InboundBandwidth1S (std::ostringstream& results); - void OutboundBandwidth1S (std::ostringstream& results); - void NetTotalReceivedBytes (std::ostringstream& results); - void NetTotalSentBytes (std::ostringstream& results); - // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); - // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); - void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); - void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); - - // ClientServicesInfo - typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results); - void I2PTunnelInfoHandler (std::ostringstream& results); - void HTTPProxyInfoHandler (std::ostringstream& results); - void SOCKSInfoHandler (std::ostringstream& results); - void SAMInfoHandler (std::ostringstream& results); - void BOBInfoHandler (std::ostringstream& results); - void I2CPInfoHandler (std::ostringstream& results); - private: std::string m_Password; bool m_IsRunning; - std::thread * m_Thread; + std::unique_ptr m_Thread; - boost::asio::io_service m_Service; - boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::io_context m_Service; + std::unique_ptr m_Acceptor; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + std::unique_ptr m_LocalAcceptor; +#endif boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; std::map m_MethodHandlers; std::map m_I2PControlHandlers; - std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; - std::map m_NetworkSettingHandlers; - std::map m_ClientServicesInfoHandlers; }; } } diff --git a/daemon/I2PControlHandlers.cpp b/daemon/I2PControlHandlers.cpp new file mode 100644 index 00000000..f3ea7f61 --- /dev/null +++ b/daemon/I2PControlHandlers.cpp @@ -0,0 +1,390 @@ +/* +* Copyright (c) 2013-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 +*/ + +#include +#define BOOST_BIND_GLOBAL_PLACEHOLDERS +#include +#include + +#include "Log.h" +#include "RouterContext.h" +#include "NetDb.hpp" +#include "Tunnel.h" +#include "Transports.h" +#include "version.h" +#include "ClientContext.h" +#include "I2PControlHandlers.h" + +namespace i2p +{ +namespace client +{ + I2PControlHandlers::I2PControlHandlers () + { + // RouterInfo + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlHandlers::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlHandlers::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlHandlers::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlHandlers::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlHandlers::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlHandlers::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"] = &I2PControlHandlers::InboundBandwidth15S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"] = &I2PControlHandlers::OutboundBandwidth15S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlHandlers::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlHandlers::TunnelsSuccessRateHandler; + m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlHandlers::NetTotalReceivedBytes; + m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlHandlers::NetTotalSentBytes; + + // NetworkSetting + m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlHandlers::InboundBandwidthLimit; + m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlHandlers::OutboundBandwidthLimit; + + // ClientServicesInfo + m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlHandlers::I2PTunnelInfoHandler; + m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlHandlers::HTTPProxyInfoHandler; + m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlHandlers::SOCKSInfoHandler; + m_ClientServicesInfoHandlers["SAM"] = &I2PControlHandlers::SAMInfoHandler; + m_ClientServicesInfoHandlers["BOB"] = &I2PControlHandlers::BOBInfoHandler; + m_ClientServicesInfoHandlers["I2CP"] = &I2PControlHandlers::I2CPInfoHandler; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, int value) const + { + ss << "\"" << name << "\":" << value; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const + { + ss << "\"" << name << "\":"; + if (value.length () > 0) + { + if (quotes) + ss << "\"" << value << "\""; + else + ss << value; + } + else + ss << "null"; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, double value) const + { + ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const + { + std::ostringstream buf; + boost::property_tree::write_json (buf, value, false); + ss << "\"" << name << "\":" << buf.str(); + } + +// RouterInfo + + void I2PControlHandlers::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + bool first = true; + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); + auto it1 = m_RouterInfoHandlers.find (it->first); + if (it1 != m_RouterInfoHandlers.end ()) + { + if (!first) results << ","; + else first = false; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); + } + } + + void I2PControlHandlers::UptimeHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); + } + + void I2PControlHandlers::VersionHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.version", VERSION); + } + + void I2PControlHandlers::StatusHandler (std::ostringstream& results) + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); + } + + void I2PControlHandlers::NetDbKnownPeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); + } + + void I2PControlHandlers::NetDbActivePeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); + } + + void I2PControlHandlers::NetStatusHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); + } + + void I2PControlHandlers::TunnelsParticipatingHandler (std::ostringstream& results) + { + int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); + InsertParam (results, "i2p.router.net.tunnels.participating", transit); + } + + void I2PControlHandlers::TunnelsSuccessRateHandler (std::ostringstream& results) + { + int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); + InsertParam (results, "i2p.router.net.tunnels.successrate", rate); + } + + void I2PControlHandlers::InboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetInBandwidth (); + InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); + } + + void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetInBandwidth15s (); + InsertParam (results, "i2p.router.net.bw.inbound.15s", bw); + } + + void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth (); + InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); + } + + void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth15s (); + InsertParam (results, "i2p.router.net.bw.outbound.15s", bw); + } + + void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); + } + + void I2PControlHandlers::NetTotalSentBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); + } + +// network setting + void I2PControlHandlers::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); + auto it1 = m_NetworkSettingHandlers.find (it->first); + if (it1 != m_NetworkSettingHandlers.end ()) { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(it->second.data (), results); + } else + LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); + } + } + + void I2PControlHandlers::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.in", bw); + } + + void I2PControlHandlers::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.out", bw); + } + +// ClientServicesInfo + + void I2PControlHandlers::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); + auto it1 = m_ClientServicesInfoHandlers.find (it->first); + if (it1 != m_ClientServicesInfoHandlers.end ()) + { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); + } + } + + void I2PControlHandlers::I2PTunnelInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + boost::property_tree::ptree client_tunnels, server_tunnels; + + for (auto& it: i2p::client::context.GetClientTunnels ()) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + + auto& serverTunnels = i2p::client::context.GetServerTunnels (); + if (!serverTunnels.empty ()) { + for (auto& it: serverTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + st.put("port", it.second->GetLocalPort ()); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + } + + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + pt.add_child("client", client_tunnels); + pt.add_child("server", server_tunnels); + + InsertParam (results, "I2PTunnel", pt); + } + + void I2PControlHandlers::HTTPProxyInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto httpProxy = i2p::client::context.GetHttpProxy (); + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "HTTPProxy", pt); + } + + void I2PControlHandlers::SOCKSInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto socksProxy = i2p::client::context.GetSocksProxy (); + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "SOCKS", pt); + } + + void I2PControlHandlers::SAMInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + pt.put("enabled", true); + boost::property_tree::ptree sam_sessions; + for (auto& it: sam->GetSessions ()) + { + boost::property_tree::ptree sam_session, sam_session_sockets; + auto& name = it.second->GetLocalDestination ()->GetNickname (); + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + sam_session.put("name", name); + sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + + for (const auto& socket: sam->ListSockets(it.first)) + { + boost::property_tree::ptree stream; + stream.put("type", socket->GetSocketType ()); + stream.put("peer", socket->GetSocket ().remote_endpoint()); + + sam_session_sockets.push_back(std::make_pair("", stream)); + } + sam_session.add_child("sockets", sam_session_sockets); + sam_sessions.add_child(it.first, sam_session); + } + + pt.add_child("sessions", sam_sessions); + } + else + pt.put("enabled", false); + + InsertParam (results, "SAM", pt); + } + + void I2PControlHandlers::BOBInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto bob = i2p::client::context.GetBOBCommandChannel (); + if (bob) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "BOB", pt); + } + + void I2PControlHandlers::I2CPInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto i2cp = i2p::client::context.GetI2CPServer (); + if (i2cp) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "I2CP", pt); + } +} +} diff --git a/daemon/I2PControlHandlers.h b/daemon/I2PControlHandlers.h new file mode 100644 index 00000000..d106f288 --- /dev/null +++ b/daemon/I2PControlHandlers.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2013-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 +*/ + +#ifndef I2P_CONTROL_HANDLERS_H__ +#define I2P_CONTROL_HANDLERS_H__ + +#include +#include +#include +#include + +namespace i2p +{ +namespace client +{ + class I2PControlHandlers + { + public: + + I2PControlHandlers (); + + // methods + // TODO: make protected + void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + + protected: + + void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes = true) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; + + private: + + // RouterInfo + typedef void (I2PControlHandlers::*RouterInfoRequestHandler)(std::ostringstream& results); + void UptimeHandler (std::ostringstream& results); + void VersionHandler (std::ostringstream& results); + void StatusHandler (std::ostringstream& results); + void NetDbKnownPeersHandler (std::ostringstream& results); + void NetDbActivePeersHandler (std::ostringstream& results); + void NetStatusHandler (std::ostringstream& results); + void TunnelsParticipatingHandler (std::ostringstream& results); + void TunnelsSuccessRateHandler (std::ostringstream& results); + void InboundBandwidth1S (std::ostringstream& results); + void InboundBandwidth15S (std::ostringstream& results); + void OutboundBandwidth1S (std::ostringstream& results); + void OutboundBandwidth15S (std::ostringstream& results); + void NetTotalReceivedBytes (std::ostringstream& results); + void NetTotalSentBytes (std::ostringstream& results); + + // NetworkSetting + typedef void (I2PControlHandlers::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); + void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); + + // ClientServicesInfo + typedef void (I2PControlHandlers::*ClientServicesInfoRequestHandler)(std::ostringstream& results); + void I2PTunnelInfoHandler (std::ostringstream& results); + void HTTPProxyInfoHandler (std::ostringstream& results); + void SOCKSInfoHandler (std::ostringstream& results); + void SAMInfoHandler (std::ostringstream& results); + void BOBInfoHandler (std::ostringstream& results); + void I2CPInfoHandler (std::ostringstream& results); + + private: + + std::map m_RouterInfoHandlers; + std::map m_NetworkSettingHandlers; + std::map m_ClientServicesInfoHandlers; + }; +} +} + +#endif diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 6ea4dc24..8e6dbcf6 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,10 +1,15 @@ +/* +* Copyright (c) 2013-2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifdef USE_UPNP #include #include -#include -#include - #include "Log.h" #include "RouterContext.h" @@ -47,7 +52,7 @@ namespace transport { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: Starting"); - m_Service.post (std::bind (&UPnP::Discover, this)); + boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum @@ -110,10 +115,16 @@ namespace transport return; } +#if (MINIUPNPC_API_VERSION >= 18) + err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr), + m_externalIPAddress, sizeof (m_externalIPAddress)); +#else err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); +#endif m_upnpUrlsInitialized=err!=0; if (err == UPNP_IGD_VALID_CONNECTED) { +#if (MINIUPNPC_API_VERSION < 18) err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(err != UPNPCOMMAND_SUCCESS) { @@ -121,6 +132,7 @@ namespace transport return; } else +#endif { LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) @@ -138,7 +150,7 @@ namespace transport // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); + i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress)); // port mapping PortMapping (); } @@ -159,17 +171,18 @@ namespace transport void UPnP::PortMapping () { - const auto& a = context.GetRouterInfo().GetAddresses(); - for (const auto& address : a) + auto a = context.GetRouterInfo().GetAddresses(); + 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 + m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) - PortMapping (); + PortMapping (); }); } @@ -210,10 +223,11 @@ namespace transport void UPnP::CloseMapping () { - const auto& a = context.GetRouterInfo().GetAddresses(); - for (const auto& address : a) + auto a = context.GetRouterInfo().GetAddresses(); + 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); } } @@ -248,10 +262,10 @@ namespace transport { switch (address->transportStyle) { - case i2p::data::RouterInfo::eTransportNTCP: + case i2p::data::RouterInfo::eTransportNTCP2: return "TCP"; break; - case i2p::data::RouterInfo::eTransportSSU: + case i2p::data::RouterInfo::eTransportSSU2: default: return "UDP"; } diff --git a/daemon/UPnP.h b/daemon/UPnP.h index 59f3b785..2a5fe9f3 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,7 +28,8 @@ namespace i2p namespace transport { const int UPNP_RESPONSE_TIMEOUT = 2000; // in milliseconds - + const int UPNP_PORT_FORWARDING_INTERVAL = 20; // in minutes + enum { UPNP_IGD_NONE = 0, @@ -66,7 +67,7 @@ namespace transport std::unique_ptr m_Thread; std::condition_variable m_Started; std::mutex m_StartedMutex; - boost::asio::io_service m_Service; + boost::asio::io_context m_Service; boost::asio::deadline_timer m_Timer; bool m_upnpUrlsInitialized = false; struct UPNPUrls m_upnpUrls; diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index e6bad5a0..66661e0f 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -25,6 +25,7 @@ #include "RouterContext.h" #include "ClientContext.h" #include "Transports.h" +#include "util.h" void handle_signal(int sig) { @@ -162,18 +163,27 @@ namespace i2p #ifndef ANDROID if (lockf(pidFH, F_TLOCK, 0) != 0) +#else + struct flock fl; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + + if (fcntl(pidFH, F_SETLK, &fl) != 0) +#endif { LogPrint(eLogError, "Daemon: Could not lock pid file ", pidfile, ": ", strerror(errno)); std::cerr << "i2pd: Could not lock pid file " << pidfile << ": " << strerror(errno) << std::endl; return false; } -#endif + char pid[10]; sprintf(pid, "%d\n", getpid()); ftruncate(pidFH, 0); if (write(pidFH, pid, strlen(pid)) < 0) { - LogPrint(eLogError, "Daemon: Could not write pidfile ", pidfile, ": ", strerror(errno)); + LogPrint(eLogCritical, "Daemon: Could not write pidfile ", pidfile, ": ", strerror(errno)); std::cerr << "i2pd: Could not write pidfile " << pidfile << ": " << strerror(errno) << std::endl; return false; } @@ -211,6 +221,7 @@ namespace i2p void DaemonLinux::run () { + i2p::util::SetThreadName ("i2pd-daemon"); while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); diff --git a/debian/NEWS b/debian/NEWS new file mode 100644 index 00000000..c0add110 --- /dev/null +++ b/debian/NEWS @@ -0,0 +1,5 @@ +i2pd (2.53.0-1) unstable; urgency=medium + + i2pd binary moved from /usr/sbin to /usr/bin. Please check your scripts if you used the old path. + + -- r4sas Fri, 19 Jul 2024 16:00:00 +0000 diff --git a/debian/changelog b/debian/changelog index 78ce9472..d170f534 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,123 @@ +i2pd (2.56.0-1) unstable; urgency=medium + + * updated to version 2.56.0/0.9.65 + + -- orignal Tue, 11 Feb 2025 16:00:00 +0000 + +i2pd (2.55.0-1) unstable; urgency=medium + + * updated to version 2.55.0 + + -- orignal Mon, 30 Dec 2024 16:00:00 +0000 + +i2pd (2.54.0-1) unstable; urgency=medium + + * updated to version 2.54.0/0.9.64 + + -- orignal Sun, 6 Oct 2024 16:00:00 +0000 + +i2pd (2.53.1-1) unstable; urgency=medium + + * updated to version 2.53.1 + + -- orignal Tue, 30 Jul 2024 16:00:00 +0000 + +i2pd (2.53.0-1) unstable; urgency=medium + + * updated to version 2.53.0/0.9.63 + * binary moved from /usr/sbin to /usr/bin + + -- r4sas Sat, 20 Jul 2024 15:10:00 +0000 + +i2pd (2.52.0-1) unstable; urgency=medium + + * updated to version 2.52.0 + + -- orignal Sun, 12 May 2024 16:00:00 +0000 + +i2pd (2.51.0-1) unstable; urgency=medium + + * updated to version 2.51.0/0.9.62 + + -- orignal Sat, 06 Apr 2024 16:00:00 +0000 + +i2pd (2.50.2-1) unstable; urgency=medium + + * updated to version 2.50.2/0.9.61 + + -- orignal Sat, 06 Jan 2024 16:00:00 +0000 + +i2pd (2.50.1-1) unstable; urgency=medium + + * updated to version 2.50.1/0.9.61 + + -- r4sas Sat, 23 Dec 2023 18:30:00 +0000 + +i2pd (2.50.0-1) unstable; urgency=medium + + * updated to version 2.50.0/0.9.61 + + -- orignal Mon, 18 Dec 2023 16:00:00 +0000 + +i2pd (2.49.0-1) unstable; urgency=medium + + * updated to version 2.49.0/0.9.60 + + -- orignal Mon, 18 Sep 2023 16:00:00 +0000 + +i2pd (2.48.0-1) unstable; urgency=high + + * updated to version 2.48.0/0.9.59 + + -- orignal Mon, 12 Jun 2023 16:00:00 +0000 + +i2pd (2.47.0-1) unstable; urgency=high + + * updated to version 2.47.0/0.9.58 + + -- orignal Sat, 11 Mar 2023 16:00:00 +0000 + +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 + + -- orignal Sun, 20 Nov 2022 19:00:00 +0000 + i2pd (2.43.0-1) unstable; urgency=medium * updated to version 2.43.0/0.9.55 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.1 b/debian/i2pd.1 index 5145321f..42c282d9 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -64,7 +64,7 @@ The network interface to bind to for IPv4 connections The network interface to bind to for IPv6 connections .TP \fB\-\-ipv4=\fR -Enable communication through ipv6 (\fIenabled\fR by default) +Enable communication through ipv4 (\fIenabled\fR by default) .TP \fB\-\-ipv6\fR Enable communication through ipv6 (\fIdisabled\fR by default) 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.init b/debian/i2pd.init index 33fd80a5..9b5f7669 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -13,7 +13,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=i2pd # Introduce a short description here NAME=i2pd # Introduce the short server's name here -DAEMON=/usr/sbin/$NAME # Introduce the server's location here +DAEMON=/usr/bin/$NAME # Introduce the server's location here DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME/$NAME.pid I2PCONF=/etc/$NAME/i2pd.conf diff --git a/debian/i2pd.install b/debian/i2pd.install index 6eb6c8c2..bde52854 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,7 +1,6 @@ -i2pd usr/sbin/ +i2pd usr/bin/ 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 +contrib/apparmor/usr.bin.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/01-upnp.patch b/debian/patches/01-upnp.patch new file mode 100644 index 00000000..74d36c06 --- /dev/null +++ b/debian/patches/01-upnp.patch @@ -0,0 +1,17 @@ +Description: Enable UPnP usage in package +Author: r4sas + +Reviewed-By: r4sas +Last-Update: 2024-12-30 + +--- i2pd.orig/Makefile ++++ i2pd/Makefile +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk + + USE_STATIC := $(or $(USE_STATIC),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) + + # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string 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..b69c42ef 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace afrikaans // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"failed", "Het misluk"}, {"unknown", "onbekend"}, @@ -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..67955d8a 100644 --- a/i18n/Armenian.cpp +++ b/i18n/Armenian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,17 +29,18 @@ namespace armenian // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings 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 b933df3d..e3b63ebd 100644 --- a/i18n/Chinese.cpp +++ b/i18n/Chinese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, 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 { @@ -30,23 +29,23 @@ namespace chinese // language namespace return 0; } - static std::map strings + static const LocaleStrings strings { - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, {"building", "正在构建"}, {"failed", "连接失败"}, {"expiring", "即将过期"}, - {"established", "连接已建立"}, + {"established", "连接成功"}, {"unknown", "未知"}, - {"exploratory", "探测"}, + {"exploratory", "探索"}, {"Purple I2P Webconsole", "Purple I2P 网页控制台"}, {"i2pd webconsole", "i2pd 网页控制台"}, {"Main page", "主页"}, {"Router commands", "路由命令"}, {"Local Destinations", "本地目标"}, - {"LeaseSets", "租契集"}, + {"LeaseSets", "租约集"}, {"Tunnels", "隧道"}, {"Transit Tunnels", "中转隧道"}, {"Transports", "传输"}, @@ -58,19 +57,21 @@ 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", "网络状态"}, {"Network status v6", "IPv6 网络状态"}, {"Stopping in", "距停止还有:"}, {"Family", "家族"}, {"Tunnel creation success rate", "隧道创建成功率"}, + {"Total tunnel creation success rate", "当前隧道创建成功率"}, {"Received", "已接收"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "已发送"}, {"Transit", "中转"}, {"Data path", "数据文件路径"}, @@ -91,12 +92,13 @@ 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", "加密类型"}, + {"Expire LeaseSet", "到期租约集"}, {"Inbound tunnels", "入站隧道"}, - {"ms", "毫秒"}, + {"%dms", "%dms"}, {"Outbound tunnels", "出站隧道"}, {"Tags", "标签"}, {"Incoming", "传入"}, @@ -109,6 +111,7 @@ namespace chinese // language namespace {"Local Destination", "本地目标"}, {"Streams", "流"}, {"Close stream", "断开流"}, + {"Such destination is not found", "找不到此目标"}, {"I2CP session not found", "未找到 I2CP 会话"}, {"I2CP is not enabled", "I2CP 未启用"}, {"Invalid", "无效"}, @@ -118,20 +121,21 @@ namespace chinese // language namespace {"Gateway", "网关"}, {"TunnelID", "隧道 ID"}, {"EndDate", "结束日期"}, - {"not floodfill", "非洪泛"}, + {"floodfill mode is disabled", "洪泛已禁用"}, {"Queue size", "队列大小"}, - {"Run peer test", "运行群体测试"}, + {"Run peer test", "运行节点测试"}, + {"Reload tunnels configuration", "重新载入隧道配置"}, {"Decline transit tunnels", "拒绝中转隧道"}, {"Accept transit tunnels", "允许中转隧道"}, - {"Cancel graceful shutdown", "取消离线"}, - {"Start graceful shutdown", "优雅地离线"}, + {"Cancel graceful shutdown", "取消平滑关闭"}, + {"Start graceful shutdown", "平滑关闭"}, {"Force shutdown", "强制停止"}, {"Reload external CSS styles", "重载外部 CSS 样式"}, {"Note: any action done here are not persistent and not changes your config files.", "注意: 此处完成的任何操作都不是永久的,不会更改您的配置文件。"}, {"Logging level", "日志级别"}, {"Transit tunnels limit", "中转隧道限制"}, - {"Change", "更换"}, - {"Change language", "更换语言"}, + {"Change", "修改"}, + {"Change language", "更改语言"}, {"no transit tunnels currently built", "目前未构建中转隧道"}, {"SAM disabled", "SAM 已禁用"}, {"no sessions currently running", "没有正在运行的会话"}, @@ -141,15 +145,17 @@ namespace chinese // language namespace {"Client Forwards", "客户端转发"}, {"Server Forwards", "服务器转发"}, {"Unknown page", "未知页面"}, - {"Invalid token", "无效凭证"}, + {"Invalid token", "无效令牌"}, {"SUCCESS", "成功"}, {"Stream closed", "流已关闭"}, {"Stream not found or already was closed", "流未找到或已关闭"}, {"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秒内被重定向"}, + {"LeaseSet expiration time updated", "租约集到期时间已更新"}, + {"LeaseSet is not found or already expired", "租约集未找到或已过期"}, + {"Transit tunnels count must not exceed %d", "中转隧道数量限制为 %d"}, {"Back to commands list", "返回命令列表"}, {"Register at reg.i2p", "在 reg.i2p 注册域名"}, {"Description", "描述"}, @@ -157,42 +163,42 @@ namespace chinese // language namespace {"Submit", "提交"}, {"Domain can't end with .b32.i2p", "域名不能以 .b32.i2p 结尾"}, {"Domain must end with .i2p", "域名必须以 .i2p 结尾"}, - {"Such destination is not found", "找不到此目标"}, {"Unknown command", "未知指令"}, {"Command accepted", "已接受指令"}, {"Proxy error", "代理错误"}, {"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", "您可以尝试在下方的跳转服务上找到这个主机"}, + {"Proxy error: Host not found", "代理错误:未找到主机"}, + {"Remote host not found in router's addressbook", "在路由地址簿中未找到远程主机"}, + {"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 代理协商"}, + {"Outproxy failure", "出口代理故障"}, + {"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 +206,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..94803354 --- /dev/null +++ b/i18n/Czech.cpp @@ -0,0 +1,223 @@ +/* +* Copyright (c) 2022-2025, 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 const LocaleStrings strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "vytváří se"}, + {"failed", "selhalo"}, + {"expiring", "vyprší platnost"}, + {"established", "vytvořeno"}, + {"unknown", "neznámý"}, + {"exploratory", "průzkumné"}, + {"Purple I2P Webconsole", "Purple I2P webová konzole"}, + {"i2pd webconsole", "i2pd webová konzole"}, + {"Main page", "Hlavní stránka"}, + {"Router commands", "Router příkazy"}, + {"Local Destinations", "Místní cíle"}, + {"LeaseSets", "Sety pronájmu"}, + {"Tunnels", "Tunely"}, + {"Transit Tunnels", "Tranzitní 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"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Žádné popisovače"}, + {"Uptime", "Doba provozu"}, + {"Network status", "Stav sítě"}, + {"Network status v6", "Stav sítě v6"}, + {"Stopping in", "Zastavuji za"}, + {"Family", "Rodina"}, + {"Tunnel creation success rate", "Úspěšnost vytváření tunelů"}, + {"Total tunnel creation success rate", "Celková míra úspěšnosti vytváření tunelů"}, + {"Received", "Přijato"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Odesláno"}, + {"Transit", "Tranzit"}, + {"Data path", "Cesta k datovým souborům"}, + {"Hidden content. Press on text to see.", "Skrytý obsah. Pro zobrazení klikněte sem."}, + {"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"}, + {"Expire LeaseSet", "Zrušit platnost setu pronájmu"}, + {"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", "Stav"}, + {"Local Destination", "Místní cíl"}, + {"Streams", "Toky"}, + {"Close stream", "Uzavřít tok"}, + {"Such destination is not found", "Takováto destinace nebyla nalezena"}, + {"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", "Pronájmy, kterým nevypršela platnost"}, + {"Gateway", "Brána"}, + {"TunnelID", "ID tunelu"}, + {"EndDate", "Datum ukončení"}, + {"floodfill mode is disabled", "režim floodfill je vypnut"}, + {"Queue size", "Velikost fronty"}, + {"Run peer test", "Spustit peer test"}, + {"Reload tunnels configuration", "Znovu načíst nastavení tunelů"}, + {"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í"}, + {"You will be redirected in %d seconds", "Budete přesměrováni za %d sekund"}, + {"LeaseSet expiration time updated", "Aktualizován čas vypršení platnosti setu pronájmu"}, + {"LeaseSet is not found or already expired", "Set pronájmu není k nalezení nebo již vypršela jeho platnost"}, + {"Transit tunnels count must not exceed %d", "Počet tranzitních tunelů nesmí překročit %d"}, + {"Back to commands list", "Zpět na seznam 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"}, + {"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"}, + {"Addresshelper is not supported", "Addresshelper není podporován"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Buďte opatrní: zdroj této URL může být škodlivý! Klikněte zde pro aktualizaci záznamu: Pokračovat."}, + {"Addresshelper forced update rejected", "Addresshelperem vynucená aktualizace zamítnuta"}, + {"To add host %s in router's addressbook, click here: Continue.", "Pro přidání hostitele %s do adresáře routeru, klikněte zde: Pokračovat."}, + {"Addresshelper request", "Požadavek Addresshelperu"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Hostitel %s přidán do adresáře routeru od pomocníka. Klikněte zde pro pokračování: Pokračovat."}, + {"Addresshelper adding", "Addresshelper přidávání"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Klikněte zde pro aktualizaci záznamu: Pokračovat."}, + {"Addresshelper update", "Addresshelper aktualizace"}, + {"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/English.cpp b/i18n/English.cpp index 2670e984..fb774527 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,7 +30,7 @@ namespace english // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { {"", ""}, }; diff --git a/i18n/French.cpp b/i18n/French.cpp index 1a49ddfc..985296a3 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,11 +29,11 @@ namespace french // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings 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,18 +58,20 @@ namespace french // language namespace {"Unknown", "Inconnu"}, {"Proxy", "Proxy"}, {"Mesh", "Maillé"}, - {"Error", "Erreur"}, - {"Clock skew", "Horloge décalée"}, + {"Clock skew", "Décalage de l'horloge"}, {"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"}, {"Stopping in", "Arrêt dans"}, {"Family", "Famille"}, - {"Tunnel creation success rate", "Taux de succès de création de tunnels"}, + {"Tunnel creation success rate", "Taux de création de tunnel réussie"}, + {"Total tunnel creation success rate", "Taux total de création de tunnel réussie"}, {"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 +83,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é"}, @@ -89,11 +92,13 @@ namespace french // language namespace {"Address registration line", "Ligne d'inscription de l'adresse"}, {"Domain", "Domaine"}, {"Generate", "Générer"}, - {"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."}, + {"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"}, + {"Expire LeaseSet", "Expirer le jeu de baux"}, {"Inbound tunnels", "Tunnels entrants"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Tunnels sortants"}, {"Tags", "Balises"}, {"Incoming", "Entrant"}, @@ -106,6 +111,7 @@ namespace french // language namespace {"Local Destination", "Destination locale"}, {"Streams", "Flux"}, {"Close stream", "Fermer le flux"}, + {"Such destination is not found", "Cette destination est introuvable"}, {"I2CP session not found", "Session I2CP introuvable"}, {"I2CP is not enabled", "I2CP est désactivé"}, {"Invalid", "Invalide"}, @@ -115,15 +121,17 @@ 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"}, {"Start graceful shutdown", "Démarrer l'arrêt gracieux"}, {"Force shutdown", "Forcer l'arrêt"}, {"Reload external CSS styles", "Rafraîchir les styles CSS externes"}, - {"Note: any action done here are not persistent and not changes your config files.", "Note: Toute action effectuée ici n'est pas permanente et ne modifie pas vos fichiers de configuration."}, + {"Note: any action done here are not persistent and not changes your config files.", "Note : Toute action effectuée ici n'est pas permanente et ne modifie pas vos fichiers de configuration."}, {"Logging level", "Niveau de journalisation"}, {"Transit tunnels limit", "Limite sur les tunnels transitoires"}, {"Change", "Changer"}, @@ -134,6 +142,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 +152,10 @@ 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"}, + {"LeaseSet expiration time updated", "Temps d'expiration du jeu de baux mis à jour"}, + {"LeaseSet is not found or already expired", "Le jeu de baux est introuvable ou a déjà expiré"}, + {"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"}, @@ -151,42 +163,42 @@ namespace french // language namespace {"Submit", "Soumettre"}, {"Domain can't end with .b32.i2p", "Le domaine ne peut pas terminer par .b32.i2p"}, {"Domain must end with .i2p", "Le domaine doit terminer par .i2p"}, - {"Such destination is not found", "Cette destination est introuvable"}, {"Unknown command", "Commande inconnue"}, {"Command accepted", "Commande acceptée"}, {"Proxy error", "Erreur de proxy"}, {"Proxy info", "Information sur le proxy"}, - {"Proxy error: Host not found", "Erreur de proxy: Hôte introuvable"}, + {"Proxy error: Host not found", "Erreur de proxy : Hôte introuvable"}, {"Remote host not found in router's addressbook", "Hôte distant introuvable dans le carnet d'adresse du routeur"}, {"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 +206,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 f114bf8e..90ce82f4 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,18 +29,18 @@ namespace german // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings strings { - {"Purple I2P Webconsole", "Purple-I2P-Webkonsole"}, - {"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"}, {"established", "hergestellt"}, {"unknown", "Unbekannt"}, {"exploratory", "erforschend"}, + {"Purple I2P Webconsole", "Purple I2P-Webkonsole"}, {"i2pd webconsole", "i2pd-Webkonsole"}, {"Main page", "Startseite"}, {"Router commands", "Routerbefehle"}, @@ -49,8 +49,8 @@ namespace german // language namespace {"Tunnels", "Tunnel"}, {"Transit Tunnels", "Transittunnel"}, {"Transports", "Transporte"}, - {"I2P tunnels", "I2P-Tunnel"}, - {"SAM sessions", "SAM-Sitzungen"}, + {"I2P tunnels", "I2P Tunnel"}, + {"SAM sessions", "SAM Sitzungen"}, {"ERROR", "FEHLER"}, {"OK", "OK"}, {"Testing", "Testen"}, @@ -58,10 +58,10 @@ namespace german // language namespace {"Unknown", "Unbekannt"}, {"Proxy", "Proxy"}, {"Mesh", "Mesh"}, - {"Error", "Fehler"}, {"Clock skew", "Zeitabweichung"}, {"Offline", "Offline"}, {"Symmetric NAT", "Symmetrisches NAT"}, + {"No Descriptors", "Keine Beschreibungen"}, {"Uptime", "Laufzeit"}, {"Network status", "Netzwerkstatus"}, {"Network status v6", "Netzwerkstatus v6"}, @@ -69,7 +69,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 +95,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,9 +117,10 @@ namespace german // language namespace {"Gateway", "Gateway"}, {"TunnelID", "TunnelID"}, {"EndDate", "Enddatum"}, - {"not floodfill", "kein Floodfill"}, + {"floodfill mode is disabled", "Floodfill Modus ist deaktiviert"}, {"Queue size", "Größe der Warteschlange"}, {"Run peer test", "Peer-Test durchführen"}, + {"Reload tunnels configuration", "Tunnel Konfiguration neu laden"}, {"Decline transit tunnels", "Transittunnel ablehnen"}, {"Accept transit tunnels", "Transittunnel akzeptieren"}, {"Cancel graceful shutdown", "Beende das kontrollierte Herunterfahren"}, @@ -147,8 +148,8 @@ 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"}, + {"You will be redirected in %d seconds", "Du wirst umgeleitet in %d Sekunden"}, + {"Transit tunnels count must not exceed %d", "Die Anzahl der Transittunnel darf nicht über %d gehen"}, {"Back to commands list", "Zurück zur Befehlsliste"}, {"Register at reg.i2p", "Auf reg.i2p registrieren"}, {"Description", "Beschreibung"}, @@ -166,32 +167,33 @@ 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"}, + {"Addresshelper is not supported", "Adresshelfer wird nicht unterstützt"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Vorsicht: Die Quelle dieser URL kann schädlich sein! Klicken Sie hier, um den Datensatz zu aktualisieren: Weiter."}, + {"Addresshelper forced update rejected", "Adresshelfer gezwungene Aktualisierung abgelehnt"}, + {"To add host %s in router's addressbook, click here: Continue.", "Um den Host %s im Adressbuch des Routers hinzuzufügen, klicken Sie hier: Weiter."}, + {"Addresshelper request", "Adresshelfer gefunden"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Host %s wurde vom Helfer zum Adressbuch des Routers hinzugefügt. Klicken Sie hier, um fortzufahren: Weiter."}, + {"Addresshelper adding", "Adresshelfer hinzufügen"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Klicken Sie hier, um den Eintrag zu aktualisieren: Weiter."}, + {"Addresshelper update", "Adresshelfer aktualisieren"}, + {"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 +201,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 new file mode 100644 index 00000000..48a02357 --- /dev/null +++ b/i18n/I18N.cpp @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2021-2025, 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" + +namespace i2p +{ +namespace i18n +{ + void SetLanguage(const std::string &lang) + { + 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_view translate (std::string_view arg) + { + return i2p::client::context.GetLanguage ()->GetString (arg); + } + + std::string translate (const std::string& arg, const std::string& arg2, const int n) + { + return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); + } +} // i18n +} // i2p diff --git a/i18n/I18N.h b/i18n/I18N.h index dd804926..8ed77a6b 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,37 +9,129 @@ #ifndef __I18N_H__ #define __I18N_H__ -#include "ClientContext.h" +#include +#include +#include +#include +#include namespace i2p { namespace i18n { - inline void SetLanguage(const std::string &lang) + typedef std::map LocaleStrings; + class Locale { - const auto it = i2p::i18n::languages.find(lang); - if (it == i2p::i18n::languages.end()) // fallback - i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); - else - i2p::client::context.SetLanguage (it->second.LocaleFunc()); - } + public: + Locale ( + const std::string& language, + const LocaleStrings& strings, + const std::map>& plurals, + std::function formula + ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; - inline std::string translate (const std::string& arg) - { - return i2p::client::context.GetLanguage ()->GetString (arg); - } + // Get activated language name for webconsole + std::string GetLanguage() const + { + return m_Language; + } - inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) - { - return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); - } + std::string_view GetString (std::string_view arg) const + { + const auto it = m_Strings.find(arg); + if (it == m_Strings.end()) + { + return arg; + } + else + { + return it->second; + } + } + + std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const + { + const auto it = m_Plurals.find(arg2); + if (it == m_Plurals.end()) // not found, fallback to english + { + return n == 1 ? arg : arg2; + } + else + { + int form = m_Formula(n); + return it->second[form]; + } + } + + private: + const std::string m_Language; + const LocaleStrings m_Strings; + const std::map> m_Plurals; + std::function m_Formula; + }; + + void SetLanguage(const std::string &lang); + std::string_view translate (std::string_view arg); + std::string translate (const std::string& arg, const std::string& arg2, int n); } // i18n } // i2p -template -std::string tr (TArgs&&... args) +/** + * @brief Get translation of string + * @param arg String with message + */ +template +std::string_view 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 = std::string (i2p::i18n::translate(std::forward(arg))); // TODO: + + size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); + std::string str(size, 0); + std::snprintf(&str.front(), size + 1, 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)...); + std::string str(size, 0); + std::snprintf(&str.front(), size + 1, 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 87bc503e..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 * @@ -9,60 +9,12 @@ #ifndef __I18N_LANGS_H__ #define __I18N_LANGS_H__ +#include "I18N.h" + namespace i2p { namespace i18n { - class Locale - { - public: - Locale ( - const std::string& language, - const std::map& strings, - const std::map>& plurals, - std::function formula - ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; - - // Get activated language name for webconsole - std::string GetLanguage() const - { - return m_Language; - } - - std::string GetString (const std::string& arg) const - { - const auto it = m_Strings.find(arg); - if (it == m_Strings.end()) - { - return arg; - } - else - { - return it->second; - } - } - - std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const - { - const auto it = m_Plurals.find(arg2); - if (it == m_Plurals.end()) // not found, fallback to english - { - return n == 1 ? arg : arg2; - } - else - { - int form = m_Formula(n); - return it->second[form]; - } - } - - private: - const std::string m_Language; - const std::map m_Strings; - const std::map> m_Plurals; - std::function m_Formula; - }; - struct langData { std::string LocaleName; // localized name @@ -71,16 +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 russian { 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 @@ -88,14 +47,21 @@ namespace i18n static std::map languages { { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, - { "armenian", {"հայերէն", "hy", i2p::i18n::armenian::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} }, - { "russian", {"русский язык", "ru", i2p::i18n::russian::GetLocale} }, - { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, - { "ukrainian", {"украї́нська мо́ва", "uk", i2p::i18n::ukrainian::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 new file mode 100644 index 00000000..0ae26f21 --- /dev/null +++ b/i18n/Italian.cpp @@ -0,0 +1,223 @@ +/* +* Copyright (c) 2022-2025, 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" + +// Italian localization file + +namespace i2p +{ +namespace i18n +{ +namespace italian // language namespace +{ + // language name in lowercase + static std::string language = "italian"; + + // 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 const LocaleStrings strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "in costruzione"}, + {"failed", "fallito"}, + {"expiring", "in scadenza"}, + {"established", "stabilita"}, + {"unknown", "sconosciuto"}, + {"exploratory", "esplorativo"}, + {"Purple I2P Webconsole", "Terminale web Purple I2P"}, + {"i2pd webconsole", "Terminal web i2pd"}, + {"Main page", "Pagina principale"}, + {"Router commands", "Comandi router"}, + {"Local Destinations", "Destinazioni locali"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnel"}, + {"Transit Tunnels", "Tunnel di transito"}, + {"Transports", "Trasporti"}, + {"I2P tunnels", "Tunnel I2P"}, + {"SAM sessions", "Sessioni SAM"}, + {"ERROR", "ERRORE"}, + {"OK", "OK"}, + {"Testing", "Testando"}, + {"Firewalled", "Protetto da firewall"}, + {"Unknown", "Sconosciuto"}, + {"Proxy", "Proxy"}, + {"Mesh", "Mesh"}, + {"Clock skew", "Orologio disallineato"}, + {"Offline", "Disconnesso"}, + {"Symmetric NAT", "NAT simmetrico"}, + {"Full cone NAT", "Cono completo NAT"}, + {"No Descriptors", "Nessun descrittore"}, + {"Uptime", "In funzione da"}, + {"Network status", "Stato della rete"}, + {"Network status v6", "Stato della rete v6"}, + {"Stopping in", "Arresto in"}, + {"Family", "Famiglia"}, + {"Tunnel creation success rate", "Percentuale di tunnel creati con successo"}, + {"Total tunnel creation success rate", "Percentuale di successo totale nella creazione del tunnel"}, + {"Received", "Ricevuti"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Inviati"}, + {"Transit", "Transitati"}, + {"Data path", "Percorso dati"}, + {"Hidden content. Press on text to see.", "Contenuto nascosto. Premi sul testo per vedere."}, + {"Router Ident", "Identificativo del router"}, + {"Router Family", "Famiglia del router"}, + {"Router Caps", "Limiti del router"}, + {"Version", "Versione"}, + {"Our external address", "Il nostro indirizzo esterno"}, + {"supported", "supportato"}, + {"Routers", "Router"}, + {"Floodfills", "Floodfill"}, + {"Client Tunnels", "Tunnel client"}, + {"Services", "Servizi"}, + {"Enabled", "Abilitato"}, + {"Disabled", "Disabilitato"}, + {"Encrypted B33 address", "Indirizzo criptato B33"}, + {"Address registration line", "Linea di registrazione indirizzo"}, + {"Domain", "Dominio"}, + {"Generate", "Genera"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la stringa risultante può essere utilizzata solo per registrare domini 2LD (example.i2p). Per registrare i sottodomini, si prega di utilizzare i2pd-tools."}, + {"Address", "Indirizzo"}, + {"Type", "Tipologia"}, + {"EncType", "Tipo di crittografia"}, + {"Expire LeaseSet", "Scadenza LeaseSet"}, + {"Inbound tunnels", "Tunnel in entrata"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Tunnel in uscita"}, + {"Tags", "Tag"}, + {"Incoming", "In entrata"}, + {"Outgoing", "In uscita"}, + {"Destination", "Destinazione"}, + {"Amount", "Quantità"}, + {"Incoming Tags", "Tag in entrata"}, + {"Tags sessions", "Sessioni dei tag"}, + {"Status", "Stato"}, + {"Local Destination", "Destinazione locale"}, + {"Streams", "Flussi"}, + {"Close stream", "Interrompi il flusso"}, + {"Such destination is not found", "Questa destinazione non è stata trovata"}, + {"I2CP session not found", "Sessione I2CP non trovata"}, + {"I2CP is not enabled", "I2CP non è abilitato"}, + {"Invalid", "Invalido"}, + {"Store type", "Tipologia di archivio"}, + {"Expires", "Scade"}, + {"Non Expired Leases", "Lease non scaduti"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Data di fine"}, + {"floodfill mode is disabled", "la modalità floodfill è disabilitata"}, + {"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"}, + {"Start graceful shutdown", "Avvia l'interruzione controllata"}, + {"Force shutdown", "Forza l'arresto"}, + {"Reload external CSS styles", "Ricarica gli stili CSS esterni"}, + {"Note: any action done here are not persistent and not changes your config files.", "Nota: qualsiasi azione effettuata qui non è persistente e non modifica i file di configurazione."}, + {"Logging level", "Livello di log"}, + {"Transit tunnels limit", "Limite di tunnel di transito"}, + {"Change", "Modifica"}, + {"Change language", "Modifica linguaggio"}, + {"no transit tunnels currently built", "Attualmente non ci sono tunnel di transito instaurati"}, + {"SAM disabled", "SAM disabilitato"}, + {"no sessions currently running", "Attualmente non ci sono sessioni attive"}, + {"SAM session not found", "Sessione SAM non trovata"}, + {"SAM Session", "Sessione SAM"}, + {"Server Tunnels", "Tunnel server"}, + {"Client Forwards", "Client di inoltro"}, + {"Server Forwards", "Server di inoltro"}, + {"Unknown page", "Pagina sconosciuta"}, + {"Invalid token", "Token non valido"}, + {"SUCCESS", "SUCCESSO"}, + {"Stream closed", "Flusso terminato"}, + {"Stream not found or already was closed", "Il flusso non è stato trovato oppure è già stato terminato"}, + {"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 %d seconds", "Sarai reindirizzato tra %d secondi"}, + {"LeaseSet expiration time updated", "Tempo di scadenza LeaseSet aggiornato"}, + {"LeaseSet is not found or already expired", "LeaseSet non trovato o già scaduto"}, + {"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"}, + {"A bit information about service on domain", "Alcune informazioni riguardo il servizio sul dominio"}, + {"Submit", "Invia"}, + {"Domain can't end with .b32.i2p", "I domini non possono terminare con .b32.i2p"}, + {"Domain must end with .i2p", "I domini devono terminare con .i2p"}, + {"Unknown command", "Comando sconosciuto"}, + {"Command accepted", "Comando accettato"}, + {"Proxy error", "Errore del proxy"}, + {"Proxy info", "Informazioni del proxy"}, + {"Proxy error: Host not found", "Errore del proxy: Host non trovato"}, + {"Remote host not found in router's addressbook", "L'host remoto non è stato trovato nella rubrica del router"}, + {"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 %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Attenzione: la fonte di questo URL potrebbe essere dannosa! Fai clic qui per aggiornare il record: Continua."}, + {"Addresshelper forced update rejected", "Aggiornamento forzato dell'helper degli indirizzi rifiutato"}, + {"To add host %s in router's addressbook, click here: Continue.", "Per aggiungere host %s nella rubrica del router, clicca qui: Continua."}, + {"Addresshelper request", "Richiesta di indirizzo helper"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'host %s viene aggiunto alla rubrica del router dall'helper. Fai clic qui per procedere: Continua."}, + {"Addresshelper adding", "Aggiunta di Addresshelper"}, + {"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."}, + {"Addresshelper update", "Aggiornamento dell'helper degli indirizzi"}, + {"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"}, + {"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 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."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d giorno", "%d giorni"}}, + {"%d hours", {"%d ora", "%d ore"}}, + {"%d minutes", {"%d minuto", "%d minuti"}}, + {"%d seconds", {"%d secondo", "%d secondi"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp new file mode 100644 index 00000000..0e8df096 --- /dev/null +++ b/i18n/Polish.cpp @@ -0,0 +1,223 @@ +/* +* Copyright (c) 2023-2025, 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 const LocaleStrings strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "Kompilowanie"}, + {"failed", "nieudane"}, + {"expiring", "wygasający"}, + {"established", "ustanowiony"}, + {"unknown", "nieznany"}, + {"exploratory", "eksploracyjny"}, + {"Purple I2P Webconsole", "Konsola webowa Purple I2P"}, + {"i2pd webconsole", "i2pd konsola webowa"}, + {"Main page", "Strona główna"}, + {"Router commands", "Komendy routera"}, + {"Local Destinations", "Lokalne miejsca docelowe"}, + {"LeaseSets", "ZestawyNajmu"}, + {"Tunnels", "Tunele"}, + {"Transit Tunnels", "Tunele Tranzytu"}, + {"Transports", "Transportery"}, + {"I2P tunnels", "Tunele I2P"}, + {"SAM sessions", "Sesje SAM"}, + {"ERROR", "BŁĄD"}, + {"OK", "Ok"}, + {"Testing", "Testowanie"}, + {"Firewalled", "Za zaporą sieciową"}, + {"Unknown", "Nieznany"}, + {"Proxy", "Proxy"}, + {"Mesh", "Sieć"}, + {"Clock skew", "Przesunięcie czasu"}, + {"Offline", "Offline"}, + {"Symmetric NAT", "Symetryczny NAT"}, + {"Full cone NAT", "Pełny stożek NAT"}, + {"No Descriptors", "Brak deskryptorów"}, + {"Uptime", "Czas pracy"}, + {"Network status", "Stan sieci"}, + {"Network status v6", "Stan sieci v6"}, + {"Stopping in", "Zatrzymywanie za"}, + {"Family", "Rodzina"}, + {"Tunnel creation success rate", "Wskaźnik sukcesu tworzenia tunelu"}, + {"Total tunnel creation success rate", "Całkowity wskaźnik sukcesu tworzenia tunelu"}, + {"Received", "Odebrano"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Wysłane"}, + {"Transit", "Tranzyt"}, + {"Data path", "Ścieżka do danych"}, + {"Hidden content. Press on text to see.", "Ukryta zawartość. Naciśnij tekst, aby zobaczyć."}, + {"Router Ident", "Identyfikator routera"}, + {"Router Family", "Rodzina routera"}, + {"Router Caps", "Możliwości routera"}, + {"Version", "Wersja"}, + {"Our external address", "Nasz zewnętrzny adres"}, + {"supported", "wspierane"}, + {"Routers", "Routery"}, + {"Floodfills", "Floodfille"}, + {"Client Tunnels", "Tunele Klienta"}, + {"Services", "Usługi"}, + {"Enabled", "Aktywny"}, + {"Disabled", "Wyłączony"}, + {"Encrypted B33 address", "Zaszyfrowany adres B33"}, + {"Address registration line", "Linia rejestracji adresu"}, + {"Domain", "Domena"}, + {"Generate", "Generuj"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uwaga: wynik string może być używany tylko do rejestracji domen 2LD (przykład.i2p). Do rejestracji subdomen należy użyć narzędzi i2pd."}, + {"Address", "Adres"}, + {"Type", "Typ"}, + {"EncType", "TypEnkrypcji"}, + {"Expire LeaseSet", "Wygaśnij LeaseSet"}, + {"Inbound tunnels", "Tunele przychodzące"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Tunele wychodzące"}, + {"Tags", "Tagi"}, + {"Incoming", "Przychodzące"}, + {"Outgoing", "Wychodzące"}, + {"Destination", "Miejsce docelowe"}, + {"Amount", "Ilość"}, + {"Incoming Tags", "Przychodzące tagi"}, + {"Tags sessions", "Sesje tagów"}, + {"Status", "Status"}, + {"Local Destination", "Lokalne miejsce docelowe"}, + {"Streams", "Strumienie"}, + {"Close stream", "Zamknij strumień"}, + {"Such destination is not found", "Nie znaleziono takiego miejsca docelowego"}, + {"I2CP session not found", "Sesja I2CP nie została znaleziona"}, + {"I2CP is not enabled", "I2CP nie jest włączone"}, + {"Invalid", "Niepoprawny"}, + {"Store type", "Rodzaj przechowywania"}, + {"Expires", "Wygasa za"}, + {"Non Expired Leases", "Leasingi niewygasłe"}, + {"Gateway", "Brama"}, + {"TunnelID", "IDTunelu"}, + {"EndDate", "DataZakończenia"}, + {"floodfill mode is disabled", "tryb floodfill jest wyłączony"}, + {"Queue size", "Wielkość kolejki"}, + {"Run peer test", "Wykonaj test peer"}, + {"Reload tunnels configuration", "Załaduj ponownie konfigurację tuneli"}, + {"Decline transit tunnels", "Odrzuć tunele tranzytowe"}, + {"Accept transit tunnels", "Akceptuj tunele tranzytowe"}, + {"Cancel graceful shutdown", "Anuluj łagodne wyłączenie"}, + {"Start graceful shutdown", "Rozpocznij łagodne wyłączenie"}, + {"Force shutdown", "Wymuś wyłączenie"}, + {"Reload external CSS styles", "Odśwież zewnętrzne style CSS"}, + {"Note: any action done here are not persistent and not changes your config files.", "Uwaga: każda akcja wykonana tutaj nie jest trwała i nie zmienia Twoich plików konfiguracyjnych."}, + {"Logging level", "Poziom logowania"}, + {"Transit tunnels limit", "Limit tuneli tranzytowych"}, + {"Change", "Zmień"}, + {"Change language", "Zmień język"}, + {"no transit tunnels currently built", "brak obecnie zbudowanych tuneli tranzytowych"}, + {"SAM disabled", "SAM wyłączony"}, + {"no sessions currently running", "brak aktualnie uruchomionych sesji"}, + {"SAM session not found", "Sesja SAM nie została znaleziona"}, + {"SAM Session", "Sesja SAM"}, + {"Server Tunnels", "Tunele Serwera"}, + {"Client Forwards", "Przekierowania Klienta"}, + {"Server Forwards", "Przekierowania Serwera"}, + {"Unknown page", "Nieznana strona"}, + {"Invalid token", "Nieprawidłowy token"}, + {"SUCCESS", "SUKCES"}, + {"Stream closed", "Strumień zamknięty"}, + {"Stream not found or already was closed", "Strumień nie został znaleziony lub został już zamknięty"}, + {"Destination not found", "Nie znaleziono punktu docelowego"}, + {"StreamID can't be null", "StreamID nie może być null"}, + {"Return to destination page", "Wróć do strony miejsca docelowego"}, + {"You will be redirected in %d seconds", "Zostaniesz prekierowany za %d sekund"}, + {"LeaseSet expiration time updated", "Zaktualizowano czas wygaśnięcia LeaseSet"}, + {"LeaseSet is not found or already expired", "LeaseSet nie został znaleziony lub już wygasł"}, + {"Transit tunnels count must not exceed %d", "Liczba tuneli tranzytowych nie może przekraczać %d"}, + {"Back to commands list", "Powrót do listy poleceń"}, + {"Register at reg.i2p", "Zarejestruj się na reg.i2p"}, + {"Description", "Opis"}, + {"A bit information about service on domain", "Trochę informacji o usłudze w domenie"}, + {"Submit", "Zatwierdź"}, + {"Domain can't end with .b32.i2p", "Domena nie może kończyć się na .b32.i2p"}, + {"Domain must end with .i2p", "Domena musi kończyć się na .i2p"}, + {"Unknown command", "Nieznana komenda"}, + {"Command accepted", "Polecenie zaakceptowane"}, + {"Proxy error", "Błąd serwera proxy"}, + {"Proxy info", "Informacje o proxy"}, + {"Proxy error: Host not found", "Błąd proxy: Nie znaleziono hosta"}, + {"Remote host not found in router's addressbook", "Nie znaleziono zdalnego hosta w książce adresowej routera"}, + {"You may try to find this host on jump services below", "Możesz znaleźć tego hosta na poniższych usługach skoku"}, + {"Invalid request", "Nieprawidłowe żądanie"}, + {"Proxy unable to parse your request", "Serwer proxy nie może przetworzyć Twojego żądania"}, + {"Addresshelper is not supported", "Adresshelper nie jest obsługiwany"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Host %s jest już w książce adresowej routera. Uważaj: źródło tego adresu URL może być szkodliwe! Kliknij tutaj, aby zaktualizować rekord: Kontynuuj."}, + {"Addresshelper forced update rejected", "Wymuszona aktualizacja Addreshelper odrzucona"}, + {"To add host %s in router's addressbook, click here: Continue.", "Aby dodać host %s w książce adresowej routera, kliknij tutaj: Kontynuuj."}, + {"Addresshelper request", "Prośba Addresshelper"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Host %s dodany do książki adresowej routera od pomocnika. Kliknij tutaj, aby kontynuować: Kontynuuj."}, + {"Addresshelper adding", "Dodawanie Addresshelper"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Host %s jest już w książce adresowej routera. Kliknij tutaj, aby zaktualizować rekord: Kontynuuj."}, + {"Addresshelper update", "Aktualizacja Adresshelper"}, + {"Invalid request URI", "Nieprawidłowe URI żądania"}, + {"Can't detect destination host from request", "Nie można wykryć hosta docelowego z żądania"}, + {"Outproxy failure", "Błąd proxy wyjściowego"}, + {"Bad outproxy settings", "Błędne ustawienia proxy wyjściowych"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s nie jest wewnątrz sieci I2P, a proxy wyjściowe nie jest włączone"}, + {"Unknown outproxy URL", "Nieznany adres URL proxy wyjściowego"}, + {"Cannot resolve upstream proxy", "Nie można rozwiązać serwera proxy upstream"}, + {"Hostname is too long", "Nazwa hosta jest zbyt długa"}, + {"Cannot connect to upstream SOCKS proxy", "Nie można połączyć się z proxy SOCKS upstream"}, + {"Cannot negotiate with SOCKS proxy", "Nie można negocjować z proxy SOCKS"}, + {"CONNECT error", "Błąd POŁĄCZENIE"}, + {"Failed to connect", "Nie udało się połączyć"}, + {"SOCKS proxy error", "Błąd proxy SOCKS"}, + {"Failed to send request to upstream", "Nie udało się wysłać żądania do upstream"}, + {"No reply from SOCKS proxy", "Brak odpowiedzi od serwera proxy SOCKS"}, + {"Cannot connect", "Nie można się połączyć"}, + {"HTTP out proxy not implemented", "Serwer wyjściowy proxy HTTP nie został zaimplementowany"}, + {"Cannot connect to upstream HTTP proxy", "Nie można połączyć się z proxy HTTP upstream"}, + {"Host is down", "Host jest niedostępny"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Nie można utworzyć połączenia z żądanym hostem, może być wyłączony. Spróbuj ponownie później."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d dzień", "%d dni", "%d dni", "%d dni"}}, + {"%d hours", {"%d godzina", "%d godziny", "%d godzin", "%d godzin"}}, + {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, + {"%d seconds", {"%d sekunda", "%d sekundy", "%d sekund", "%d sekund"}}, + {"", {"", "", "", ""}}, + }; + + 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..26204dc3 --- /dev/null +++ b/i18n/Portuguese.cpp @@ -0,0 +1,223 @@ +/* +* Copyright (c) 2023-2025, 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 const LocaleStrings 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", "Desvio de 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"}, + {"Total tunnel creation success rate", "Taxa total de sucesso na criação de túneis"}, + {"Received", "Recebido"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Enviado"}, + {"Transit", "Trânsito"}, + {"Data path", "Diretório de 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"}, + {"Expire LeaseSet", "Expirar LeaseSet"}, + {"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", "Destino Local"}, + {"Streams", "Fluxos"}, + {"Close stream", "Fechar fluxo"}, + {"Such destination is not found", "Tal destino não foi encontrado"}, + {"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úneis de trânsito"}, + {"Accept transit tunnels", "Aceitar túneis 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 de 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á fechado"}, + {"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"}, + {"LeaseSet expiration time updated", "Tempo de validade do LeaseSet atualizado"}, + {"LeaseSet is not found or already expired", "LeaseSet não foi encontrado ou já expirou"}, + {"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 em 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"}, + {"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 serviços de jump 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 ao 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 prosseguir: 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", "Má configurações do 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..235cc0ae 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,17 +29,18 @@ namespace russian // language namespace return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; } - static std::map strings + static const LocaleStrings strings { - {"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,18 +58,20 @@ 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"}, {"Stopping in", "Остановка через"}, {"Family", "Семейство"}, {"Tunnel creation success rate", "Успешно построенных туннелей"}, + {"Total tunnel creation success rate", "Общий процент успешно построенных туннелей"}, {"Received", "Получено"}, - {"KiB/s", "КиБ/с"}, + {"%.2f KiB/s", "%.2f КиБ/с"}, {"Sent", "Отправлено"}, {"Transit", "Транзит"}, {"Data path", "Путь к данным"}, @@ -93,8 +96,9 @@ namespace russian // language namespace {"Address", "Адрес"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, + {"Expire LeaseSet", "Просрочить Лизсет"}, {"Inbound tunnels", "Входящие туннели"}, - {"ms", "мс"}, + {"%dms", "%dмс"}, {"Outbound tunnels", "Исходящие туннели"}, {"Tags", "Теги"}, {"Incoming", "Входящие"}, @@ -107,6 +111,7 @@ namespace russian // language namespace {"Local Destination", "Локальное назначение"}, {"Streams", "Стримы"}, {"Close stream", "Закрыть стрим"}, + {"Such destination is not found", "Такая точка назначения не найдена"}, {"I2CP session not found", "I2CP сессия не найдена"}, {"I2CP is not enabled", "I2CP не включен"}, {"Invalid", "Некорректный"}, @@ -116,9 +121,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 +152,10 @@ 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 секунд"}, + {"LeaseSet expiration time updated", "Время действия LeaseSet обновлено"}, + {"LeaseSet is not found or already expired", "Лизсет не найден или время действия уже истекло"}, + {"Transit tunnels count must not exceed %d", "Число транзитных туннелей не должно превышать %d"}, {"Back to commands list", "Вернуться к списку команд"}, {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, {"Description", "Описание"}, @@ -155,7 +163,6 @@ namespace russian // language namespace {"Submit", "Отправить"}, {"Domain can't end with .b32.i2p", "Домен не может заканчиваться на .b32.i2p"}, {"Domain must end with .i2p", "Домен должен заканчиваться на .i2p"}, - {"Such destination is not found", "Такая точка назначения не найдена"}, {"Unknown command", "Неизвестная команда"}, {"Command accepted", "Команда принята"}, {"Proxy error", "Ошибка прокси"}, @@ -165,32 +172,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 +206,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 new file mode 100644 index 00000000..0e657fb4 --- /dev/null +++ b/i18n/Spanish.cpp @@ -0,0 +1,204 @@ +/* +* Copyright (c) 2022-2025, 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" + +// Spanish localization file + +namespace i2p +{ +namespace i18n +{ +namespace spanish // language namespace +{ + // language name in lowercase + static std::string language = "spanish"; + + // 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 const LocaleStrings strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "pendiente"}, + {"failed", "fallido"}, + {"expiring", "expiró"}, + {"established", "establecido"}, + {"unknown", "desconocido"}, + {"exploratory", "exploratorio"}, + {"Purple I2P Webconsole", "Consola web de Purple I2P"}, + {"i2pd webconsole", "Consola web de i2pd"}, + {"Main page", "Inicio"}, + {"Router commands", "Comandos de enrutador"}, + {"Local Destinations", "Destinos locales"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Túneles"}, + {"Transit Tunnels", "Túneles de Tránsito"}, + {"Transports", "Transportes"}, + {"I2P tunnels", "Túneles I2P"}, + {"SAM sessions", "Sesiones SAM"}, + {"ERROR", "ERROR"}, + {"OK", "VALE"}, + {"Testing", "Probando"}, + {"Firewalled", "Con cortafuegos"}, + {"Unknown", "Desconocido"}, + {"Proxy", "Proxy"}, + {"Mesh", "Malla"}, + {"Clock skew", "Reloj desfasado"}, + {"Offline", "Desconectado"}, + {"Symmetric NAT", "NAT simétrico"}, + {"Uptime", "Tiempo en línea"}, + {"Network status", "Estado de red"}, + {"Network status v6", "Estado de red v6"}, + {"Stopping in", "Parando en"}, + {"Family", "Familia"}, + {"Tunnel creation success rate", "Tasa de éxito de creación de túneles"}, + {"Received", "Recibido"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Enviado"}, + {"Transit", "Tránsito"}, + {"Data path", "Ruta de datos"}, + {"Hidden content. Press on text to see.", "Contenido oculto. Presione para ver."}, + {"Router Ident", "Ident del Enrutador"}, + {"Router Family", "Familia de enrutador"}, + {"Router Caps", "Atributos del Enrutador"}, + {"Version", "Versión"}, + {"Our external address", "Nuestra dirección externa"}, + {"supported", "soportado"}, + {"Routers", "Enrutadores"}, + {"Floodfills", "Inundaciones"}, + {"Client Tunnels", "Túneles de cliente"}, + {"Services", "Servicios"}, + {"Enabled", "Activado"}, + {"Disabled", "Desactivado"}, + {"Encrypted B33 address", "Dirección encriptada B33"}, + {"Address registration line", "Línea para registrar direcciones"}, + {"Domain", "Dominio"}, + {"Generate", "Generar"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la cadena resultante solo se puede usar para registrar dominios 2LD (ejemplo.i2p). Para registrar subdominios, por favor utilice i2pd-tools."}, + {"Address", "Dirección"}, + {"Type", "Tipo"}, + {"EncType", "TipoEncrip"}, + {"Inbound tunnels", "Túneles entrantes"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Túneles salientes"}, + {"Tags", "Etiquetas"}, + {"Incoming", "Entrante"}, + {"Outgoing", "Saliente"}, + {"Destination", "Destino"}, + {"Amount", "Cantidad"}, + {"Incoming Tags", "Etiquetas entrantes"}, + {"Tags sessions", "Sesiones de etiquetas"}, + {"Status", "Estado"}, + {"Local Destination", "Destino Local"}, + {"Streams", "Flujos"}, + {"Close stream", "Cerrar flujo"}, + {"I2CP session not found", "Sesión I2CP no encontrada"}, + {"I2CP is not enabled", "I2CP no está activado"}, + {"Invalid", "Inválido"}, + {"Store type", "Tipo de almacenamiento"}, + {"Expires", "Caduca"}, + {"Non Expired Leases", "Sesiones No Expiradas"}, + {"Gateway", "Puerta de enlace"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "FechaVenc"}, + {"Queue size", "Tamaño de cola"}, + {"Run peer test", "Ejecutar prueba de par"}, + {"Decline transit tunnels", "Rechazar túneles de tránsito"}, + {"Accept transit tunnels", "Aceptar túneles de tránsito"}, + {"Cancel graceful shutdown", "Cancelar apagado con gracia"}, + {"Start graceful shutdown", "Iniciar apagado con gracia"}, + {"Force shutdown", "Forzar apagado"}, + {"Reload external CSS styles", "Recargar estilos CSS externos"}, + {"Note: any action done here are not persistent and not changes your config files.", "Nota: cualquier acción hecha aquí no es persistente y no cambia tus archivos de configuración."}, + {"Logging level", "Nivel de registro de errores"}, + {"Transit tunnels limit", "Límite de túneles de tránsito"}, + {"Change", "Cambiar"}, + {"Change language", "Cambiar idioma"}, + {"no transit tunnels currently built", "no hay túneles de tránsito actualmente construidos"}, + {"SAM disabled", "SAM desactivado"}, + {"no sessions currently running", "no hay sesiones ejecutándose ahora"}, + {"SAM session not found", "Sesión SAM no encontrada"}, + {"SAM Session", "Sesión SAM"}, + {"Server Tunnels", "Túneles de Servidor"}, + {"Client Forwards", "Redirecciones de Cliente"}, + {"Server Forwards", "Redirecciones de Servidor"}, + {"Unknown page", "Página desconocida"}, + {"Invalid token", "Token inválido"}, + {"SUCCESS", "ÉXITO"}, + {"Stream closed", "Transmisión cerrada"}, + {"Stream not found or already was closed", "No se encontró la transmisión o ya se cerró"}, + {"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"}, + {"Back to commands list", "Volver a lista de comandos"}, + {"Register at reg.i2p", "Registrar en reg.i2p"}, + {"Description", "Descripción"}, + {"A bit information about service on domain", "Un poco de información sobre el servicio en el dominio"}, + {"Submit", "Enviar"}, + {"Domain can't end with .b32.i2p", "El dominio no puede terminar con .b32.i2p"}, + {"Domain must end with .i2p", "El dominio debe terminar con .i2p"}, + {"Such destination is not found", "No se encontró el destino"}, + {"Unknown command", "Comando desconocido"}, + {"Command accepted", "Comando aceptado"}, + {"Proxy error", "Error de proxy"}, + {"Proxy info", "Información del proxy"}, + {"Proxy error: Host not found", "Error de proxy: Host no encontrado"}, + {"Remote host not found in router's addressbook", "Servidor remoto no encontrado en la libreta de direcciones del enrutador"}, + {"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"}, + {"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"}, + {"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"}, + {"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."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%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"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp new file mode 100644 index 00000000..df13d22f --- /dev/null +++ b/i18n/Swedish.cpp @@ -0,0 +1,220 @@ +/* +* Copyright (c) 2023-2025, 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 const LocaleStrings 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"}, + {"Full cone NAT", "Full kon NAT"}, + {"No Descriptors", "Inga Beskrivningar"}, + {"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"}, + {"Such destination is not found", "En sådan plats hittas ej"}, + {"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"}, + {"floodfill mode is disabled", "Floodfill läget är inaktiverat"}, + {"Queue size", "Köstorlek"}, + {"Run peer test", "Utför utsiktstest"}, + {"Reload tunnels configuration", "Ladda om tunnelkonfiguration"}, + {"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"}, + {"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"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Var försiktig: källan till denna URL kan vara skadlig! Klicka här för att uppdatera registreringen: Fortsätt."}, + {"Addresshelper forced update rejected", "Tvingad uppdatering av adresshjälparen nekad"}, + {"To add host %s in router's addressbook, click here: Continue.", "För att lägga till värd %s i routerns adressbok, klicka här: Fortsätt."}, + {"Addresshelper request", "Adresshjälpare förfrågan"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Värd %s tillagd i routerns adressbok från hjälparen. Klicka här för att fortsätta: Fortsätt."}, + {"Addresshelper adding", "Adresshjälpare tilläggning"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Klicka här för att uppdatera registreringen: Fortsätt."}, + {"Addresshelper update", "Adresshjälpare uppdatering"}, + {"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..9946b336 --- /dev/null +++ b/i18n/Turkish.cpp @@ -0,0 +1,114 @@ +/* +* Copyright (c) 2023-2025, 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 const LocaleStrings 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..7efb8891 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,17 +29,18 @@ namespace turkmen // language namespace return n != 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings 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..c1b6c772 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,17 +29,18 @@ namespace ukrainian // language namespace return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; } - static std::map strings + static const LocaleStrings strings { - {"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,18 +58,20 @@ 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"}, {"Stopping in", "Зупинка через"}, {"Family", "Сімейство"}, {"Tunnel creation success rate", "Успішно побудованих тунелів"}, + {"Total tunnel creation success rate", "Загальна кількість створених тунелів"}, {"Received", "Отримано"}, - {"KiB/s", "КіБ/с"}, + {"%.2f KiB/s", "%.2f КіБ/с"}, {"Sent", "Відправлено"}, {"Transit", "Транзит"}, {"Data path", "Шлях до даних"}, @@ -93,8 +96,9 @@ namespace ukrainian // language namespace {"Address", "Адреса"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, + {"Expire LeaseSet", "Завершити LeaseSet"}, {"Inbound tunnels", "Вхідні тунелі"}, - {"ms", "мс"}, + {"%dms", "%dмс"}, {"Outbound tunnels", "Вихідні тунелі"}, {"Tags", "Теги"}, {"Incoming", "Вхідні"}, @@ -107,6 +111,7 @@ namespace ukrainian // language namespace {"Local Destination", "Локальні Призначення"}, {"Streams", "Потоки"}, {"Close stream", "Закрити потік"}, + {"Such destination is not found", "Така точка призначення не знайдена"}, {"I2CP session not found", "I2CP сесія не знайдена"}, {"I2CP is not enabled", "I2CP не увікнуто"}, {"Invalid", "Некоректний"}, @@ -116,9 +121,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 +152,10 @@ 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 секунд"}, + {"LeaseSet expiration time updated", "Час закінчення LeaseSet оновлено"}, + {"LeaseSet is not found or already expired", "LeaseSet не знайдено або вже закінчився"}, + {"Transit tunnels count must not exceed %d", "Кількість транзитних тунелів не повинна перевищувати %d"}, {"Back to commands list", "Повернутися до списку команд"}, {"Register at reg.i2p", "Зареєструвати на reg.i2p"}, {"Description", "Опис"}, @@ -155,7 +163,6 @@ namespace ukrainian // language namespace {"Submit", "Надіслати"}, {"Domain can't end with .b32.i2p", "Домен не може закінчуватися на .b32.i2p"}, {"Domain must end with .i2p", "Домен повинен закінчуватися на .i2p"}, - {"Such destination is not found", "Така точка призначення не знайдена"}, {"Unknown command", "Невідома команда"}, {"Command accepted", "Команда прийнята"}, {"Proxy error", "Помилка проксі"}, @@ -165,32 +172,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 +206,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..8e870772 100644 --- a/i18n/Uzbek.cpp +++ b/i18n/Uzbek.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2022, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,17 +29,18 @@ namespace uzbek // language namespace return n > 1 ? 1 : 0; } - static std::map strings + static const LocaleStrings 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,18 +58,20 @@ 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"}, {"Stopping in", "Ichida to'xtatish"}, {"Family", "Oila"}, {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, + {"Total tunnel creation success rate", "Tunnel yaratishning umumiy muvaffaqiyat darajasi"}, {"Received", "Qabul qilindi"}, - {"KiB/s", "KiB/s"}, + {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Yuborilgan"}, {"Transit", "Tranzit"}, {"Data path", "Ma'lumotlar joylanishi"}, @@ -93,8 +96,9 @@ namespace uzbek // language namespace {"Address", "Manzil"}, {"Type", "Turi"}, {"EncType", "ShifrlashTuri"}, + {"Expire LeaseSet", "LeaseSet muddati tugaydi"}, {"Inbound tunnels", "Kirish tunnellari"}, - {"ms", "ms"}, + {"%dms", "%dms"}, {"Outbound tunnels", "Chiquvchi tunnellar"}, {"Tags", "Teglar"}, {"Incoming", "Kiruvchi"}, @@ -107,6 +111,7 @@ namespace uzbek // language namespace {"Local Destination", "Mahalliy joylanish"}, {"Streams", "Strim"}, {"Close stream", "Strimni o'chirish"}, + {"Such destination is not found", "Bunday yo'nalish topilmadi"}, {"I2CP session not found", "I2CP sessiyasi topilmadi"}, {"I2CP is not enabled", "I2CP yoqilmagan"}, {"Invalid", "Noto'g'ri"}, @@ -116,9 +121,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 +152,10 @@ 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"}, + {"LeaseSet expiration time updated", "LeaseSet amal qilish muddati yangilandi"}, + {"LeaseSet is not found or already expired", "LeaseSet topilmadi yoki muddati tugagan"}, + {"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"}, @@ -155,7 +163,6 @@ namespace uzbek // language namespace {"Submit", "Yuborish"}, {"Domain can't end with .b32.i2p", "Domen .b32.i2p bilan tugashi mumkin emas"}, {"Domain must end with .i2p", "Domen .i2p bilan tugashi kerak"}, - {"Such destination is not found", "Bunday yo'nalish topilmadi"}, {"Unknown command", "Noma'lum buyruq"}, {"Command accepted", "Buyruq qabul qilindi"}, {"Proxy error", "Proksi xatosi"}, @@ -165,32 +172,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 +206,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/Base.cpp b/libi2pd/Base.cpp index 94446e86..bc9da4fb 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,7 @@ namespace i2p { namespace data { - static const char T32[32] = + static constexpr char T32[32] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', @@ -27,7 +27,7 @@ namespace data { return T32; } - + static void iT64Build(void); /* @@ -38,7 +38,7 @@ namespace data * Direct Substitution Table */ - static const char T64[64] = + static constexpr char T64[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', @@ -54,19 +54,17 @@ namespace data { return T64; } - + /* * Reverse Substitution Table (built in run time) */ - static char iT64[256]; static int isFirstTime = 1; /* * Padding */ - - static char P64 = '='; + static constexpr char P64 = '='; /* * @@ -76,131 +74,112 @@ namespace data * Converts binary encoded data to BASE64 format. * */ - - size_t ByteStreamToBase64 ( /* Number of bytes in the encoded buffer */ - const uint8_t * InBuffer, /* Input buffer, binary data */ - size_t InCount, /* Number of bytes in the input buffer */ - char * OutBuffer, /* output buffer */ - size_t len /* length of output buffer */ + std::string ByteStreamToBase64 (// base64 encoded string + const uint8_t * InBuffer, // Input buffer, binary data + size_t InCount // Number of bytes in the input buffer ) { unsigned char * ps; - unsigned char * pd; unsigned char acc_1; unsigned char acc_2; int i; int n; int m; - size_t outCount; ps = (unsigned char *)InBuffer; n = InCount / 3; m = InCount % 3; - if (!m) - outCount = 4 * n; - else - outCount = 4 * (n + 1); + size_t outCount = m ? (4 * (n + 1)) : (4 * n); - if (outCount > len) return 0; - - pd = (unsigned char *)OutBuffer; + std::string out; + out.reserve (outCount); for ( i = 0; i < n; i++ ) { acc_1 = *ps++; acc_2 = (acc_1 << 4) & 0x30; - acc_1 >>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; + acc_1 >>= 2; // base64 digit #1 + out.push_back (T64[acc_1]); acc_1 = *ps++; - acc_2 |= acc_1 >> 4; /* base64 digit #2 */ - *pd++ = T64[acc_2]; + acc_2 |= acc_1 >> 4; // base64 digit #2 + out.push_back (T64[acc_2]); acc_1 &= 0x0f; acc_1 <<= 2; acc_2 = *ps++; - acc_1 |= acc_2 >> 6; /* base64 digit #3 */ - *pd++ = T64[acc_1]; - acc_2 &= 0x3f; /* base64 digit #4 */ - *pd++ = T64[acc_2]; + acc_1 |= acc_2 >> 6; // base64 digit #3 + out.push_back (T64[acc_1]); + acc_2 &= 0x3f; // base64 digit #4 + out.push_back (T64[acc_2]); } if ( m == 1 ) { acc_1 = *ps++; - acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */ - acc_1 >>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; - *pd++ = T64[acc_2]; - *pd++ = P64; - *pd++ = P64; + acc_2 = (acc_1 << 4) & 0x3f; // base64 digit #2 + acc_1 >>= 2; // base64 digit #1 + out.push_back (T64[acc_1]); + out.push_back (T64[acc_2]); + out.push_back (P64); + out.push_back (P64); } else if ( m == 2 ) { acc_1 = *ps++; acc_2 = (acc_1 << 4) & 0x3f; - acc_1 >>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; + acc_1 >>= 2; // base64 digit #1 + out.push_back (T64[acc_1]); acc_1 = *ps++; - acc_2 |= acc_1 >> 4; /* base64 digit #2 */ - *pd++ = T64[acc_2]; + acc_2 |= acc_1 >> 4; // base64 digit #2 + out.push_back (T64[acc_2]); acc_1 &= 0x0f; - acc_1 <<= 2; /* base64 digit #3 */ - *pd++ = T64[acc_1]; - *pd++ = P64; + acc_1 <<= 2; // base64 digit #3 + out.push_back (T64[acc_1]); + out.push_back (P64); } - return outCount; - } - + return out; + } + /* * * Base64ToByteStream * ------------------ * - * Converts BASE64 encoded data to binary format. If input buffer is + * Converts BASE64 encoded string to binary format. If input buffer is * not properly padded, buffer of negative length is returned * */ - - size_t Base64ToByteStream ( /* Number of output bytes */ - const char * InBuffer, /* BASE64 encoded buffer */ - size_t InCount, /* Number of input bytes */ - uint8_t * OutBuffer, /* output buffer length */ - size_t len /* length of output buffer */ + size_t Base64ToByteStream ( // Number of output bytes + std::string_view base64Str, // BASE64 encoded string + uint8_t * OutBuffer, // output buffer length + size_t len // length of output buffer ) { - unsigned char * ps; unsigned char * pd; unsigned char acc_1; unsigned char acc_2; - int i; - int n; - int m; size_t outCount; - if (isFirstTime) - iT64Build(); - - n = InCount / 4; - m = InCount % 4; - - if (InCount && !m) - outCount = 3 * n; + if (base64Str.empty () || base64Str[0] == P64) return 0; + auto d = std::div (base64Str.length (), 4); + if (!d.rem) + outCount = 3 * d.quot; else return 0; - ps = (unsigned char *)(InBuffer + InCount - 1); - while ( *ps-- == P64 ) - outCount--; - ps = (unsigned char *)InBuffer; - - if (outCount > len) - return 0; + if (isFirstTime) iT64Build(); + auto pos = base64Str.find_last_not_of (P64); + if (pos == base64Str.npos) return 0; + outCount -= (base64Str.length () - pos - 1); + if (outCount > len) return 0; + + auto ps = base64Str.begin (); pd = OutBuffer; auto endOfOutBuffer = OutBuffer + outCount; - for ( i = 0; i < n; i++ ) + for (int i = 0; i < d.quot; i++) { - acc_1 = iT64[*ps++]; - acc_2 = iT64[*ps++]; + acc_1 = iT64[int(*ps++)]; + acc_2 = iT64[int(*ps++)]; acc_1 <<= 2; acc_1 |= acc_2 >> 4; *pd++ = acc_1; @@ -208,45 +187,30 @@ namespace data break; acc_2 <<= 4; - acc_1 = iT64[*ps++]; + acc_1 = iT64[int(*ps++)]; acc_2 |= acc_1 >> 2; *pd++ = acc_2; if (pd >= endOfOutBuffer) break; - acc_2 = iT64[*ps++]; + acc_2 = iT64[int(*ps++)]; acc_2 |= acc_1 << 6; *pd++ = acc_2; } return outCount; - } - - size_t Base64EncodingBufferSize (const size_t input_size) + } + + std::string ToBase64Standard (std::string_view in) { - auto d = div (input_size, 3); - if (d.rem) - d.quot++; - - return 4 * d.quot; - } - - std::string ToBase64Standard (const std::string& in) - { - auto len = Base64EncodingBufferSize (in.length ()); - char * str = new char[len + 1]; - auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len); - str[l] = 0; + auto str = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length ()); // replace '-' by '+' and '~' by '/' - for (size_t i = 0; i < l; i++) - if (str[i] == '-') - str[i] = '+'; - else if (str[i] == '~') - str[i] = '/'; - - std::string s(str); - delete[] str; - return s; + for (auto& ch: str) + if (ch == '-') + ch = '+'; + else if (ch == '~') + ch = '/'; + return str; } /* @@ -267,13 +231,12 @@ namespace data iT64[(int)P64] = 0; } - size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen) + size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen) { - int tmp = 0, bits = 0; + unsigned int tmp = 0, bits = 0; size_t ret = 0; - for (size_t i = 0; i < len; i++) + for (auto ch: base32Str) { - char ch = inBuf[i]; if (ch >= '2' && ch <= '7') // digit ch = (ch - '2') + 26; // 26 means a-z else if (ch >= 'a' && ch <= 'z') @@ -293,13 +256,15 @@ namespace data tmp <<= 5; } return ret; - } - - size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen) + } + + std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len) { - size_t ret = 0, pos = 1; - int bits = 8, tmp = inBuf[0]; - while (ret < outLen && (bits > 0 || pos < len)) + std::string out; + out.reserve ((len * 8 + 4) / 5); + size_t pos = 1; + unsigned int bits = 8, tmp = inBuf[0]; + while (bits > 0 || pos < len) { if (bits < 5) { @@ -319,10 +284,9 @@ namespace data bits -= 5; int ind = (tmp >> bits) & 0x1F; - outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2'); - ret++; + out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2')); } - return ret; - } + return out; + } } } diff --git a/libi2pd/Base.h b/libi2pd/Base.h index 79152e02..945dc8b3 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,25 +11,42 @@ #include #include -#include +#include +#include + +namespace i2p +{ +namespace data +{ + std::string ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount); + size_t Base64ToByteStream (std::string_view base64Str, uint8_t * OutBuffer, size_t len); -namespace i2p { -namespace data { - size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); - size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); const char * GetBase32SubstitutionTable (); const char * GetBase64SubstitutionTable (); + constexpr bool IsBase64 (char ch) + { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~'; + } - size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); - size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); + size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen); + std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len); + constexpr bool IsBase32 (char ch) + { + return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7'); + } /** * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes */ - size_t Base64EncodingBufferSize(const size_t input_size); - - std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization + inline size_t Base64EncodingBufferSize(size_t input_size) + { + auto d = std::div (input_size, 3); + if (d.rem) d.quot++; + return 4 * d.quot; + } + std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization + } // data } // i2p diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 65e5f78c..a661b428 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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): @@ -152,11 +152,11 @@ namespace data m_BlindedSigType = m_SigType; } - BlindedPublicKey::BlindedPublicKey (const std::string& b33): + BlindedPublicKey::BlindedPublicKey (std::string_view b33): m_SigType (0) // 0 means invalid, we can't blind DSA, set it later { uint8_t addr[40]; // TODO: define length from b33 - size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); + size_t l = i2p::data::Base32ToByteStream (b33, addr, 40); if (l < 32) { LogPrint (eLogError, "Blinding: Malformed b33 ", b33); @@ -198,7 +198,7 @@ namespace data std::string BlindedPublicKey::ToB33 () const { if (m_PublicKey.size () > 32) return ""; // assume 25519 - uint8_t addr[35]; char str[60]; // TODO: define actual length + uint8_t addr[35]; uint8_t flags = 0; if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; addr[0] = flags; // flags @@ -208,8 +208,7 @@ namespace data uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); // checksum is Little Endian addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); - auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); - return std::string (str, str + l); + return ByteStreamToBase32 (addr, m_PublicKey.size () + 3); } void BlindedPublicKey::GetCredential (uint8_t * credential) const diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index c78db003..fc11f613 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include "Identity.h" @@ -23,7 +24,7 @@ namespace data public: BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p std::string ToB33 () const; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp deleted file mode 100644 index de077e60..00000000 --- a/libi2pd/BloomFilter.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 "BloomFilter.h" -#include "I2PEndian.h" -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief decaying bloom filter implementation */ - class DecayingBloomFilter : public IBloomFilter - { - public: - - DecayingBloomFilter(const std::size_t size) - { - m_Size = size; - m_Data = new uint8_t[size]; - } - - /** @brief implements IBloomFilter::~IBloomFilter */ - ~DecayingBloomFilter() - { - delete [] m_Data; - } - - /** @brief implements IBloomFilter::Add */ - bool Add(const uint8_t * data, std::size_t len) - { - std::size_t idx; - uint8_t mask; - Get(data, len, idx, mask); - if(m_Data[idx] & mask) return false; // filter hit - m_Data[idx] |= mask; - return true; - } - - /** @brief implements IBloomFilter::Decay */ - void Decay() - { - // reset bloom filter buffer - memset(m_Data, 0, m_Size); - } - - private: - /** @brief get bit index for for data */ - void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm) - { - bm = 1; - uint8_t digest[32]; - // TODO: use blake2 because it's faster - SHA256(data, len, digest); - uint64_t i = buf64toh(digest); - idx = i % m_Size; - bm <<= (i % 8); - } - - uint8_t * m_Data; - std::size_t m_Size; - }; - - - BloomFilterPtr BloomFilter(std::size_t capacity) - { - return std::make_shared(capacity); - } -} -} diff --git a/libi2pd/BloomFilter.h b/libi2pd/BloomFilter.h deleted file mode 100644 index ade854e4..00000000 --- a/libi2pd/BloomFilter.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 BLOOM_FILTER_H_ -#define BLOOM_FILTER_H_ -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief interface for bloom filter */ - struct IBloomFilter - { - - /** @brief destructor */ - virtual ~IBloomFilter() {}; - /** @brief add entry to bloom filter, return false if filter hit otherwise return true */ - virtual bool Add(const uint8_t * data, std::size_t len) = 0; - /** @brief optionally decay old entries */ - virtual void Decay() = 0; - }; - - typedef std::shared_ptr BloomFilterPtr; - - /** @brief create bloom filter */ - BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8); - -} -} - -#endif diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp deleted file mode 100644 index 0804e2ac..00000000 --- a/libi2pd/CPU.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include "CPU.h" -#if defined(__x86_64__) || defined(__i386__) -#include -#endif -#include "Log.h" - -#ifndef bit_AES -#define bit_AES (1 << 25) -#endif -#ifndef bit_AVX -#define bit_AVX (1 << 28) -#endif - - -namespace i2p -{ -namespace cpu -{ - bool aesni = false; - bool avx = false; - - void Detect(bool AesSwitch, bool AvxSwitch, bool force) - { -#if defined(__x86_64__) || defined(__i386__) - int info[4]; - __cpuid(0, info[0], info[1], info[2], info[3]); - if (info[0] >= 0x00000001) { - __cpuid(0x00000001, info[0], info[1], info[2], info[3]); -#if defined (_WIN32) && (WINVER == 0x0501) // WinXP - if (AesSwitch && force) { // only if forced -#else - if ((info[2] & bit_AES && AesSwitch) || (AesSwitch && force)) { -#endif - aesni = true; - } -#if defined (_WIN32) && (WINVER == 0x0501) // WinXP - if (AvxSwitch && force) { // only if forced -#else - if ((info[2] & bit_AVX && AvxSwitch) || (AvxSwitch && force)) { -#endif - avx = true; - } - } -#endif // defined(__x86_64__) || defined(__i386__) - - LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled")); - LogPrint(eLogInfo, "AVX ", (avx ? "enabled" : "disabled")); - } -} -} diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index f021bccb..3fc38d47 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,15 +9,16 @@ #ifndef LIBI2PD_CPU_H #define LIBI2PD_CPU_H -namespace i2p -{ -namespace cpu -{ - extern bool aesni; - extern bool avx; - - void Detect(bool AesSwitch, bool AvxSwitch, bool force); -} -} +#if defined(_M_AMD64) || defined(__x86_64__) || defined(_M_IX86) || defined(__i386__) +# define IS_X86 1 +# if defined(_M_AMD64) || defined(__x86_64__) +# define IS_X86_64 1 +# else +# define IS_X86_64 0 +# endif +#else +# define IS_X86 0 +# define IS_X86_64 0 +#endif #endif diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp deleted file mode 100644 index 66bc135f..00000000 --- a/libi2pd/ChaCha20.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 -* -* Kovri go write your own code -* -*/ - -#include "I2PEndian.h" -#include "ChaCha20.h" - -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -namespace i2p -{ -namespace crypto -{ -namespace chacha -{ -void u32t8le(uint32_t v, uint8_t * p) -{ - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; -} - -uint32_t u8t32le(const uint8_t * p) -{ - uint32_t value = p[3]; - - value = (value << 8) | p[2]; - value = (value << 8) | p[1]; - value = (value << 8) | p[0]; - - return value; -} - -uint32_t rotl32(uint32_t x, int n) -{ - return x << n | (x >> (-n & 31)); -} - -void quarterround(uint32_t *x, int a, int b, int c, int d) -{ - x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16); - x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12); - x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8); - x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7); -} - - -void Chacha20Block::operator << (const Chacha20State & st) -{ - int i; - for (i = 0; i < 16; i++) - u32t8le(st.data[i], data + (i << 2)); -} - -void block (Chacha20State &input, int rounds) -{ - int i; - Chacha20State x; - x.Copy(input); - - for (i = rounds; i > 0; i -= 2) - { - quarterround(x.data, 0, 4, 8, 12); - quarterround(x.data, 1, 5, 9, 13); - quarterround(x.data, 2, 6, 10, 14); - quarterround(x.data, 3, 7, 11, 15); - quarterround(x.data, 0, 5, 10, 15); - quarterround(x.data, 1, 6, 11, 12); - quarterround(x.data, 2, 7, 8, 13); - quarterround(x.data, 3, 4, 9, 14); - } - x += input; - input.block << x; -} - -void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter) -{ - state.data[0] = 0x61707865; - state.data[1] = 0x3320646e; - state.data[2] = 0x79622d32; - state.data[3] = 0x6b206574; - for (size_t i = 0; i < 8; i++) - state.data[4 + i] = chacha::u8t32le(key + i * 4); - - state.data[12] = htole32 (counter); - for (size_t i = 0; i < 3; i++) - state.data[13 + i] = chacha::u8t32le(nonce + i * 4); -} - -void Chacha20SetCounter (Chacha20State& state, uint32_t counter) -{ - state.data[12] = htole32 (counter); - state.offset = 0; -} - -void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz) -{ - if (state.offset > 0) - { - // previous block if any - auto s = chacha::blocksize - state.offset; - if (sz < s) s = sz; - for (size_t i = 0; i < s; i++) - buf[i] ^= state.block.data[state.offset + i]; - buf += s; - sz -= s; - state.offset += s; - if (state.offset >= chacha::blocksize) state.offset = 0; - } - for (size_t i = 0; i < sz; i += chacha::blocksize) - { - chacha::block(state, chacha::rounds); - state.data[12]++; - for (size_t j = i; j < i + chacha::blocksize; j++) - { - if (j >= sz) - { - state.offset = j & 0x3F; // % 64 - break; - } - buf[j] ^= state.block.data[j - i]; - } - } -} - -} // namespace chacha -} // namespace crypto -} // namespace i2p - -#endif diff --git a/libi2pd/ChaCha20.h b/libi2pd/ChaCha20.h deleted file mode 100644 index 4364024b..00000000 --- a/libi2pd/ChaCha20.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 -* -* Kovri go write your own code -* -*/ -#ifndef LIBI2PD_CHACHA20_H -#define LIBI2PD_CHACHA20_H -#include -#include -#include -#include -#include "Crypto.h" - -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -namespace i2p -{ -namespace crypto -{ - const std::size_t CHACHA20_KEY_BYTES = 32; - const std::size_t CHACHA20_NOUNCE_BYTES = 12; - -namespace chacha -{ - constexpr std::size_t blocksize = 64; - constexpr int rounds = 20; - - struct Chacha20State; - struct Chacha20Block - { - Chacha20Block () {}; - Chacha20Block (Chacha20Block &&) = delete; - - uint8_t data[blocksize]; - - void operator << (const Chacha20State & st); - }; - - struct Chacha20State - { - Chacha20State (): offset (0) {}; - Chacha20State (Chacha20State &&) = delete; - - Chacha20State & operator += (const Chacha20State & other) - { - for(int i = 0; i < 16; i++) - data[i] += other.data[i]; - return *this; - } - - void Copy(const Chacha20State & other) - { - memcpy(data, other.data, sizeof(uint32_t) * 16); - } - uint32_t data[16]; - Chacha20Block block; - size_t offset; - }; - - void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter); - void Chacha20SetCounter (Chacha20State& state, uint32_t counter); - void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz); // encrypt buf in place -} // namespace chacha -} // namespace crypto -} // namespace i2p - -#endif -#endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 56da9b7c..939cd9ff 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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") @@ -64,7 +64,7 @@ namespace config { ("bandwidth", value()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") - ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") + ("ssu", bool_switch()->default_value(false), "Ignored. Always false") ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Ignored") @@ -77,7 +77,8 @@ 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(10000), "Maximum active transit tunnels (default:10000)") + ("limits.zombies", value()->default_value(0), "Minimum percentage of successfully created tunnels under which tunnel cleanup is paused (default [%]: 0.00)") ("limits.ntcpsoft", value()->default_value(0), "Ignored") ("limits.ntcphard", value()->default_value(0), "Ignored") ("limits.ntcpthreads", value()->default_value(1), "Ignored") @@ -95,6 +96,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"); @@ -115,9 +117,14 @@ namespace config { ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") + ("httpproxy.senduseragent", value()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by default") ("httpproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("httpproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") + ("httpproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec") + ("httpproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec") + ("httpproxy.i2p.streaming.profile", value()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") + ; options_description socksproxy("SOCKS Proxy options"); @@ -142,13 +149,28 @@ namespace config { ("socksproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("socksproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") + ("socksproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec") + ("socksproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec") + ("socksproxy.i2p.streaming.profile", value()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") ; + options_description shareddest("Shared local destination options"); + shareddest.add_options() + ("shareddest.inbound.length", value()->default_value("3"), "Shared local destination inbound tunnel length") + ("shareddest.outbound.length", value()->default_value("3"), "Shared local destination outbound tunnel length") + ("shareddest.inbound.quantity", value()->default_value("3"), "Shared local destination inbound tunnels quantity") + ("shareddest.outbound.quantity", value()->default_value("3"), "Shared local destination outbound tunnels quantity") + ("shareddest.i2cp.leaseSetType", value()->default_value("3"), "Shared local destination's LeaseSet type") + ("shareddest.i2cp.leaseSetEncType", value()->default_value("0,4"), "Shared local destination's LeaseSet encryption type") + ("shareddest.i2p.streaming.profile", value()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") + ; + options_description sam("SAM bridge options"); sam.add_options() ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") - ("sam.port", value()->default_value(7656), "SAM listen port") + ("sam.port", value()->default_value(7656), "SAM listen TCP port") + ("sam.portudp", value()->default_value(0), "SAM listen UDP port") ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") ; @@ -165,6 +187,8 @@ namespace config { ("i2cp.address", value()->default_value("127.0.0.1"), "I2CP listen address") ("i2cp.port", value()->default_value(7654), "I2CP listen port") ("i2cp.singlethread", value()->default_value(true), "Destinations run in the I2CP server's thread") + ("i2cp.inboundlimit", value()->default_value(0), "Client inbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default") + ("i2cp.outboundlimit", value()->default_value(0), "Client outbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default") ; options_description i2pcontrol("I2PControl options"); @@ -190,7 +214,7 @@ namespace config { options_description precomputation("Precomputation options"); precomputation.add_options() ("precomputation.elgamal", -#if defined(__x86_64__) +#if (defined(_M_AMD64) || defined(__x86_64__)) value()->default_value(false), #else value()->default_value(true), @@ -202,7 +226,7 @@ namespace config { reseed.add_options() ("reseed.verify", value()->default_value(false), "Verify .su3 signature") ("reseed.threshold", value()->default_value(25), "Minimum number of known routers before requesting reseed") - ("reseed.floodfill", value()->default_value(""), "Path to router info of floodfill to reseed from") + ("reseed.floodfill", value()->default_value(""), "Ignored. Always empty") ("reseed.file", value()->default_value(""), "Path to local .su3 file or HTTPS URL to reseed from") ("reseed.zipfile", value()->default_value(""), "Path to local .zip file to reseed from") ("reseed.proxy", value()->default_value(""), "url for reseed proxy, supports http/socks") @@ -210,20 +234,21 @@ namespace config { "https://reseed2.i2p.net/," "https://reseed.diva.exchange/," "https://reseed-fr.i2pd.xyz/," - "https://reseed.memcpy.io/," "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," "https://reseed.i2pgit.org/," - "https://i2p.novg.net/," - "https://banana.incognet.io/," + "https://coconut.incognet.io/," "https://reseed-pl.i2pd.xyz/," - "https://www2.mk16.de/" + "https://www2.mk16.de/," + "https://i2p.ghativega.in/," + "https://i2p.novg.net/," + "https://reseed.stormycloud.org/," + "https://cubicchaos.net:8443/" ), "Reseed URLs, separated by comma") ("reseed.yggurls", value()->default_value( "http://[324:71e:281a:9ed3::ace]:7070/," "http://[301:65b9:c7cd:9a36::1]:18801/," "http://[320:8936:ec1a:31f1::216]/," - "http://[306:3834:97b9:a00a::1]/," "http://[316:f9e0:f22e:a74f::216]/" ), "Reseed URLs through the Yggdrasil, separated by comma") ; @@ -274,14 +299,17 @@ namespace config { options_description ssu2("SSU2 Options"); ssu2.add_options() - ("ssu2.enabled", value()->default_value(false), "Enable SSU2 (default: disabled)") - ("ssu2.published", value()->default_value(false), "Publish SSU2 (default: disabled)") + ("ssu2.enabled", value()->default_value(true), "Enable SSU2 (default: enabled)") + ("ssu2.published", value()->default_value(true), "Publish SSU2 (default: enabled)") ("ssu2.port", value()->default_value(0), "Port to listen for incoming SSU2 packets (default: auto)") + ("ssu2.mtu4", value()->default_value(0), "MTU for ipv4 address (default: detect)") + ("ssu2.mtu6", value()->default_value(0), "MTU for ipv6 address (default: detect)") + ("ssu2.proxy", value()->default_value(""), "Socks5 proxy URL for SSU2 transport") ; 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," @@ -298,11 +326,11 @@ namespace config { ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; - options_description cpuext("CPU encryption extensions options"); + options_description cpuext("CPU encryption extensions options. Deprecated"); cpuext.add_options() - ("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used") - ("cpuext.avx", bool_switch()->default_value(true), "Use auto detection for AVX CPU extensions. If false, AVX will be not used") - ("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines") + ("cpuext.aesni", bool_switch()->default_value(true), "Deprecated option") + ("cpuext.avx", bool_switch()->default_value(false), "Deprecated option") + ("cpuext.force", bool_switch()->default_value(false), "Deprecated option") ; options_description meshnets("Meshnet transports options"); @@ -324,6 +352,7 @@ namespace config { .add(httpserver) .add(httpproxy) .add(socksproxy) + .add(shareddest) .add(sam) .add(bob) .add(i2cp) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index d7fb965e..c41b4c10 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,10 +19,11 @@ #if OPENSSL_HKDF #include #endif -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -#include "ChaCha20.h" -#include "Poly1305.h" +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +#include +#include #endif +#include "CPU.h" #include "Crypto.h" #include "Ed25519.h" #include "I2PEndian.h" @@ -32,7 +33,7 @@ namespace i2p { namespace crypto { - const uint8_t elgp_[256]= + constexpr uint8_t elgp_[256]= { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, @@ -52,9 +53,9 @@ namespace crypto 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - const int elgg_ = 2; + constexpr int elgg_ = 2; - const uint8_t dsap_[128]= + constexpr uint8_t dsap_[128]= { 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, @@ -66,13 +67,13 @@ namespace crypto 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 }; - const uint8_t dsaq_[20]= + constexpr uint8_t dsaq_[20]= { 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, 0x68, 0x40, 0x46, 0xb7 }; - const uint8_t dsag_[128]= + constexpr uint8_t dsag_[128]= { 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, @@ -84,7 +85,7 @@ namespace crypto 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 }; - const int rsae_ = 65537; + constexpr int rsae_ = 65537; struct CryptoConstants { @@ -149,6 +150,37 @@ namespace crypto #define dsap GetCryptoConstants ().dsap #define dsaq GetCryptoConstants ().dsaq #define dsag GetCryptoConstants ().dsag +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + EVP_PKEY * CreateDSA (BIGNUM * pubKey, BIGNUM * privKey) + { + EVP_PKEY * pkey = nullptr; + int selection = EVP_PKEY_KEY_PARAMETERS; + auto bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, dsap); + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, dsaq); + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, dsag); + if (pubKey) + { + OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PUB_KEY, pubKey); + selection = EVP_PKEY_PUBLIC_KEY; + } + if (privKey) + { + OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PRIV_KEY, privKey); + selection = EVP_PKEY_KEYPAIR; + } + auto params = OSSL_PARAM_BLD_to_param(bld); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "DSA", NULL); + EVP_PKEY_fromdata_init(ctx); + EVP_PKEY_fromdata(ctx, &pkey, selection, params); + + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + return pkey; + } +#else DSA * CreateDSA () { DSA * dsa = DSA_new (); @@ -156,11 +188,14 @@ namespace crypto DSA_set0_key (dsa, NULL, NULL); return dsa; } - +#endif + // DH/ElGamal +#if !IS_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; @@ -239,69 +274,15 @@ namespace crypto static BIGNUM * (* g_ElggTable)[255] = nullptr; -// DH - - DHKeys::DHKeys () - { - m_DH = DH_new (); - DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); - DH_set0_key (m_DH, NULL, NULL); - } - - DHKeys::~DHKeys () - { - DH_free (m_DH); - } - - void DHKeys::GenerateKeys () - { - BIGNUM * priv_key = NULL, * pub_key = NULL; -#if !defined(__x86_64__) // use short exponent for non x64 - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); -#endif - if (g_ElggTable) - { -#if defined(__x86_64__) - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); -#endif - auto ctx = BN_CTX_new (); - pub_key = ElggPow (priv_key, g_ElggTable, ctx); - DH_set0_key (m_DH, pub_key, priv_key); - BN_CTX_free (ctx); - } - else - { - DH_set0_key (m_DH, NULL, priv_key); - DH_generate_key (m_DH); - DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key); - } - - bn2buf (pub_key, m_PublicKey, 256); - } - - void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) - { - BIGNUM * pk = BN_bin2bn (pub, 256, NULL); - DH_compute_key (shared, pk, m_DH); - BN_free (pk); - } - // x25519 X25519Keys::X25519Keys () { -#if OPENSSL_X25519 m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL); m_Pkey = nullptr; -#else - m_Ctx = BN_CTX_new (); -#endif } X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub) { -#if OPENSSL_X25519 m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); if (pub) @@ -311,29 +292,16 @@ namespace crypto size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); } -#else - m_Ctx = BN_CTX_new (); - memcpy (m_PrivateKey, priv, 32); - if (pub) - memcpy (m_PublicKey, pub, 32); - else - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } X25519Keys::~X25519Keys () { -#if OPENSSL_X25519 EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); -#else - BN_CTX_free (m_Ctx); -#endif } void X25519Keys::GenerateKeys () { -#if OPENSSL_X25519 if (m_Pkey) { EVP_PKEY_free (m_Pkey); @@ -345,16 +313,11 @@ namespace crypto m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); // TODO: do we really need to re-create m_Ctx? size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); -#else - RAND_bytes (m_PrivateKey, 32); - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { if (!pub || (pub[31] & 0x80)) return false; // not x25519 key -#if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); if (!pkey) return false; @@ -362,25 +325,17 @@ namespace crypto size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); EVP_PKEY_free (pkey); -#else - GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); -#endif return true; } void X25519Keys::GetPrivateKey (uint8_t * priv) const { -#if OPENSSL_X25519 size_t len = 32; EVP_PKEY_get_raw_private_key (m_Pkey, priv, &len); -#else - memcpy (priv, m_PrivateKey, 32); -#endif } void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic) { -#if OPENSSL_X25519 if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); @@ -390,11 +345,6 @@ namespace crypto size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); } -#else - memcpy (m_PrivateKey, priv, 32); - if (calculatePublic) - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } // ElGamal @@ -408,7 +358,7 @@ namespace crypto BIGNUM * b1 = BN_CTX_get (ctx); BIGNUM * b = BN_CTX_get (ctx); // select random k -#if defined(__x86_64__) +#if IS_X86_64 BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 #else BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits @@ -475,7 +425,7 @@ namespace crypto void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub) { -#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) +#if IS_X86 || defined(_MSC_VER) RAND_bytes (priv, 256); #else // lower 226 bits (28 bytes and 2 bits) only. short exponent @@ -528,9 +478,8 @@ namespace crypto // encrypt CBCEncryption encryption; encryption.SetKey (shared); - encryption.SetIV (iv); encrypted[257] = 0; - encryption.Encrypt (m, 256, encrypted + 258); + encryption.Encrypt (m, 256, iv, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); BN_CTX_free (ctx); @@ -563,8 +512,7 @@ namespace crypto uint8_t m[256]; CBCDecryption decryption; decryption.SetKey (shared); - decryption.SetIV (iv); - decryption.Decrypt (encrypted + 258, 256, m); + decryption.Decrypt (encrypted + 258, 256, iv, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -601,507 +549,115 @@ namespace crypto BN_CTX_free (ctx); } -// HMAC - const uint64_t IPAD = 0x3636363636363636; - const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; - - - static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; - static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; - - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) - // key is 32 bytes - // digest is 16 bytes - // block size is 64 bytes - { - uint64_t buf[256]; - uint64_t hash[12]; // 96 bytes -#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) - if(i2p::cpu::avx) - { - __asm__ - ( - "vmovups %[key], %%ymm0 \n" - "vmovups %[ipad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[buf]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[buf]) \n" - "vmovups %[opad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[hash]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[hash]) \n" - "vzeroall \n" // end of AVX - "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes - : - : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), - [buf]"r"(buf), [hash]"r"(hash) - : "memory", "%xmm0" // TODO: change to %ymm0 later - ); - } - else -#endif - { - // ikeypad - buf[0] = key.GetLL ()[0] ^ IPAD; - buf[1] = key.GetLL ()[1] ^ IPAD; - buf[2] = key.GetLL ()[2] ^ IPAD; - buf[3] = key.GetLL ()[3] ^ IPAD; - buf[4] = IPAD; - buf[5] = IPAD; - buf[6] = IPAD; - buf[7] = IPAD; - // okeypad - hash[0] = key.GetLL ()[0] ^ OPAD; - hash[1] = key.GetLL ()[1] ^ OPAD; - hash[2] = key.GetLL ()[2] ^ OPAD; - hash[3] = key.GetLL ()[3] ^ OPAD; - hash[4] = OPAD; - hash[5] = OPAD; - hash[6] = OPAD; - hash[7] = OPAD; - // fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (hash + 10, 0, 16); - } - - // concatenate with msg - memcpy (buf + 8, msg, len); - // calculate first hash - MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes - - // calculate digest - MD5((uint8_t *)hash, 96, digest); - } - // AES -#ifdef __AES__ - #define KeyExpansion256(round0,round1) \ - "pshufd $0xff, %%xmm2, %%xmm2 \n" \ - "movaps %%xmm1, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm1 \n" \ - "pxor %%xmm2, %%xmm1 \n" \ - "movaps %%xmm1, "#round0"(%[sched]) \n" \ - "aeskeygenassist $0, %%xmm1, %%xmm4 \n" \ - "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ - "movaps %%xmm3, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ - "pxor %%xmm4, %%xmm3 \n" \ - "pxor %%xmm2, %%xmm3 \n" \ - "movaps %%xmm3, "#round1"(%[sched]) \n" -#endif - -#ifdef __AES__ - void ECBCryptoAESNI::ExpandKey (const AESKey& key) + ECBEncryption::ECBEncryption () { - __asm__ - ( - "movups (%[key]), %%xmm1 \n" - "movups 16(%[key]), %%xmm3 \n" - "movaps %%xmm1, (%[sched]) \n" - "movaps %%xmm3, 16(%[sched]) \n" - "aeskeygenassist $1, %%xmm3, %%xmm2 \n" - KeyExpansion256(32,48) - "aeskeygenassist $2, %%xmm3, %%xmm2 \n" - KeyExpansion256(64,80) - "aeskeygenassist $4, %%xmm3, %%xmm2 \n" - KeyExpansion256(96,112) - "aeskeygenassist $8, %%xmm3, %%xmm2 \n" - KeyExpansion256(128,144) - "aeskeygenassist $16, %%xmm3, %%xmm2 \n" - KeyExpansion256(160,176) - "aeskeygenassist $32, %%xmm3, %%xmm2 \n" - KeyExpansion256(192,208) - "aeskeygenassist $64, %%xmm3, %%xmm2 \n" - // key expansion final - "pshufd $0xff, %%xmm2, %%xmm2 \n" - "movaps %%xmm1, %%xmm4 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" - "pxor %%xmm4, %%xmm1 \n" - "pxor %%xmm2, %%xmm1 \n" - "movups %%xmm1, 224(%[sched]) \n" - : // output - : [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input - : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged - ); + m_Ctx = EVP_CIPHER_CTX_new (); } -#endif - - -#ifdef __AES__ - #define EncryptAES256(sched) \ - "pxor (%["#sched"]), %%xmm0 \n" \ - "aesenc 16(%["#sched"]), %%xmm0 \n" \ - "aesenc 32(%["#sched"]), %%xmm0 \n" \ - "aesenc 48(%["#sched"]), %%xmm0 \n" \ - "aesenc 64(%["#sched"]), %%xmm0 \n" \ - "aesenc 80(%["#sched"]), %%xmm0 \n" \ - "aesenc 96(%["#sched"]), %%xmm0 \n" \ - "aesenc 112(%["#sched"]), %%xmm0 \n" \ - "aesenc 128(%["#sched"]), %%xmm0 \n" \ - "aesenc 144(%["#sched"]), %%xmm0 \n" \ - "aesenc 160(%["#sched"]), %%xmm0 \n" \ - "aesenc 176(%["#sched"]), %%xmm0 \n" \ - "aesenc 192(%["#sched"]), %%xmm0 \n" \ - "aesenc 208(%["#sched"]), %%xmm0 \n" \ - "aesenclast 224(%["#sched"]), %%xmm0 \n" -#endif - - void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out) + + ECBEncryption::~ECBEncryption () { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" - ); - } - else -#endif - { - AES_encrypt (in->buf, out->buf, &m_Key); - } + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ECBEncryption::Encrypt (const uint8_t * in, uint8_t * out) + { + EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int len; + EVP_EncryptUpdate (m_Ctx, out, &len, in, 16); + EVP_EncryptFinal_ex (m_Ctx, out + len, &len); } -#ifdef __AES__ - #define DecryptAES256(sched) \ - "pxor 224(%["#sched"]), %%xmm0 \n" \ - "aesdec 208(%["#sched"]), %%xmm0 \n" \ - "aesdec 192(%["#sched"]), %%xmm0 \n" \ - "aesdec 176(%["#sched"]), %%xmm0 \n" \ - "aesdec 160(%["#sched"]), %%xmm0 \n" \ - "aesdec 144(%["#sched"]), %%xmm0 \n" \ - "aesdec 128(%["#sched"]), %%xmm0 \n" \ - "aesdec 112(%["#sched"]), %%xmm0 \n" \ - "aesdec 96(%["#sched"]), %%xmm0 \n" \ - "aesdec 80(%["#sched"]), %%xmm0 \n" \ - "aesdec 64(%["#sched"]), %%xmm0 \n" \ - "aesdec 48(%["#sched"]), %%xmm0 \n" \ - "aesdec 32(%["#sched"]), %%xmm0 \n" \ - "aesdec 16(%["#sched"]), %%xmm0 \n" \ - "aesdeclast (%["#sched"]), %%xmm0 \n" -#endif - - void ECBDecryption::Decrypt (const ChipherBlock * in, ChipherBlock * out) + ECBDecryption::ECBDecryption () { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" - ); - } - else -#endif - { - AES_decrypt (in->buf, out->buf, &m_Key); - } + m_Ctx = EVP_CIPHER_CTX_new (); + } + + ECBDecryption::~ECBDecryption () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out) + { + EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int len; + EVP_DecryptUpdate (m_Ctx, out, &len, in, 16); + EVP_DecryptFinal_ex (m_Ctx, out + len, &len); } -#ifdef __AES__ - #define CallAESIMC(offset) \ - "movaps "#offset"(%[shed]), %%xmm0 \n" \ - "aesimc %%xmm0, %%xmm0 \n" \ - "movaps %%xmm0, "#offset"(%[shed]) \n" -#endif - void ECBEncryption::SetKey (const AESKey& key) - { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - ExpandKey (key); - } - else -#endif - { - AES_set_encrypt_key (key, 256, &m_Key); - } + CBCEncryption::CBCEncryption () + { + m_Ctx = EVP_CIPHER_CTX_new (); } - - void ECBDecryption::SetKey (const AESKey& key) + + CBCEncryption::~CBCEncryption () { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - ExpandKey (key); // expand encryption key first - // then invert it using aesimc - __asm__ - ( - CallAESIMC(16) - CallAESIMC(32) - CallAESIMC(48) - CallAESIMC(64) - CallAESIMC(80) - CallAESIMC(96) - CallAESIMC(112) - CallAESIMC(128) - CallAESIMC(144) - CallAESIMC(160) - CallAESIMC(176) - CallAESIMC(192) - CallAESIMC(208) - : : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory" - ); - } - else -#endif - { - AES_set_decrypt_key (key, 256, &m_Key); - } - } - - void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) - { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "cc", "memory" - ); - } - else -#endif - { - for (int i = 0; i < numBlocks; i++) - { - *m_LastBlock.GetChipherBlock () ^= in[i]; - m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ()); - out[i] = *m_LastBlock.GetChipherBlock (); - } - } - } - - void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out) + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void CBCEncryption::Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out) { // len/16 - int numBlocks = len >> 4; - if (numBlocks > 0) - Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); + EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int l; + EVP_EncryptUpdate (m_Ctx, out, &l, in, len); + EVP_EncryptFinal_ex (m_Ctx, out + l, &l); } - void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) - { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - "movups %%xmm0, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); - } - else -#endif - Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + CBCDecryption::CBCDecryption () + { + m_Ctx = EVP_CIPHER_CTX_new (); } - - void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) + + CBCDecryption::~CBCDecryption () { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); - } - else -#endif - { - for (int i = 0; i < numBlocks; i++) - { - ChipherBlock tmp = in[i]; - m_ECBDecryption.Decrypt (in + i, out + i); - out[i] ^= *m_IV.GetChipherBlock (); - *m_IV.GetChipherBlock () = tmp; - } - } - } - - void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out) + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void CBCDecryption::Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out) { - int numBlocks = len >> 4; - if (numBlocks > 0) - Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); - } - - void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) - { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "movups %%xmm0, (%[iv]) \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); - } - else -#endif - Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + // len/16 + EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv); + EVP_CIPHER_CTX_set_padding (m_Ctx, 0); + int l; + EVP_DecryptUpdate (m_Ctx, out, &l, in, len); + EVP_DecryptFinal_ex (m_Ctx, out + l, &l); } void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - // encrypt IV - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - EncryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // encrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched_l) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "cc", "memory" - ); - } - else -#endif - { - m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv - m_LayerEncryption.SetIV (out); - m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data - m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv - } + uint8_t iv[16]; + m_IVEncryption.Encrypt (in, iv); // iv + m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data + m_IVEncryption.Encrypt (iv, out); // double iv } void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ - if(i2p::cpu::aesni) - { - __asm__ - ( - // decrypt IV - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - DecryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // decrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched_l) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); - } - else -#endif - { - m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv - m_LayerDecryption.SetIV (out); - m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data - m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv - } + uint8_t iv[16]; + m_IVDecryption.Decrypt (in, iv); // iv + m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data + m_IVDecryption.Decrypt (iv, out); // double iv } // AEAD/ChaCha20/Poly1305 - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) + static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) { - if (len < msgLen) return false; + if (!ctx || len < msgLen) return false; if (encrypt && len < msgLen + 16) return false; bool ret = true; -#if OPENSSL_AEAD_CHACHA20_POLY1305 int outlen = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); if (encrypt) { EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); @@ -1109,11 +665,20 @@ namespace crypto EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); EVP_EncryptUpdate(ctx, NULL, &outlen, ad, adLen); EVP_EncryptUpdate(ctx, buf, &outlen, msg, msgLen); - EVP_EncryptFinal_ex(ctx, buf, &outlen); + EVP_EncryptFinal_ex(ctx, buf + outlen, &outlen); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, buf + msgLen); } else { +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL + std::vector m(msgLen + 16); + if (msg == buf) + { + // we have to use different buffers otherwise verification fails + memcpy (m.data (), msg, msgLen + 16); + msg = m.data (); + } +#endif EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (uint8_t *)(msg + msgLen)); @@ -1122,140 +687,100 @@ namespace crypto EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen); ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0; } - - EVP_CIPHER_CTX_free (ctx); -#else - chacha::Chacha20State state; - // generate one time poly key - chacha::Chacha20Init (state, nonce, key, 0); - uint64_t polyKey[8]; - memset(polyKey, 0, sizeof(polyKey)); - chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64); - // create Poly1305 hash - Poly1305 polyHash (polyKey); - if (!ad) adLen = 0; - uint8_t padding[16]; memset (padding, 0, 16); - if (ad) - { - polyHash.Update (ad, adLen);// additional authenticated data - auto rem = adLen & 0x0F; // %16 - if (rem) - { - // padding1 - rem = 16 - rem; - polyHash.Update (padding, rem); - } - } - // encrypt/decrypt data and add to hash - Chacha20SetCounter (state, 1); - if (buf != msg) - memcpy (buf, msg, msgLen); - if (encrypt) - { - chacha::Chacha20Encrypt (state, buf, msgLen); // encrypt - polyHash.Update (buf, msgLen); // after encryption - } - else - { - polyHash.Update (buf, msgLen); // before decryption - chacha::Chacha20Encrypt (state, buf, msgLen); // decrypt - } - - auto rem = msgLen & 0x0F; // %16 - if (rem) - { - // padding2 - rem = 16 - rem; - polyHash.Update (padding, rem); - } - // adLen and msgLen - htole64buf (padding, adLen); - htole64buf (padding + 8, msgLen); - polyHash.Update (padding, 16); - - if (encrypt) - // calculate Poly1305 tag and write in after encrypted data - polyHash.Finish ((uint64_t *)(buf + msgLen)); - else - { - uint64_t tag[4]; - // calculate Poly1305 tag - polyHash.Finish (tag); - if (memcmp (tag, msg + msgLen, 16)) ret = false; // compare with provided - } -#endif return ret; } - void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac) + bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) { - if (bufs.empty ()) return; -#if OPENSSL_AEAD_CHACHA20_POLY1305 - int outlen = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); - EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); - EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); - for (const auto& it: bufs) - EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second); - EVP_EncryptFinal_ex(ctx, NULL, &outlen); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); + EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new (); + auto ret = AEADChaCha20Poly1305 (ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, encrypt); EVP_CIPHER_CTX_free (ctx); -#else - chacha::Chacha20State state; - // generate one time poly key - chacha::Chacha20Init (state, nonce, key, 0); - uint64_t polyKey[8]; - memset(polyKey, 0, sizeof(polyKey)); - chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64); - Poly1305 polyHash (polyKey); - // encrypt buffers - Chacha20SetCounter (state, 1); - size_t size = 0; - for (const auto& it: bufs) - { - chacha::Chacha20Encrypt (state, it.first, it.second); - polyHash.Update (it.first, it.second); // after encryption - size += it.second; - } - // padding - uint8_t padding[16]; - memset (padding, 0, 16); - auto rem = size & 0x0F; // %16 - if (rem) - { - // padding2 - rem = 16 - rem; - polyHash.Update (padding, rem); - } - // adLen and msgLen - // adLen is always zero - htole64buf (padding + 8, size); - polyHash.Update (padding, 16); - // MAC - polyHash.Finish ((uint64_t *)mac); -#endif + return ret; } - void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + AEADChaCha20Poly1305Encryptor::AEADChaCha20Poly1305Encryptor () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + AEADChaCha20Poly1305Encryptor::~AEADChaCha20Poly1305Encryptor () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + bool AEADChaCha20Poly1305Encryptor::Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, true); + } + + void AEADChaCha20Poly1305Encryptor::Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac) + { + if (bufs.empty ()) return; + int outlen = 0; + EVP_EncryptInit_ex(m_Ctx, EVP_chacha20_poly1305(), 0, 0, 0); + EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); + EVP_EncryptInit_ex(m_Ctx, NULL, NULL, key, nonce); + for (const auto& it: bufs) + EVP_EncryptUpdate(m_Ctx, it.first, &outlen, it.first, it.second); + EVP_EncryptFinal_ex(m_Ctx, NULL, &outlen); + EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); + } + + AEADChaCha20Poly1305Decryptor::AEADChaCha20Poly1305Decryptor () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + AEADChaCha20Poly1305Decryptor::~AEADChaCha20Poly1305Decryptor () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + bool AEADChaCha20Poly1305Decryptor::Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false); + } + + static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) { -#if OPENSSL_AEAD_CHACHA20_POLY1305 - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); uint32_t iv[4]; iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv); int outlen = 0; EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen); EVP_EncryptFinal_ex(ctx, NULL, &outlen); + } + + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); + ChaCha20 (ctx, msg, msgLen, key, nonce, out); EVP_CIPHER_CTX_free (ctx); -#else - chacha::Chacha20State state; - chacha::Chacha20Init (state, nonce, key, 1); - if (out != msg) memcpy (out, msg, msgLen); - chacha::Chacha20Encrypt (state, out, msgLen); -#endif } + + ChaCha20Context::ChaCha20Context () + { + m_Ctx = EVP_CIPHER_CTX_new (); + } + + ChaCha20Context::~ChaCha20Context () + { + if (m_Ctx) + EVP_CIPHER_CTX_free (m_Ctx); + } + + void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out); + } + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen) { @@ -1296,6 +821,18 @@ namespace crypto // Noise + void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub) + { + // pub is Bob's public static key, hh = SHA256(h) + memcpy (m_CK, ck, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + m_N = 0; + } + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) { SHA256_CTX ctx; @@ -1319,76 +856,95 @@ namespace crypto { HKDF (m_CK, sharedSecret, 32, "", m_CK); // new ck is m_CK[0:31], key is m_CK[32:63] + m_N = 0; } - static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck, - const uint8_t * hh, const uint8_t * pub) + bool NoiseSymmetricState::Encrypt (const uint8_t * in, uint8_t * out, size_t len) { - // pub is Bob's public static key, hh = SHA256(h) - memcpy (state.m_CK, ck, 32); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, pub, 32); - SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + uint8_t nonce[12]; + if (m_N) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, m_N); + } + else + memset (nonce, 0, 12); + auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len + 16, true); + if (ret) m_N++; + return ret; } + bool NoiseSymmetricState::Decrypt (const uint8_t * in, uint8_t * out, size_t len) + { + uint8_t nonce[12]; + if (m_N) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, m_N); + } + else + memset (nonce, 0, 12); + auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len, false); + if (ret) m_N++; + return ret; + } + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) { - static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - static const uint8_t hh[32] = + static constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static constexpr uint8_t hh[32] = { 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 }; // hh = SHA256(protocol_name || 0) - InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 + state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 } void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) { - static const uint8_t protocolNameHash[32] = + static constexpr uint8_t protocolNameHash[32] = { 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") - static const uint8_t hh[32] = + static constexpr uint8_t hh[32] = { 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e }; // SHA256 (protocolNameHash) - InitNoiseState (state, protocolNameHash, hh, pub); + state.Init (protocolNameHash, hh, pub); } void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) { - static const uint8_t protocolNameHash[32] = + static constexpr uint8_t protocolNameHash[32] = { 0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4, 0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32 }; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256") - static const uint8_t hh[32] = + static constexpr uint8_t hh[32] = { 0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53, 0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33 }; // SHA256 (protocolNameHash) - InitNoiseState (state, protocolNameHash, hh, pub); + state.Init (protocolNameHash, hh, pub); } void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) { - static const uint8_t protocolNameHash[32] = + static constexpr uint8_t protocolNameHash[32] = { 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes - static const uint8_t hh[32] = + static constexpr uint8_t hh[32] = { 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c }; // SHA256 (protocolNameHash) - InitNoiseState (state, protocolNameHash, hh, pub); + state.Init (protocolNameHash, hh, pub); } - + // init and terminate /* std::vector > m_OpenSSLMutexes; @@ -1403,19 +959,15 @@ namespace crypto } }*/ - void InitCrypto (bool precomputation, bool aesni, bool avx, bool force) + void InitCrypto (bool precomputation) { - i2p::cpu::Detect (aesni, avx, force); -#if LEGACY_OPENSSL - SSL_library_init (); -#endif /* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); CRYPTO_set_locking_callback (OpensslLockingCallback);*/ if (precomputation) { -#if defined(__x86_64__) +#if IS_X86_64 g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); #else @@ -1430,7 +982,7 @@ namespace crypto if (g_ElggTable) { DestroyElggTable (g_ElggTable, -#if defined(__x86_64__) +#if IS_X86_64 ELGAMAL_FULL_EXPONENT_NUM_BYTES #else ELGAMAL_SHORT_EXPONENT_NUM_BYTES diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index c6dcd2cc..125a217c 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,32 +21,20 @@ #include #include #include -#include #include #include "Base.h" #include "Tag.h" -#include "CPU.h" // recognize openssl version and features -#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3050200fL)) // LibreSSL 3.5.2 and above -# define LEGACY_OPENSSL 0 -#elif ((OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER)) // 1.0.2 and below or LibreSSL -# define LEGACY_OPENSSL 1 -# define X509_getm_notBefore X509_get_notBefore -# define X509_getm_notAfter X509_get_notAfter -#else -# define LEGACY_OPENSSL 0 -# if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 -# define OPENSSL_HKDF 1 -# define OPENSSL_EDDSA 1 -# define OPENSSL_X25519 1 -# if (OPENSSL_VERSION_NUMBER != 0x030000000) // 3.0.0, regression in SipHash -# define OPENSSL_SIPHASH 1 -# endif +#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 +# define OPENSSL_HKDF 1 +# define OPENSSL_EDDSA 1 +# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL +# define OPENSSL_SIPHASH 1 # endif -# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them -# define OPENSSL_AEAD_CHACHA20_POLY1305 1 +# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0 +# define OPENSSL_PQ 1 # endif #endif @@ -57,29 +45,15 @@ namespace crypto bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); // DSA +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr); +#else DSA * CreateDSA (); +#endif // RSA const BIGNUM * GetRSAE (); - // DH - class DHKeys - { - public: - - DHKeys (); - ~DHKeys (); - - void GenerateKeys (); - const uint8_t * GetPublicKey () const { return m_PublicKey; }; - void Agree (const uint8_t * pub, uint8_t * shared); - - private: - - DH * m_DH; - uint8_t m_PublicKey[256]; - }; - // x25519 class X25519Keys { @@ -101,13 +75,8 @@ namespace crypto private: uint8_t m_PublicKey[32]; -#if OPENSSL_X25519 EVP_PKEY_CTX * m_Ctx; EVP_PKEY * m_Pkey; -#else - BN_CTX * m_Ctx; - uint8_t m_PrivateKey[32]; -#endif bool m_IsElligatorIneligible = false; // true if definitely ineligible }; @@ -121,147 +90,71 @@ namespace crypto bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); - // HMAC - typedef i2p::data::Tag<32> MACKey; - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); - // AES - struct ChipherBlock - { - uint8_t buf[16]; - - void operator^=(const ChipherBlock& other) // XOR - { - if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ? - { - for (int i = 0; i < 4; i++) - reinterpret_cast(buf)[i] ^= reinterpret_cast(other.buf)[i]; - } - else - { - for (int i = 0; i < 16; i++) - buf[i] ^= other.buf[i]; - } - } - }; - typedef i2p::data::Tag<32> AESKey; - - template - class AESAlignedBuffer // 16 bytes alignment - { - public: - - AESAlignedBuffer () - { - m_Buf = m_UnalignedBuffer; - uint8_t rem = ((size_t)m_Buf) & 0x0f; - if (rem) - m_Buf += (16 - rem); - } - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; }; - const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; }; - - private: - - uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment - uint8_t * m_Buf; - }; - - -#ifdef __AES__ - class ECBCryptoAESNI - { - public: - - uint8_t * GetKeySchedule () { return m_KeySchedule; }; - - protected: - - void ExpandKey (const AESKey& key); - - private: - - AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes - }; -#endif - -#ifdef __AES__ - class ECBEncryption: public ECBCryptoAESNI -#else + class ECBEncryption -#endif { public: - void SetKey (const AESKey& key); + ECBEncryption (); + ~ECBEncryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; + void Encrypt(const uint8_t * in, uint8_t * out); - void Encrypt(const ChipherBlock * in, ChipherBlock * out); + private: - private: - AES_KEY m_Key; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; -#ifdef __AES__ - class ECBDecryption: public ECBCryptoAESNI -#else class ECBDecryption -#endif { public: - void SetKey (const AESKey& key); - void Decrypt (const ChipherBlock * in, ChipherBlock * out); + ECBDecryption (); + ~ECBDecryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; + void Decrypt (const uint8_t * in, uint8_t * out); + private: - AES_KEY m_Key; + + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class CBCEncryption { public: - CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); }; - - void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes - void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes - void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); }; - - void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out); - void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out); - void Encrypt (const uint8_t * in, uint8_t * out); // one block - - ECBEncryption & ECB() { return m_ECBEncryption; } + CBCEncryption (); + ~CBCEncryption (); + void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes + void Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out); + private: - AESAlignedBuffer<16> m_LastBlock; - - ECBEncryption m_ECBEncryption; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class CBCDecryption { public: - CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); }; - - void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes - void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes - void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); }; - - void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out); - void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out); - void Decrypt (const uint8_t * in, uint8_t * out); // one block - - ECBDecryption & ECB() { return m_ECBDecryption; } + CBCDecryption (); + ~CBCDecryption (); + + void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes + void Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out); private: - AESAlignedBuffer<16> m_IV; - ECBDecryption m_ECBDecryption; + AESKey m_Key; + EVP_CIPHER_CTX * m_Ctx; }; class TunnelEncryption // with double IV encryption @@ -301,13 +194,58 @@ namespace crypto }; // AEAD/ChaCha20/Poly1305 - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag - void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad + class AEADChaCha20Poly1305Encryptor + { + public: + AEADChaCha20Poly1305Encryptor (); + ~AEADChaCha20Poly1305Encryptor (); + + bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag + + void Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + + class AEADChaCha20Poly1305Decryptor + { + public: + + AEADChaCha20Poly1305Decryptor (); + ~AEADChaCha20Poly1305Decryptor (); + + bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + + bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag + // ChaCha20 void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + class ChaCha20Context + { + public: + + ChaCha20Context (); + ~ChaCha20Context (); + void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + + private: + + EVP_CIPHER_CTX * m_Ctx; + }; + // HKDF void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 @@ -317,96 +255,27 @@ namespace crypto struct NoiseSymmetricState { uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; + uint64_t m_N; + void Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub); + void MixHash (const uint8_t * buf, size_t len); void MixHash (const std::vector >& bufs); void MixKey (const uint8_t * sharedSecret); + + bool Encrypt (const uint8_t * in, uint8_t * out, size_t len); // out length = len + 16 + bool Decrypt (const uint8_t * in, uint8_t * out, size_t len); // len without 16 bytes tag }; void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2) void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (SSU2) void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) - + // init and terminate - void InitCrypto (bool precomputation, bool aesni, bool avx, bool force); + void InitCrypto (bool precomputation); void TerminateCrypto (); } } -// take care about openssl below 1.1.0 -#if LEGACY_OPENSSL -// define getters and setters introduced in 1.1.0 -inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) - { - if (d->p) BN_free (d->p); - if (d->q) BN_free (d->q); - if (d->g) BN_free (d->g); - d->p = p; d->q = q; d->g = g; return 1; - } -inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) - { - if (d->pub_key) BN_free (d->pub_key); - if (d->priv_key) BN_free (d->priv_key); - d->pub_key = pub_key; d->priv_key = priv_key; return 1; - } -inline void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) - { *pub_key = d->pub_key; *priv_key = d->priv_key; } -inline int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) - { - if (sig->r) BN_free (sig->r); - if (sig->s) BN_free (sig->s); - sig->r = r; sig->s = s; return 1; - } -inline void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) - { *pr = sig->r; *ps = sig->s; } - -inline int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) - { - if (sig->r) BN_free (sig->r); - if (sig->s) BN_free (sig->s); - sig->r = r; sig->s = s; return 1; - } -inline void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) - { *pr = sig->r; *ps = sig->s; } - -inline int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) - { - if (r->n) BN_free (r->n); - if (r->e) BN_free (r->e); - if (r->d) BN_free (r->d); - r->n = n; r->e = e; r->d = d; return 1; - } -inline void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) - { *n = r->n; *e = r->e; *d = r->d; } - -inline int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) - { - if (dh->p) BN_free (dh->p); - if (dh->q) BN_free (dh->q); - if (dh->g) BN_free (dh->g); - dh->p = p; dh->q = q; dh->g = g; return 1; - } -inline int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) - { - if (dh->pub_key) BN_free (dh->pub_key); - if (dh->priv_key) BN_free (dh->priv_key); - dh->pub_key = pub_key; dh->priv_key = priv_key; return 1; - } -inline void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) - { *pub_key = dh->pub_key; *priv_key = dh->priv_key; } - -inline RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey) - { return pkey->pkey.rsa; } - -inline EVP_MD_CTX *EVP_MD_CTX_new () - { return EVP_MD_CTX_create(); } -inline void EVP_MD_CTX_free (EVP_MD_CTX *ctx) - { EVP_MD_CTX_destroy (ctx); } - -// ssl -#define TLS_method TLSv1_method - -#endif - #endif diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index ad986129..e37d4039 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -181,5 +181,21 @@ namespace crypto k.GetPrivateKey (priv); memcpy (pub, k.GetPublicKey (), 32); } + + LocalEncryptionKey::LocalEncryptionKey (i2p::data::CryptoKeyType t): keyType(t) + { + pub.resize (GetCryptoPublicKeyLen (keyType)); + priv.resize (GetCryptoPrivateKeyLen (keyType)); + } + + void LocalEncryptionKey::GenerateKeys () + { + i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv.data (), pub.data ()); + } + + void LocalEncryptionKey::CreateDecryptor () + { + decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv.data ()); + } } } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index a7d86d09..b6c37ddf 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include "Crypto.h" +#include "Identity.h" namespace i2p { @@ -157,7 +158,50 @@ namespace crypto X25519Keys m_StaticKeys; }; - void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); + void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); // including hybrid + + constexpr size_t GetCryptoPrivateKeyLen (i2p::data::CryptoKeyType type) + { + switch (type) + { + case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256; + case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32; + case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; + // ML-KEM hybrid + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: + return 32; + }; + return 0; + } + + constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type) + { + switch (type) + { + case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256; + case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32; + case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; + // ML-KEM hybrid + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: + case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: + return 32; + }; + return 0; + } + + struct LocalEncryptionKey + { + std::vector pub, priv; + i2p::data::CryptoKeyType keyType; + std::shared_ptr decryptor; + + LocalEncryptionKey (i2p::data::CryptoKeyType t); + void GenerateKeys (); + void CreateDecryptor (); + }; } } diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 30635b09..732efca7 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,7 +19,7 @@ namespace i2p namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): - m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) + m_Owner (owner), m_DefaultReceiver (nullptr), m_DefaultRawReceiver (nullptr), m_Gzip (gzip) { if (m_Gzip) m_Deflator.reset (new i2p::data::GzipDeflator); @@ -104,8 +104,7 @@ namespace datagram if (verified) { - auto h = identity.GetIdentHash(); - auto session = ObtainSession(h); + auto session = ObtainSession (identity.GetIdentHash()); session->Ack(); auto r = FindReceiver(toPort); if(r) @@ -119,19 +118,79 @@ namespace datagram void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - if (m_RawReceiver) - m_RawReceiver (fromPort, toPort, buf, len); + auto r = FindRawReceiver(toPort); + + if (r) + r (fromPort, toPort, buf, len); else LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram"); } + void DatagramDestination::SetReceiver (const Receiver& receiver, uint16_t port) + { + std::lock_guard lock(m_ReceiversMutex); + m_ReceiversByPorts[port] = receiver; + if (!m_DefaultReceiver) { + m_DefaultReceiver = receiver; + m_DefaultReceiverPort = port; + } + } + + void DatagramDestination::ResetReceiver (uint16_t port) + { + std::lock_guard lock(m_ReceiversMutex); + m_ReceiversByPorts.erase (port); + if (m_DefaultReceiverPort == port) { + m_DefaultReceiver = nullptr; + m_DefaultReceiverPort = 0; + } + } + + + void DatagramDestination::SetRawReceiver (const RawReceiver& receiver, uint16_t port) + { + std::lock_guard lock(m_RawReceiversMutex); + m_RawReceiversByPorts[port] = receiver; + if (!m_DefaultRawReceiver) { + m_DefaultRawReceiver = receiver; + m_DefaultRawReceiverPort = port; + } + } + + void DatagramDestination::ResetRawReceiver (uint16_t port) + { + std::lock_guard lock(m_RawReceiversMutex); + m_RawReceiversByPorts.erase (port); + if (m_DefaultRawReceiverPort == port) { + m_DefaultRawReceiver = nullptr; + m_DefaultRawReceiverPort = 0; + } + } + + DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port) { std::lock_guard lock(m_ReceiversMutex); - Receiver r = m_Receiver; + Receiver r = nullptr; auto itr = m_ReceiversByPorts.find(port); if (itr != m_ReceiversByPorts.end()) r = itr->second; + else { + r = m_DefaultReceiver; + } + return r; + } + + DatagramDestination::RawReceiver DatagramDestination::FindRawReceiver(uint16_t port) + { + std::lock_guard lock(m_RawReceiversMutex); + RawReceiver r = nullptr; + auto itr = m_RawReceiversByPorts.find(port); + if (itr != m_RawReceiversByPorts.end()) + r = itr->second; + else { + r = m_DefaultRawReceiver; + } return r; } @@ -228,8 +287,8 @@ namespace datagram DatagramSession::DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent) : - m_LocalDestination(localDestination), - m_RemoteIdent(remoteIdent), + m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), + m_LastUse (0), m_LastFlush (0), m_RequestingLS(false) { } @@ -250,8 +309,12 @@ namespace datagram if (msg || m_SendQueue.empty ()) m_SendQueue.push_back(msg); // flush queue right away if full - if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) + if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE || + m_LastUse > m_LastFlush + DATAGRAM_MAX_FLUSH_INTERVAL) + { FlushSendQueue(); + m_LastFlush = m_LastUse; + } } DatagramSession::Info DatagramSession::GetSessionInfo() const @@ -284,7 +347,7 @@ namespace datagram if(path) path->updateTime = i2p::util::GetSecondsSinceEpoch (); if (IsRatchets ()) - SendMsg (nullptr); // send empty message in case if we have some data to send + SendMsg (nullptr); // send empty message in case if we don't have some data to send } std::shared_ptr DatagramSession::GetSharedRoutingPath () @@ -317,15 +380,19 @@ namespace datagram if (!found) { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) - m_PendingRoutingSessions.push_back (m_RoutingSession); + if (m_RoutingSession) + { + m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL); + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) + m_PendingRoutingSessions.push_back (m_RoutingSession); + } } } auto path = m_RoutingSession->GetSharedRoutingPath(); - if (path && m_RoutingSession->IsRatchets () && - m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) + if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) { + LogPrint (eLogDebug, "Datagram: path reset"); m_RoutingSession->SetSharedRoutingPath (nullptr); path = nullptr; } @@ -353,7 +420,14 @@ namespace datagram auto sz = ls.size(); if (sz) { - auto idx = rand() % sz; + int idx = -1; + if (m_LocalDestination) + { + auto pool = m_LocalDestination->GetTunnelPool (); + if (pool) + idx = pool->GetRng ()() % sz; + } + if (idx < 0) idx = rand () % sz; path->remoteLease = ls[idx]; } else @@ -379,7 +453,14 @@ namespace datagram auto sz = ls.size(); if (sz) { - auto idx = rand() % sz; + int idx = -1; + if (m_LocalDestination) + { + auto pool = m_LocalDestination->GetTunnelPool (); + if (pool) + idx = pool->GetRng ()() % sz; + } + if (idx < 0) idx = rand () % sz; path->remoteLease = ls[idx]; } else @@ -425,7 +506,7 @@ namespace datagram if (m) send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m}); } - routingPath->outboundTunnel->SendTunnelDataMsg(send); + routingPath->outboundTunnel->SendTunnelDataMsgs(send); } m_SendQueue.clear(); } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index a55c8edf..dd358434 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ #include #include #include "Base.h" +#include "Gzip.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" @@ -30,8 +31,6 @@ namespace datagram { // milliseconds for max session idle time const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; - // milliseconds for how long we try sticking to a dead routing path before trying to switch - const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000; // milliseconds interval a routing path is used before switching const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; // milliseconds before lease expire should we try switching leases @@ -42,6 +41,8 @@ namespace datagram const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000; // max 64 messages buffered in send queue for each datagram session const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; + const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds + const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds class DatagramSession : public std::enable_shared_from_this { @@ -97,7 +98,7 @@ namespace datagram std::shared_ptr m_RoutingSession; std::vector > m_PendingRoutingSessions; std::vector > m_SendQueue; - uint64_t m_LastUse; + uint64_t m_LastUse, m_LastFlush; // milliseconds bool m_RequestingLS; }; @@ -125,14 +126,12 @@ namespace datagram void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); - void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; - void ResetReceiver () { m_Receiver = nullptr; }; - void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; }; - void ResetReceiver (uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); }; + void SetReceiver (const Receiver& receiver, uint16_t port); + void ResetReceiver (uint16_t port); - void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; }; - void ResetRawReceiver () { m_RawReceiver = nullptr; }; + void SetRawReceiver (const RawReceiver& receiver, uint16_t port); + void ResetRawReceiver (uint16_t port); std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); @@ -149,20 +148,26 @@ namespace datagram void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - /** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */ Receiver FindReceiver(uint16_t port); + RawReceiver FindRawReceiver(uint16_t port); private: std::shared_ptr m_Owner; - Receiver m_Receiver; // default - RawReceiver m_RawReceiver; // default - bool m_Gzip; // gzip compression of data messages + std::mutex m_SessionsMutex; std::map m_Sessions; - std::mutex m_ReceiversMutex; - std::map m_ReceiversByPorts; + Receiver m_DefaultReceiver; + RawReceiver m_DefaultRawReceiver; + uint16_t m_DefaultReceiverPort; + uint16_t m_DefaultRawReceiverPort; + std::mutex m_ReceiversMutex; + std::mutex m_RawReceiversMutex; + std::unordered_map m_ReceiversByPorts; + std::unordered_map m_RawReceiversByPorts; + + bool m_Gzip; // gzip compression of data messages i2p::data::GzipInflator m_Inflator; std::unique_ptr m_Deflator; std::vector m_From, m_Signature; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 719f5830..fd23e228 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,6 +13,7 @@ #include #include #include "Crypto.h" +#include "ECIESX25519AEADRatchetSession.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" @@ -23,7 +24,7 @@ namespace i2p { namespace client { - LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service, + LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map * params): m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), @@ -37,6 +38,7 @@ namespace client int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE; int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE; int numTags = DEFAULT_TAGS_TO_SEND; + bool isHighBandwidth = true; std::shared_ptr > explicitPeers; try { @@ -92,7 +94,7 @@ namespace client it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); if (it != params->end ()) { - // oveeride isPublic + // override isPublic m_IsPublic = (it->second != "true"); } it = params->find (I2CP_PARAM_LEASESET_TYPE); @@ -108,7 +110,7 @@ namespace client if (authType >= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE && authType <= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK) m_AuthType = authType; else - LogPrint (eLogError, "Destination: Unknown auth type ", authType); + LogPrint (eLogError, "Destination: Unknown auth type: ", authType); } } it = params->find (I2CP_PARAM_LEASESET_PRIV_KEY); @@ -117,10 +119,13 @@ namespace client m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>()); if (m_LeaseSetPrivKey->FromBase64 (it->second) != 32) { - LogPrint(eLogError, "Destination: Invalid value i2cp.leaseSetPrivKey ", it->second); + LogPrint(eLogCritical, "Destination: Invalid value i2cp.leaseSetPrivKey: ", it->second); m_LeaseSetPrivKey.reset (nullptr); } } + it = params->find (I2CP_PARAM_STREAMING_PROFILE); + if (it != params->end ()) + isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE; } } catch (std::exception & ex) @@ -128,7 +133,7 @@ namespace client LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } SetNumTags (numTags); - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -164,7 +169,7 @@ namespace client LoadTags (); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -191,7 +196,7 @@ namespace client m_IsPublic = itr->second != "true"; } - int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency; + int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0; std::map intOpts = { {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, @@ -262,17 +267,6 @@ namespace client return nullptr; } } - else - { - auto ls = i2p::data::netdb.FindLeaseSet (ident); - if (ls && !ls->IsExpired ()) - { - ls->PopulateLeases (); // since we don't store them in netdb - std::lock_guard _lock(m_RemoteLeaseSetsMutex); - m_RemoteLeaseSets[ident] = ls; - return ls; - } - } return nullptr; } @@ -301,7 +295,7 @@ namespace client if (m_IsPublic) { auto s = shared_from_this (); - m_Service.post ([s](void) + boost::asio::post (m_Service, [s](void) { s->m_PublishVerificationTimer.cancel (); s->Publish (); @@ -329,7 +323,7 @@ namespace client memcpy (data.k, key, 32); memcpy (data.t, tag, 32); auto s = shared_from_this (); - m_Service.post ([s,data](void) + boost::asio::post (m_Service, [s,data](void) { s->AddSessionKey (data.k, data.t); }); @@ -346,7 +340,7 @@ namespace client memcpy (data.k, key, 32); data.t = tag; auto s = shared_from_this (); - m_Service.post ([s,data](void) + boost::asio::post (m_Service, [s,data](void) { s->AddECIESx25519Key (data.k, data.t); }); @@ -354,23 +348,42 @@ namespace client void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); + if (!msg) return; + bool empty = false; + { + std::lock_guard l(m_IncomingMsgsQueueMutex); + empty = m_IncomingMsgsQueue.empty (); + m_IncomingMsgsQueue.push_back (msg); + } + if (empty) + boost::asio::post (m_Service, [s = shared_from_this ()]() + { + std::list > receivedMsgs; + { + std::lock_guard l(s->m_IncomingMsgsQueueMutex); + s->m_IncomingMsgsQueue.swap (receivedMsgs); + } + for (auto& it: receivedMsgs) + s->HandleGarlicMessage (it); + }); } void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); - m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID)); + boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID)); } void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len) { I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); - LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); + LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, + GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr); } - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) + bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) { switch (typeID) { @@ -378,11 +391,14 @@ namespace client HandleDataMessage (payload, len); break; case eI2NPDeliveryStatus: - // we assume tunnel tests non-encrypted HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET)); break; + case eI2NPTunnelTest: + if (m_Pool) + m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)); + break; case eI2NPDatabaseStore: - HandleDatabaseStoreMessage (payload, len); + HandleDatabaseStoreMessage (payload, len, from); break; case eI2NPDatabaseSearchReply: HandleDatabaseSearchReplyMessage (payload, len); @@ -397,8 +413,14 @@ namespace client return true; } - void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) { + 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,8 +428,14 @@ 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; + std::shared_ptr request; switch (buf[DATABASE_STORE_TYPE_OFFSET]) { case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1 @@ -441,8 +469,21 @@ namespace client if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else - leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2 - if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) + { + leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], + buf + offset, len - offset, true, from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2 + if (from) + { + uint8_t pub[32]; + leaseSet->Encrypt (nullptr, pub); + if (memcmp (from->GetRemoteStaticKey (), pub, 32)) + { + LogPrint (eLogError, "Destination: Remote LeaseSet static key mismatch"); + leaseSet = nullptr; + } + } + } + if (leaseSet && leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) { if (leaseSet->GetIdentHash () != GetIdentHash ()) { @@ -463,31 +504,60 @@ namespace client case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5 { auto it2 = m_LeaseSetRequests.find (key); - if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) + if (it2 != m_LeaseSetRequests.end ()) { - auto ls2 = std::make_shared (buf + offset, len - offset, - it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ()); - if (ls2->IsValid ()) + request = it2->second; + m_LeaseSetRequests.erase (it2); + if (request->requestedBlindedKey) { - m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key - m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup - leaseSet = ls2; + auto ls2 = std::make_shared (buf + offset, len - offset, + request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, + GetPreferredCryptoType ()); + 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 + } + else + LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed"); + } + else + { + // publishing verification doesn't have requestedBlindedKey + auto localLeaseSet = GetLeaseSetMt (); + if (localLeaseSet->GetStoreHash () == key) + { + auto ls = std::make_shared (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, + localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false); + leaseSet = ls; + } + else + LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key"); } } else - LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2"); + LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2"); break; } default: LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped"); } - auto it1 = m_LeaseSetRequests.find (key); - if (it1 != m_LeaseSetRequests.end ()) + if (!request) { - it1->second->requestTimeoutTimer.cancel (); - if (it1->second) it1->second->Complete (leaseSet); - m_LeaseSetRequests.erase (it1); + auto it1 = m_LeaseSetRequests.find (key); + if (it1 != m_LeaseSetRequests.end ()) + { + request = it1->second; + m_LeaseSetRequests.erase (it1); + } + } + if (request) + { + request->requestTimeoutTimer.cancel (); + request->Complete (leaseSet); } } @@ -500,38 +570,43 @@ namespace client if (it != m_LeaseSetRequests.end ()) { auto request = it->second; - bool found = false; - if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) + for (int i = 0; i < num; i++) { - for (int i = 0; i < num; i++) + i2p::data::IdentHash peerHash (buf + 33 + i*32); + if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - i2p::data::IdentHash peerHash (buf + 33 + i*32); - if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) - { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); - i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory - } - } - - auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded); - if (floodfill) - { - LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ()); - if (SendLeaseSetRequest (key, floodfill, request)) - found = true; + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); + i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory } } - if (!found) - { - LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); - request->Complete (nullptr); - m_LeaseSetRequests.erase (key); - } + SendNextLeaseSetRequest (key, request); } else LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found"); } + void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key, + std::shared_ptr request) + { + bool found = false; + if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) + { + auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded); + if (floodfill) + { + LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ()); + if (SendLeaseSetRequest (key, floodfill, request)) + found = true; + } + } + if (!found) + { + LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); + request->Complete (nullptr); + m_LeaseSetRequests.erase (key); + } + } + void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID) { if (msgID == m_PublishReplyToken) @@ -540,7 +615,8 @@ namespace client m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; // schedule verification - m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + + (m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0))); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); } @@ -548,9 +624,12 @@ namespace client i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); } - void LeaseSetDestination::SetLeaseSetUpdated () + void LeaseSetDestination::SetLeaseSetUpdated (bool post) { - UpdateLeaseSet (); + if (post) + boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); }); + else + UpdateLeaseSet (); } void LeaseSetDestination::Publish () @@ -576,12 +655,7 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - if (!m_Pool->GetInboundTunnels ().size () || !m_Pool->GetOutboundTunnels ().size ()) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. Destination is not ready"); - return; - } - auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); + auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills); if (!floodfill) { LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); @@ -592,26 +666,39 @@ namespace client auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); if (!outbound || !inbound) { - LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill"); - m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); - floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); - if (floodfill) + if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ()) { - outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); - if (outbound) + LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill"); + m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); + floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills); + if (floodfill) { - inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); - if (!inbound) - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + if (outbound) + { + inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); + if (!inbound) + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + } + else + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); } else - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); + LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); } else - LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); + LogPrint (eLogDebug, "Destination: No tunnels in pool"); + if (!floodfill || !outbound || !inbound) { + // we can't publish now m_ExcludedFloodfills.clear (); + m_PublishReplyToken = 1; // dummy non-zero value + // try again after a while + LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds"); + m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); + m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, + shared_from_this (), std::placeholders::_1)); return; } } @@ -619,10 +706,19 @@ namespace client LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); - m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); + auto s = shared_from_this (); + msg->onDrop = [s]() + { + boost::asio::post (s->GetService (), [s]() + { + s->m_PublishConfirmationTimer.cancel (); + s->HandlePublishConfirmationTimer (boost::system::error_code()); + }); + }; + m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); + outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg); m_LastSubmissionTime = ts; } @@ -635,15 +731,15 @@ namespace client m_PublishReplyToken = 0; if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); Publish (); } else { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); // Java floodfill never sends confirmation back for unknown crypto type // assume it successive and try to verify - m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); @@ -698,10 +794,10 @@ namespace client if (!m_Pool || !IsReady ()) { if (requestComplete) - m_Service.post ([requestComplete](void){requestComplete (nullptr);}); + boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);}); return false; } - m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr)); + boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr)); return true; } @@ -710,7 +806,7 @@ namespace client if (!dest || !m_Pool || !IsReady ()) { if (requestComplete) - m_Service.post ([requestComplete](void){requestComplete (nullptr);}); + boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);}); return false; } auto storeHash = dest->GetStoreHash (); @@ -718,17 +814,17 @@ namespace client if (leaseSet) { if (requestComplete) - m_Service.post ([requestComplete, leaseSet](void){requestComplete (leaseSet);}); + boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);}); return true; } - m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest)); + boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest)); return true; } void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify) { auto s = shared_from_this (); - m_Service.post ([dest, notify, s](void) + boost::asio::post (m_Service, [dest, notify, s](void) { auto it = s->m_LeaseSetRequests.find (dest); if (it != s->m_LeaseSetRequests.end ()) @@ -748,7 +844,7 @@ namespace client void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr requestedBlindedKey) { - std::set excluded; + std::unordered_set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded); if (floodfill) { @@ -756,16 +852,24 @@ namespace client request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2 if (requestComplete) request->requestComplete.push_back (requestComplete); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ret = m_LeaseSetRequests.insert (std::pair >(dest,request)); if (ret.second) // inserted { 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 +896,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) { @@ -812,9 +916,18 @@ namespace client AddECIESx25519Key (replyKey, replyTag); else AddSessionKey (replyKey, replyTag); - auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, - request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); - request->outboundTunnel->SendTunnelDataMsg ( + + auto msg = WrapMessageForRouter (nextFloodfill, + CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); + auto s = shared_from_this (); + msg->onDrop = [s, dest, request]() + { + boost::asio::post (s->GetService (), [s, dest, request]() + { + s->SendNextLeaseSetRequest (dest, request); + }); + }; + request->outboundTunnel->SendTunnelDataMsgs ( { i2p::tunnel::TunnelMessageBlock { @@ -822,7 +935,7 @@ namespace client nextFloodfill->GetIdentHash (), 0, msg } }); - request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); + request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT)); request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer, shared_from_this (), std::placeholders::_1, dest)); } @@ -839,7 +952,7 @@ namespace client if (it != m_LeaseSetRequests.end ()) { bool done = false; - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT) { auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded); @@ -876,7 +989,8 @@ namespace client CleanupExpiredTags (); CleanupRemoteLeaseSets (); CleanupDestination (); - m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT + + (m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0))); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -890,7 +1004,7 @@ namespace client { if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired { - LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); } else @@ -898,19 +1012,15 @@ namespace client } } - i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const - { - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; - return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; - } - - ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (service, isPublic, params), - m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), - m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), - m_DatagramDestination (nullptr), m_RefCounter (0), + m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED), + m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED), + m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS), + m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0), + m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0), m_ReadyChecker(service) { if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) @@ -930,7 +1040,15 @@ namespace client { try { - encryptionKeyTypes.insert (std::stoi(it1)); + i2p::data::CryptoKeyType cryptoType = std::stoi(it1); +#if !OPENSSL_PQ + if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported +#endif + { + if (!m_PreferredCryptoType && cryptoType) + m_PreferredCryptoType = cryptoType; // first non-zero in the list + encryptionKeyTypes.insert (cryptoType); + } } catch (std::exception& ex) { @@ -941,29 +1059,21 @@ namespace client } } // if no param or valid crypto type use from identity - bool isSingleKey = false; if (encryptionKeyTypes.empty ()) - { - isSingleKey = true; - encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ()); - } + encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (), + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4 for (auto& it: encryptionKeyTypes) { - auto encryptionKey = new EncryptionKey (it); + auto encryptionKey = std::make_shared (it); if (IsPublic ()) - PersistTemporaryKeys (encryptionKey, isSingleKey); + PersistTemporaryKeys (encryptionKey); else encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); - if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - { - m_ECIESx25519EncryptionKey.reset (encryptionKey); - if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) - SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2 - } - else - m_StandardEncryptionKey.reset (encryptionKey); + if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) + SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1 + m_EncryptionKeys.emplace (it, encryptionKey); } if (IsPublic ()) @@ -977,9 +1087,17 @@ namespace client auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); if (it != params->end ()) m_StreamingAckDelay = std::stoi(it->second); + it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED); + if (it != params->end ()) + m_StreamingOutboundSpeed = std::stoi(it->second); + it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED); + if (it != params->end ()) + m_StreamingInboundSpeed = std::stoi(it->second); + if (it != params->end ()) + m_StreamingMaxConcurrentStreams = std::stoi(it->second); it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end ()) - m_IsStreamingAnswerPings = (it->second == "true"); + m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { @@ -993,12 +1111,12 @@ namespace client else if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK) ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params); else - LogPrint (eLogError, "Destination: Unexpected auth type ", authType); + LogPrint (eLogError, "Destination: Unexpected auth type: ", authType); if (m_AuthKeys->size ()) LogPrint (eLogInfo, "Destination: ", m_AuthKeys->size (), " auth keys read"); else { - LogPrint (eLogError, "Destination: No auth keys read for auth type ", authType); + LogPrint (eLogCritical, "Destination: No auth keys read for auth type: ", authType); m_AuthKeys = nullptr; } } @@ -1007,7 +1125,7 @@ namespace client } catch (std::exception & ex) { - LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); + LogPrint(eLogCritical, "Destination: Unable to parse parameters for destination: ", ex.what()); } } @@ -1026,22 +1144,30 @@ namespace client void ClientDestination::Stop () { - LeaseSetDestination::Stop (); + LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p"); m_ReadyChecker.cancel(); + LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination"); m_StreamingDestination->Stop (); //m_StreamingDestination->SetOwner (nullptr); m_StreamingDestination = nullptr; + + LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination by ports"); for (auto& it: m_StreamingDestinationsByPorts) { it.second->Stop (); //it.second->SetOwner (nullptr); } m_StreamingDestinationsByPorts.clear (); + m_LastStreamingDestination = nullptr; + if (m_DatagramDestination) { + LogPrint(eLogDebug, "Destination: -> Stopping Datagram Destination"); delete m_DatagramDestination; m_DatagramDestination = nullptr; } + LeaseSetDestination::Stop (); + LogPrint(eLogDebug, "Destination: -> Stopping done"); } void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) @@ -1061,9 +1187,15 @@ namespace client case PROTOCOL_TYPE_STREAMING: { // streaming protocol - auto dest = GetStreamingDestination (toPort); - if (dest) - dest->HandleDataMessagePayload (buf, length); + if (toPort != m_LastPort || !m_LastStreamingDestination) + { + m_LastStreamingDestination = GetStreamingDestination (toPort); + if (!m_LastStreamingDestination) + m_LastStreamingDestination = m_StreamingDestination; // if no destination on port use default + m_LastPort = toPort; + } + if (m_LastStreamingDestination) + m_LastStreamingDestination->HandleDataMessagePayload (buf, length); else LogPrint (eLogError, "Destination: Missing streaming destination"); } @@ -1087,7 +1219,7 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, uint16_t port) { if (!streamRequestComplete) { @@ -1096,7 +1228,13 @@ namespace client } auto leaseSet = FindLeaseSet (dest); if (leaseSet) - streamRequestComplete(CreateStream (leaseSet, port)); + { + auto stream = CreateStream (leaseSet, port); + boost::asio::post (GetService (), [streamRequestComplete, stream]() + { + streamRequestComplete(stream); + }); + } else { auto s = GetSharedFromThis (); @@ -1111,7 +1249,7 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port) { if (!streamRequestComplete) { @@ -1129,7 +1267,42 @@ namespace client }); } - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) + template + std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, uint16_t port) + { + volatile bool done = false; + std::shared_ptr stream; + std::condition_variable streamRequestComplete; + std::mutex streamRequestCompleteMutex; + CreateStream ( + [&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) + { + stream = s; + std::unique_lock l(streamRequestCompleteMutex); + streamRequestComplete.notify_all (); + done = true; + }, + dest, port); + while (!done) + { + std::unique_lock l(streamRequestCompleteMutex); + if (!done) + streamRequestComplete.wait (l); + } + return stream; + } + + std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, uint16_t port) + { + return CreateStreamSync (dest, port); + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr dest, uint16_t port) + { + return CreateStreamSync (dest, port); + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, uint16_t port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); @@ -1166,7 +1339,7 @@ namespace client }); } - std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + std::shared_ptr ClientDestination::GetStreamingDestination (uint16_t port) const { if (port) { @@ -1174,8 +1347,9 @@ namespace client if (it != m_StreamingDestinationsByPorts.end ()) return it->second; } - // if port is zero or not found, use default destination - return m_StreamingDestination; + else // if port is zero, use default destination + return m_StreamingDestination; + return nullptr; } void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) @@ -1203,7 +1377,7 @@ namespace client m_StreamingDestination->AcceptOnce (acceptor); } - std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) + std::shared_ptr ClientDestination::CreateStreamingDestination (uint16_t port, bool gzip) { auto dest = std::make_shared (GetSharedFromThis (), port, gzip); if (port) @@ -1213,7 +1387,7 @@ namespace client return dest; } - std::shared_ptr ClientDestination::RemoveStreamingDestination (int port) + std::shared_ptr ClientDestination::RemoveStreamingDestination (uint16_t port) { if (port) { @@ -1249,32 +1423,56 @@ namespace client return ret; } - void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey) + void ClientDestination::PersistTemporaryKeys (std::shared_ptr keys) { if (!keys) return; std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", - isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat")); + std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat"); std::ifstream f(path, std::ifstream::binary); - - if (f) { - f.read ((char *)keys->pub, 256); - f.read ((char *)keys->priv, 256); - return; + if (f) + { + size_t len = 0; + if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) + len = 512; + else if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + { + f.seekg (0, std::ios::end); + len = f.tellg(); + f.seekg (0, std::ios::beg); + } + + if (len == 512) + { + char pub[256], priv[256]; + f.read (pub, 256); + memcpy (keys->pub.data(), pub, keys->pub.size()); + f.read (priv, 256); + memcpy (keys->priv.data (), priv, keys->priv.size ()); + } + else + { + f.read ((char *)keys->pub.data(), keys->pub.size()); + f.read ((char *)keys->priv.data(), keys->priv.size()); + } + if (f) + return; + else + LogPrint(eLogWarning, "Destination: Can't read keys from ", path); } - LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p"); - memset (keys->priv, 0, 256); - memset (keys->pub, 0, 256); + LogPrint (eLogInfo, "Destination: Creating new temporary keys of type ", keys->keyType, " for address ", ident, ".b32.i2p"); + memset (keys->priv.data (), 0, keys->priv.size ()); + memset (keys->pub.data (), 0, keys->pub.size ()); keys->GenerateKeys (); - // TODO:: persist crypto key type + std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); - if (f1) { - f1.write ((char *)keys->pub, 256); - f1.write ((char *)keys->priv, 256); - return; + if (f1) + { + f1.write ((char *)keys->pub.data (), keys->pub.size ()); + f1.write ((char *)keys->priv.data (), keys->priv.size ()); } - LogPrint(eLogError, "Destinations: Can't save keys to ", path); + if (!f1) + LogPrint(eLogError, "Destination: Can't save keys to ", path); } void ClientDestination::CreateNewLeaseSet (const std::vector >& tunnels) @@ -1282,9 +1480,10 @@ namespace client std::shared_ptr leaseSet; if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) { - if (m_StandardEncryptionKey) + auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); + if (it != m_EncryptionKeys.end ()) { - leaseSet = std::make_shared (GetIdentity (), m_StandardEncryptionKey->pub, tunnels); + leaseSet = std::make_shared (GetIdentity (), it->second->pub.data (), tunnels); // sign Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); } @@ -1294,18 +1493,40 @@ namespace client else { // standard LS2 (type 3) first - i2p::data::LocalLeaseSet2::KeySections keySections; - if (m_ECIESx25519EncryptionKey) - keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} ); - if (m_StandardEncryptionKey) - keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} ); - + if (m_EncryptionKeys.empty ()) + { + LogPrint (eLogError, "Destinations: No encryption keys"); + return; + } + + i2p::data::LocalLeaseSet2::EncryptionKeys keySections; + std::shared_ptr preferredSection; + if (m_EncryptionKeys.size () == 1) + preferredSection = m_EncryptionKeys.begin ()->second; // only key + else + { + for (const auto& it: m_EncryptionKeys) + if (it.first == m_PreferredCryptoType) + preferredSection = it.second; + else + keySections.push_back (it.second); + } + if (preferredSection) + keySections.push_front (preferredSection); // make preferred first + + auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch (); + if (publishedTimestamp <= m_LastPublishedTimestamp) + { + LogPrint (eLogDebug, "Destination: LeaseSet update at the same second"); + publishedTimestamp++; // force newer timestamp + } bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, - m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted); + m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted); if (isPublishedEncrypted) // encrypt if type 5 ls2 = std::make_shared (ls2, m_Keys, GetAuthType (), m_AuthKeys); leaseSet = ls2; + m_LastPublishedTimestamp = publishedTimestamp; } SetLeaseSet (leaseSet); } @@ -1317,11 +1538,22 @@ namespace client bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) - return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data); - if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) - return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data); + std::shared_ptr encryptionKey; + if (!m_EncryptionKeys.empty ()) + { + if (m_EncryptionKeys.rbegin ()->first == preferredCrypto) + encryptionKey = m_EncryptionKeys.rbegin ()->second; + else + { + auto it = m_EncryptionKeys.find (preferredCrypto); + if (it != m_EncryptionKeys.end ()) + encryptionKey = it->second; + } + if (!encryptionKey) + encryptionKey = m_EncryptionKeys.rbegin ()->second; + } + if (encryptionKey) + return encryptionKey->decryptor->Decrypt (encrypted, data); else LogPrint (eLogError, "Destinations: Decryptor is not set"); return false; @@ -1329,14 +1561,26 @@ namespace client bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; +#if __cplusplus >= 202002L // C++20 + return m_EncryptionKeys.contains (keyType); +#else + return m_EncryptionKeys.count (keyType) > 0; +#endif } + i2p::data::CryptoKeyType ClientDestination::GetRatchetsHighestCryptoType () const + { + if (m_EncryptionKeys.empty ()) return 0; + auto cryptoType = m_EncryptionKeys.rbegin ()->first; + return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0; + } + const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; - return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; + auto it = m_EncryptionKeys.find (keyType); + if (it != m_EncryptionKeys.end ()) + return it->second->pub.data (); + return nullptr; } void ClientDestination::ReadAuthKey (const std::string& group, const std::map * params) @@ -1351,7 +1595,7 @@ namespace client if (pubKey.FromBase64 (it.second.substr (pos+1))) m_AuthKeys->push_back (pubKey); else - LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1)); + LogPrint (eLogCritical, "Destination: Unexpected auth key: ", it.second.substr (pos+1)); } } } @@ -1370,6 +1614,8 @@ namespace client RunnableService ("Destination"), ClientDestination (GetIOService (), keys, isPublic, params) { + if (!GetNickname ().empty ()) + RunnableService::SetName (GetNickname ()); } RunnableClientDestination::~RunnableClientDestination () diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 4b08ec51..35557859 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,13 +14,15 @@ #include #include #include -#include +#include +#include #include #include #include #include "Identity.h" #include "TunnelPool.h" #include "Crypto.h" +#include "CryptoKey.h" #include "LeaseSet.h" #include "Garlic.h" #include "NetDb.hpp" @@ -35,13 +37,15 @@ namespace client const uint8_t PROTOCOL_TYPE_STREAMING = 6; const uint8_t PROTOCOL_TYPE_DATAGRAM = 17; const uint8_t PROTOCOL_TYPE_RAW = 18; - const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds - const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish + const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds + const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish + const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds const int PUBLISH_MIN_INTERVAL = 20; // in seconds const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically - const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds - const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds - const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes + const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds + const int MAX_LEASESET_REQUEST_TIMEOUT = 12000; // in milliseconds + const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds + const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; // I2CP @@ -83,9 +87,19 @@ namespace client // streaming const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds + const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec + const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s + const char I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED[] = "i2p.streaming.maxInboundSpeed"; // bytes/sec + const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; const int DEFAULT_ANSWER_PINGS = true; - + const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile"; + const int STREAMING_PROFILE_BULK = 1; // high bandwidth + const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth + const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK; + const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams"; + const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048; + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, @@ -95,8 +109,8 @@ namespace client // leaseSet = nullptr means not found struct LeaseSetRequest { - LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {}; - std::set excluded; + LeaseSetRequest (boost::asio::io_context& service): requestTime (0), requestTimeoutTimer (service) {}; + std::unordered_set excluded; uint64_t requestTime; boost::asio::deadline_timer requestTimeoutTimer; std::list requestComplete; @@ -113,10 +127,10 @@ namespace client public: - LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map * params = nullptr); + LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map * params = nullptr); ~LeaseSetDestination (); const std::string& GetNickname () const { return m_Nickname; }; - boost::asio::io_service& GetService () { return m_Service; }; + auto& GetService () { return m_Service; }; virtual void Start (); virtual void Stop (); @@ -133,15 +147,15 @@ namespace client void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr dest, bool notify = true); // implements GarlicDestination - std::shared_ptr GetLeaseSet (); - std::shared_ptr GetTunnelPool () const { return m_Pool; } + std::shared_ptr GetLeaseSet () override; + std::shared_ptr GetTunnelPool () const override { return m_Pool; } // override GarlicDestination - bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); - void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag); - void ProcessGarlicMessage (std::shared_ptr msg); - void ProcessDeliveryStatusMessage (std::shared_ptr msg); - void SetLeaseSetUpdated (); + bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag) override; + void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override; + void ProcessGarlicMessage (std::shared_ptr msg) override; + void ProcessDeliveryStatusMessage (std::shared_ptr msg) override; + void SetLeaseSetUpdated (bool post) override; bool IsPublic () const { return m_IsPublic; }; void SetPublic (bool pub) { m_IsPublic = pub; }; @@ -149,18 +163,20 @@ namespace client protected: // implements GarlicDestination - void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); + void HandleI2NPMessage (const uint8_t * buf, size_t len) override; + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override; void SetLeaseSet (std::shared_ptr newLeaseSet); int GetLeaseSetType () const { return m_LeaseSetType; }; void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; }; int GetAuthType () const { return m_AuthType; }; virtual void CleanupDestination () {}; // additional clean up in derived classes + virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0; // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; - + private: void UpdateLeaseSet (); @@ -169,31 +185,34 @@ namespace client void HandlePublishConfirmationTimer (const boost::system::error_code& ecode); void HandlePublishVerificationTimer (const boost::system::error_code& ecode); void HandlePublishDelayTimer (const boost::system::error_code& ecode); - void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len); + void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from); void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len); void HandleDeliveryStatusMessage (uint32_t msgID); void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr requestedBlindedKey = nullptr); bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); + void SendNextLeaseSetRequest (const i2p::data::IdentHash& key, std::shared_ptr request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); - i2p::data::CryptoKeyType GetPreferredCryptoType () const; private: - boost::asio::io_service& m_Service; + boost::asio::io_context& m_Service; mutable std::mutex m_RemoteLeaseSetsMutex; - std::map > m_RemoteLeaseSets; - std::map > m_LeaseSetRequests; + std::unordered_map > m_RemoteLeaseSets; + std::unordered_map > m_LeaseSetRequests; + std::list > m_IncomingMsgsQueue; + mutable std::mutex m_IncomingMsgsQueueMutex; + std::shared_ptr m_Pool; std::mutex m_LeaseSetMutex; std::shared_ptr m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; uint64_t m_LastSubmissionTime; // in seconds - std::set m_ExcludedFloodfills; // for publishing + std::unordered_set m_ExcludedFloodfills; // for publishing boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_PublishDelayTimer, m_CleanupTimer; @@ -212,25 +231,14 @@ namespace client class ClientDestination: public LeaseSetDestination { - struct EncryptionKey - { - uint8_t pub[256], priv[256]; - i2p::data::CryptoKeyType keyType; - std::shared_ptr decryptor; - - EncryptionKey (i2p::data::CryptoKeyType t):keyType(t) { memset (pub, 0, 256); memset (priv, 0, 256); }; - void GenerateKeys () { i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv, pub); }; - void CreateDecryptor () { decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv); }; - }; - public: - ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); - void Start (); - void Stop (); + void Start () override; + void Stop () override; const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; @@ -241,13 +249,15 @@ namespace client int GetRefCounter () const { return m_RefCounter; }; // streaming - std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional - std::shared_ptr GetStreamingDestination (int port = 0) const; - std::shared_ptr RemoveStreamingDestination (int port); + std::shared_ptr CreateStreamingDestination (uint16_t port, bool gzip = true); // additional + std::shared_ptr GetStreamingDestination (uint16_t port = 0) const; + std::shared_ptr RemoveStreamingDestination (uint16_t port); // following methods operate with default streaming destination - void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); - void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); - std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, uint16_t port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port = 0); + std::shared_ptr CreateStream (const i2p::data::IdentHash& dest, uint16_t port = 0); // sync + std::shared_ptr CreateStream (std::shared_ptr dest, uint16_t port = 0); // sync + std::shared_ptr CreateStream (std::shared_ptr remote, uint16_t port = 0); void SendPing (const i2p::data::IdentHash& to); void SendPing (std::shared_ptr to); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); @@ -255,6 +265,9 @@ namespace client bool IsAcceptingStreams () const; void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); int GetStreamingAckDelay () const { return m_StreamingAckDelay; } + int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; } + int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; } + int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; } bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } // datagram @@ -262,38 +275,47 @@ namespace client i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; - std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; - const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; + std::shared_ptr GetIdentity () const override { return m_Keys.GetPublic (); }; + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; + const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; protected: - void CleanupDestination (); + // GarlicDestionation + i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override; + // LeaseSetDestination + void CleanupDestination () override; + i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; } // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (const std::vector >& tunnels); - + void HandleDataMessage (const uint8_t * buf, size_t len) override; + void CreateNewLeaseSet (const std::vector >& tunnels) override; + private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } - void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); + void PersistTemporaryKeys (std::shared_ptr keys); void ReadAuthKey (const std::string& group, const std::map * params); + template + std::shared_ptr CreateStreamSync (const Dest& dest, uint16_t port); + private: i2p::data::PrivateKeys m_Keys; - std::unique_ptr m_StandardEncryptionKey; - std::unique_ptr m_ECIESx25519EncryptionKey; - - int m_StreamingAckDelay; + std::map > m_EncryptionKeys; // last is most preferable + i2p::data::CryptoKeyType m_PreferredCryptoType; + + int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams; bool m_IsStreamingAnswerPings; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; + std::shared_ptr m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels i2p::datagram::DatagramDestination * m_DatagramDestination; int m_RefCounter; // how many clients(tunnels) use this destination + uint64_t m_LastPublishedTimestamp; boost::asio::deadline_timer m_ReadyChecker; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index e240e925..08af4be3 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include "Log.h" #include "util.h" #include "Crypto.h" +#include "PostQuantum.h" #include "Elligator.h" #include "Tag.h" #include "I2PEndian.h" @@ -94,6 +95,17 @@ namespace garlic m_ItermediateSymmKeys.erase (index); } + ReceiveRatchetTagSet::ReceiveRatchetTagSet (std::shared_ptr session, bool isNS): + m_Session (session), m_IsNS (isNS) + { + } + + ReceiveRatchetTagSet::~ReceiveRatchetTagSet () + { + if (m_IsNS && m_Session) + m_Session->CleanupReceiveNSRKeys (); + } + void ReceiveRatchetTagSet::Expire () { if (!m_ExpirationTimestamp) @@ -117,6 +129,12 @@ namespace garlic return session->HandleNextMessage (buf, len, shared_from_this (), index); } + bool ReceiveRatchetTagSet::IsSessionTerminated () const + { + return !m_Session || m_Session->IsTerminated (); + } + + SymmetricKeyTagSet::SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key): ReceiveRatchetTagSet (nullptr), m_Destination (destination) { @@ -156,12 +174,12 @@ namespace garlic return false; } if (m_Destination) - m_Destination->HandleECIESx25519GarlicClove (buf + offset, size); + m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr); return true; } ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): - GarlicRoutingSession (owner, true) + GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0) { if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; @@ -223,33 +241,104 @@ namespace garlic tagsetNsr->NextSessionTagRatchet (); } + bool ECIESX25519AEADRatchetSession::MessageConfirmed (uint32_t msgID) + { + auto ret = GarlicRoutingSession::MessageConfirmed (msgID); // LeaseSet + if (m_AckRequestMsgID && m_AckRequestMsgID == msgID) + { + m_AckRequestMsgID = 0; + m_AckRequestNumAttempts = 0; + ret = true; + } + return ret; + } + + bool ECIESX25519AEADRatchetSession::CleanupUnconfirmedTags () + { + if (m_AckRequestMsgID && m_AckRequestNumAttempts > ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS) + { + m_AckRequestMsgID = 0; + m_AckRequestNumAttempts = 0; + return true; + } + return false; + } + + void ECIESX25519AEADRatchetSession::CleanupReceiveNSRKeys () + { + m_EphemeralKeys = nullptr; +#if OPENSSL_PQ + m_PQKeys = nullptr; +#endif + } + bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) { if (!GetOwner ()) return false; // we are Bob // KDF1 - i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk - + if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { LogPrint (eLogError, "Garlic: Can't decode elligator"); return false; } buf += 32; len -= 32; - MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + bool decrypted = false; + auto cryptoType = GetOwner ()->GetRatchetsHighestCryptoType (); +#if OPENSSL_PQ + if (cryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // we support post quantum { - LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); - return false; - } - MixKey (sharedSecret); + i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), cryptoType, GetOwner ()->GetEncryptionPublicKey (cryptoType)); // bpk + MixHash (m_Aepk, 32); // h = SHA256(h || aepk) + + if (GetOwner ()->Decrypt (m_Aepk, sharedSecret, cryptoType)) // x25519(bsk, aepk) + { + MixKey (sharedSecret); + + auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType); + std::vector encapsKey(keyLen); + if (Decrypt (buf, encapsKey.data (), keyLen)) + { + decrypted = true; // encaps section has right hash + MixHash (buf, keyLen + 16); + buf += keyLen + 16; + len -= keyLen + 16; + + m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType); + m_PQKeys->SetPublicKey (encapsKey.data ()); + } + } + } +#endif + if (!decrypted) + { + if (cryptoType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || + GetOwner ()->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + { + cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; + i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk + MixHash (m_Aepk, 32); // h = SHA256(h || aepk) + + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } + MixKey (sharedSecret); + } + else + { + LogPrint (eLogWarning, "Garlic: No supported encryption type"); + return false; + } + } // decrypt flags/static - uint8_t nonce[12], fs[32]; - CreateNonce (0, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt + uint8_t fs[32]; + if (!Decrypt (buf, fs, 32)) { LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); return false; @@ -261,21 +350,19 @@ namespace garlic bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); if (isStatic) { - // static key, fs is apk - memcpy (m_RemoteStaticKey, fs, 32); - if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) + // static key, fs is apk + SetRemoteStaticKey (cryptoType, fs); + if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); return false; } MixKey (sharedSecret); } - else // all zeros flags - CreateNonce (1, nonce); // decrypt payload std::vector payload (len - 16); // we must save original ciphertext - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + if (!Decrypt (buf, payload.data (), len - 16)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); return false; @@ -311,7 +398,7 @@ namespace garlic { case eECIESx25519BlkGalicClove: if (GetOwner ()) - GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); + GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this); break; case eECIESx25519BlkNextKey: LogPrint (eLogDebug, "Garlic: Next key"); @@ -327,15 +414,17 @@ namespace garlic auto offset1 = offset; for (auto i = 0; i < numAcks; i++) { - offset1 += 2; // tagsetid - MessageConfirmed (bufbe16toh (buf + offset1)); offset1 += 2; // N + uint32_t tagsetid = bufbe16toh (buf + offset1); offset1 += 2; // tagsetid + uint16_t n = bufbe16toh (buf + offset1); offset1 += 2; // N + MessageConfirmed ((tagsetid << 16) + n); // msgid = (tagsetid << 16) + N } break; } 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: @@ -390,7 +479,6 @@ namespace garlic { uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID bool newKey = flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - m_SendReverseKey = true; if (!m_NextReceiveRatchet) m_NextReceiveRatchet.reset (new DHRatchet ()); else @@ -402,15 +490,14 @@ namespace garlic } m_NextReceiveRatchet->keyID = keyID; } - int tagsetID = 2*keyID; if (newKey) { m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); m_NextReceiveRatchet->newKey = true; - tagsetID++; } else m_NextReceiveRatchet->newKey = false; + auto tagsetID = m_NextReceiveRatchet->GetReceiveTagSetID (); if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) memcpy (m_NextReceiveRatchet->remote, buf, 32); @@ -424,7 +511,9 @@ namespace garlic GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS); receiveTagset->Expire (); + LogPrint (eLogDebug, "Garlic: Next receive tagset ", tagsetID, " created"); + m_SendReverseKey = true; } } @@ -461,7 +550,16 @@ namespace garlic offset += 32; // KDF1 - i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + { + i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), m_RemoteStaticKeyType, m_RemoteStaticKey); // bpk + m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_RemoteStaticKeyType); + m_PQKeys->GenerateKeys (); + } + else +#endif + i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) @@ -470,18 +568,32 @@ namespace garlic return false; } MixKey (sharedSecret); +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + { + auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType); + std::vector encapsKey(keyLen); + m_PQKeys->GetPublicKey (encapsKey.data ()); + // encrypt encapsKey + if (!Encrypt (encapsKey.data (), out + offset, keyLen)) + { + LogPrint (eLogWarning, "Garlic: ML-KEM encap_key section AEAD encryption failed "); + return false; + } + MixHash (out + offset, keyLen + 16); // h = SHA256(h || ciphertext) + offset += keyLen + 16; + } +#endif // encrypt flags/static key section - uint8_t nonce[12]; - CreateNonce (0, nonce); const uint8_t * fs; if (isStatic) - fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType); else { memset (out + offset, 0, 32); // all zeros flags section fs = out + offset; } - if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + if (!Encrypt (fs, out + offset, 32)) { LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); return false; @@ -492,13 +604,11 @@ namespace garlic // KDF2 if (isStatic) { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) - MixKey (sharedSecret); + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk) + MixKey (sharedSecret); } - else - CreateNonce (1, nonce); // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt + if (!Encrypt (payload, out + offset, len)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -536,7 +646,7 @@ namespace garlic } memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR memcpy (m_NSRH, m_H, 32); - offset += 32; + offset += 32; // KDF for Reply Key Section MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) @@ -547,16 +657,33 @@ namespace garlic return false; } MixKey (sharedSecret); +#if OPENSSL_PQ + if (m_PQKeys) + { + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType); + std::vector kemCiphertext(cipherTextLen); + m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret); + + if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen)) + { + LogPrint (eLogWarning, "Garlic: NSR ML-KEM ciphertext section AEAD encryption failed"); + return false; + } + m_NSREncodedPQKey = std::make_unique > (cipherTextLen + 16); + memcpy (m_NSREncodedPQKey->data (), out + offset, cipherTextLen + 16); + MixHash (out + offset, cipherTextLen + 16); + MixKey (sharedSecret); + offset += cipherTextLen + 16; + } +#endif if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); return false; } MixKey (sharedSecret); - uint8_t nonce[12]; - CreateNonce (0, nonce); // calculate hash for zero length - if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + if (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; @@ -577,6 +704,7 @@ namespace garlic GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // encrypt payload + uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0 if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed"); @@ -598,16 +726,34 @@ namespace garlic memcpy (m_H, m_NSRH, 32); MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) - uint8_t nonce[12]; - CreateNonce (0, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + m_N = 0; + size_t offset = 40; +#if OPENSSL_PQ + if (m_PQKeys) + { + if (m_NSREncodedPQKey) + { + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType); + memcpy (out + offset, m_NSREncodedPQKey->data (), cipherTextLen + 16); + MixHash (out + offset, cipherTextLen + 16); + offset += cipherTextLen + 16; + } + else + { + LogPrint (eLogWarning, "Garlic: No stored ML-KEM keys"); + return false; + } + } +#endif + if (!Encrypt (m_NSRH /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; } - MixHash (out + 40, 16); // h = SHA256(h || ciphertext) + MixHash (out + offset, 16); // h = SHA256(h || ciphertext) // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt + uint8_t nonce[12]; memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset + 16, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); return false; @@ -639,13 +785,30 @@ namespace garlic return false; } MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + { + // decrypt kem_ciphertext section + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType); + std::vector kemCiphertext(cipherTextLen); + if (!Decrypt (buf, kemCiphertext.data (), cipherTextLen)) + { + LogPrint (eLogWarning, "Garlic: Reply ML-KEM ciphertext section AEAD decryption failed"); + return false; + } + MixHash (buf, cipherTextLen + 16); + buf += cipherTextLen + 16; + len -= cipherTextLen + 16; + // decaps + m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret); + MixKey (sharedSecret); + } +#endif + GetOwner ()->Decrypt (bepk, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bepk) MixKey (sharedSecret); - - uint8_t nonce[12]; - CreateNonce (0, nonce); + // calculate hash for zero length - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anything */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only + if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only { LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); return false; @@ -670,6 +833,7 @@ namespace garlic } i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload + uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0 if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); @@ -679,7 +843,8 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; - //m_EphemeralKeys = nullptr; // TODO: delete after a while + // don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come + // done in CleanupReceiveNSRKeys called from NSR tagset destructor m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } @@ -694,6 +859,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { + auto owner = GetOwner (); + if (!owner) return false; uint8_t nonce[12]; auto index = m_SendTagset->GetNextIndex (); CreateNonce (index, nonce); // tag's index @@ -701,8 +868,7 @@ namespace garlic if (!tag) { LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); - if (GetOwner ()) - GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + owner->RemoveECIESx25519Session (m_RemoteStaticKey); return false; } memcpy (out, &tag, 8); @@ -710,7 +876,7 @@ namespace garlic // ciphertext = ENCRYPT(k, n, payload, ad) uint8_t key[32]; m_SendTagset->GetSymmKey (index, key); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt + if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -729,33 +895,35 @@ namespace garlic uint8_t * payload = buf + 8; uint8_t key[32]; receiveTagset->GetSymmKey (index, key); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt + auto owner = GetOwner (); + if (!owner) return true; // drop message + + if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16)) { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } HandlePayload (payload, len - 16, receiveTagset, index); - if (GetOwner ()) + + int moreTags = 0; + if (owner->GetNumRatchetInboundTags () > 0) // override in settings? { - int moreTags = 0; - if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings? - { - if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) - moreTags = GetOwner ()->GetNumRatchetInboundTags (); - index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind - } - else - { - moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 - if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; - moreTags -= (receiveTagset->GetNextIndex () - index); - index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind - } - if (moreTags > 0) - GenerateMoreReceiveTags (receiveTagset, moreTags); - if (index > 0) - receiveTagset->SetTrimBehind (index); + if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2) + moreTags = owner->GetNumRatchetInboundTags (); + index -= owner->GetNumRatchetInboundTags (); // trim behind } + else + { + moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset + (ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2 + if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; + moreTags -= (receiveTagset->GetNextIndex () - index); + index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind + } + if (moreTags > 0) + GenerateMoreReceiveTags (receiveTagset, moreTags); + if (index > 0) + receiveTagset->SetTrimBehind (index); return true; } @@ -769,10 +937,14 @@ namespace garlic m_State = eSessionStateEstablished; m_NSRSendTagset = nullptr; m_EphemeralKeys = nullptr; -#if (__cplusplus >= 201703L) // C++ 17 or higher +#if OPENSSL_PQ + m_PQKeys = nullptr; + m_NSREncodedPQKey = nullptr; +#endif [[fallthrough]]; -#endif case eSessionStateEstablished: + if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ()) + m_SendReverseKey = false; // tag received on new tagset if (receiveTagset->IsNS ()) { // our of sequence NSR @@ -799,7 +971,12 @@ namespace garlic if (!payload) return nullptr; size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); if (!len) return nullptr; +#if OPENSSL_PQ + auto m = NewI2NPMessage (len + (m_State == eSessionStateEstablished ? 28 : + i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 116)); +#else auto m = NewI2NPMessage (len + 100); // 96 + 4 +#endif m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length @@ -814,16 +991,28 @@ namespace garlic if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 96; +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + len += i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 16; +#endif break; case eSessionStateNewSessionReceived: if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16; +#endif break; case eSessionStateNewSessionReplySent: if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; +#if OPENSSL_PQ + if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD) + len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16; +#endif break; case eSessionStateOneTime: if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) @@ -850,13 +1039,14 @@ namespace garlic { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 0; + bool sendAckRequest = false; if (first) payloadLen += 7;// datatime if (msg) { payloadLen += msg->GetPayloadLength () + 13; if (m_Destination) payloadLen += 32; } - if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT) { // resubmit non-confirmed LeaseSet SetLeaseSetUpdateStatus (eLeaseSetUpdated); @@ -868,13 +1058,28 @@ namespace garlic payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; if (!first) { - // ack request + // ack request for LeaseSet + m_AckRequestMsgID = m_SendTagset->GetMsgID (); + sendAckRequest = true; + // update LeaseSet status SetLeaseSetUpdateStatus (eLeaseSetSubmitted); - SetLeaseSetUpdateMsgID (m_SendTagset->GetNextIndex ()); + SetLeaseSetUpdateMsgID (m_AckRequestMsgID); SetLeaseSetSubmissionTime (ts); - payloadLen += 4; } } + if (!sendAckRequest && !first && + ((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request + (m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again + { + // not LeaseSet + m_AckRequestMsgID = m_SendTagset->GetMsgID (); + if (m_AckRequestMsgID) + { + m_AckRequestNumAttempts++; + sendAckRequest = true; + } + } + if (sendAckRequest) payloadLen += 4; if (m_AckRequests.size () > 0) payloadLen += m_AckRequests.size ()*4 + 3; if (m_SendReverseKey) @@ -926,16 +1131,15 @@ namespace garlic } // LeaseSet if (leaseSet) - { offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); - if (!first) - { - // ack request - payload[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (payload + offset, 1); offset += 2; - payload[offset] = 0; offset++; // flags - } - } + // ack request + if (sendAckRequest) + { + payload[offset] = eECIESx25519BlkAckRequest; offset++; + htobe16buf (payload + offset, 1); offset += 2; + payload[offset] = 0; offset++; // flags + m_LastAckRequestSendTime = ts; + } // msg if (msg) offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); @@ -970,7 +1174,6 @@ namespace garlic memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } - m_SendReverseKey = false; } if (m_SendForwardKey) { @@ -1066,6 +1269,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) { CleanupUnconfirmedLeaseSet (ts); + if (!m_Destination && ts > m_LastActivityTimestamp + ECIESX25519_SESSION_CREATE_TIMEOUT) return true; // m_LastActivityTimestamp is NS receive time + if (m_State != eSessionStateEstablished && m_SessionCreatedTimestamp && ts > m_SessionCreatedTimestamp + ECIESX25519_SESSION_ESTABLISH_TIMEOUT) return true; return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds } @@ -1147,9 +1352,9 @@ namespace garlic return len; } - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { - auto m = NewI2NPMessage (); + auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128); m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length size_t offset = 0; @@ -1167,15 +1372,21 @@ namespace garlic htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); + if (msg->onDrop) + { + // move onDrop to the wrapping I2NP messages + m->onDrop = msg->onDrop; + msg->onDrop = nullptr; + } return m; } - std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) { // Noise_N, we are Alice, routerPublicKey is Bob's i2p::crypto::NoiseSymmetricState noiseState; i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); - auto m = NewI2NPMessage (); + auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128); m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length size_t offset = 0; @@ -1204,6 +1415,12 @@ namespace garlic htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); + if (msg->onDrop) + { + // move onDrop to the wrapping I2NP messages + m->onDrop = msg->onDrop; + msg->onDrop = nullptr; + } return m; } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 301f597a..fd9cc45d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,10 +14,12 @@ #include #include #include +#include #include #include #include "Identity.h" #include "Crypto.h" +#include "PostQuantum.h" #include "Garlic.h" #include "Tag.h" @@ -30,10 +32,14 @@ namespace garlic const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds - const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 + const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received + const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds + const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds + const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds + const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3; const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 800; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ @@ -57,6 +63,8 @@ namespace garlic int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; + uint32_t GetMsgID () const { return (m_TagSetID << 16) + m_NextIndex; }; // (tagsetid << 16) + N + private: i2p::data::Tag<64> m_SessionTagKeyData; @@ -73,8 +81,8 @@ namespace garlic { public: - ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false): - m_Session (session), m_IsNS (isNS) {}; + ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false); + ~ReceiveRatchetTagSet () override; bool IsNS () const { return m_IsNS; }; std::shared_ptr GetSession () { return m_Session; }; @@ -86,7 +94,8 @@ namespace garlic virtual bool IsIndexExpired (int index) const; virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); - + virtual bool IsSessionTerminated () const; + private: int m_TrimBehindIndex = 0; @@ -101,9 +110,10 @@ namespace garlic SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key); - bool IsIndexExpired (int index) const { return false; }; - bool HandleNextMessage (uint8_t * buf, size_t len, int index); - + bool IsIndexExpired (int index) const override { return false; }; + bool HandleNextMessage (uint8_t * buf, size_t len, int index) override; + bool IsSessionTerminated () const override { return false; } + private: GarlicDestination * m_Destination; @@ -147,6 +157,7 @@ namespace garlic std::shared_ptr key; uint8_t remote[32]; // last remote public key bool newKey = true; + int GetReceiveTagSetID () const { return newKey ? (2*keyID + 1) : 2*keyID; } }; public: @@ -155,34 +166,41 @@ namespace garlic ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg) override; std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } - void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } - + i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; } + void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) + { + m_RemoteStaticKeyType = keyType; + memcpy (m_RemoteStaticKey, key, 32); + } void Terminate () { m_IsTerminated = true; } - void SetDestination (const i2p::data::IdentHash& dest) // TODO: + void SetDestination (const i2p::data::IdentHash& dest) { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); } - bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } - - bool IsRatchets () const { return true; }; - bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; }; - bool IsTerminated () const { return m_IsTerminated; } - uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; - + void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side + + bool IsRatchets () const override { return true; }; + bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; + bool IsTerminated () const override { return m_IsTerminated; } + uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; }; + void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; }; + bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP + protected: i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; }; void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; }; void CreateNonce (uint64_t seqn, uint8_t * nonce); void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); - + bool MessageConfirmed (uint32_t msgID) override; + private: bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes @@ -207,20 +225,30 @@ namespace garlic private: + i2p::data::CryptoKeyType m_RemoteStaticKeyType; uint8_t m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; +#if OPENSSL_PQ + std::unique_ptr m_PQKeys; + std::unique_ptr > m_NSREncodedPQKey; +#endif SessionState m_State = eSessionStateNew; uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) m_LastSentTimestamp = 0; // in milliseconds std::shared_ptr m_SendTagset, m_NSRSendTagset; - std::unique_ptr m_Destination;// TODO: might not need it - std::list > m_AckRequests; // (tagsetid, index) + std::unique_ptr m_Destination;// must be set for NS if outgoing and NSR if incoming + std::list > m_AckRequests; // incoming (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; uint8_t m_PaddingSizes[32], m_NextPaddingSize; + uint64_t m_LastAckRequestSendTime = 0; // milliseconds + uint32_t m_AckRequestMsgID = 0; + int m_AckRequestNumAttempts = 0; + int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds + public: // for HTTP only @@ -245,8 +273,8 @@ namespace garlic i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; }; - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag); - std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey); + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey); } } diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 0c6eb4f7..47edb755 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -413,7 +413,7 @@ namespace crypto BIGNUM * y = BN_new (); BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); BIGNUM * x = RecoverX (y, ctx); - if (BN_is_bit_set (x, 0) != isHighestBitSet) + if ((bool)BN_is_bit_set (x, 0) != isHighestBitSet) BN_sub (x, q, x); // x = q - x BIGNUM * z = BN_new (), * t = BN_new (); BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t @@ -457,86 +457,6 @@ namespace crypto } } -#if !OPENSSL_X25519 - BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const - { - BN_CTX_start (ctx); - auto x1 = BN_CTX_get (ctx); BN_copy (x1, u); - auto x2 = BN_CTX_get (ctx); BN_one (x2); - auto z2 = BN_CTX_get (ctx); BN_zero (z2); - auto x3 = BN_CTX_get (ctx); BN_copy (x3, u); - auto z3 = BN_CTX_get (ctx); BN_one (z3); - auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666); - auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx); - unsigned int swap = 0; - auto bits = BN_num_bits (k); - while(bits) - { - --bits; - auto k_t = BN_is_bit_set(k, bits) ? 1 : 0; - swap ^= k_t; - if (swap) - { - std::swap (x2, x3); - std::swap (z2, z3); - } - swap = k_t; - BN_mod_sub(tmp0, x3, z3, q, ctx); - BN_mod_sub(tmp1, x2, z2, q, ctx); - BN_mod_add(x2, x2, z2, q, ctx); - BN_mod_add(z2, x3, z3, q, ctx); - BN_mod_mul(z3, tmp0, x2, q, ctx); - BN_mod_mul(z2, z2, tmp1, q, ctx); - BN_mod_sqr(tmp0, tmp1, q, ctx); - BN_mod_sqr(tmp1, x2, q, ctx); - BN_mod_add(x3, z3, z2, q, ctx); - BN_mod_sub(z2, z3, z2, q, ctx); - BN_mod_mul(x2, tmp1, tmp0, q, ctx); - BN_mod_sub(tmp1, tmp1, tmp0, q, ctx); - BN_mod_sqr(z2, z2, q, ctx); - BN_mod_mul(z3, tmp1, c121666, q, ctx); - BN_mod_sqr(x3, x3, q, ctx); - BN_mod_add(tmp0, tmp0, z3, q, ctx); - BN_mod_mul(z3, x1, z2, q, ctx); - BN_mod_mul(z2, tmp1, tmp0, q, ctx); - } - if (swap) - { - std::swap (x2, x3); - std::swap (z2, z3); - } - BN_mod_inverse (z2, z2, q, ctx); - BIGNUM * res = BN_new (); // not from ctx - BN_mod_mul(res, x2, z2, q, ctx); - BN_CTX_end (ctx); - return res; - } - - void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const - { - BIGNUM * p1 = DecodeBN<32> (p); - uint8_t k[32]; - memcpy (k, e, 32); - k[0] &= 248; k[31] &= 127; k[31] |= 64; - BIGNUM * n = DecodeBN<32> (k); - BIGNUM * q1 = ScalarMul (p1, n, ctx); - EncodeBN (q1, buf, 32); - BN_free (p1); BN_free (n); BN_free (q1); - } - - void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const - { - BIGNUM *p1 = BN_new (); BN_set_word (p1, 9); - uint8_t k[32]; - memcpy (k, e, 32); - k[0] &= 248; k[31] &= 127; k[31] |= 64; - BIGNUM * n = DecodeBN<32> (k); - BIGNUM * q1 = ScalarMul (p1, n, ctx); - EncodeBN (q1, buf, 32); - BN_free (p1); BN_free (n); BN_free (q1); - } -#endif - void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded) { BN_CTX * ctx = BN_CTX_new (); diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 470d802f..9c0ad801 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -84,10 +84,7 @@ namespace crypto EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const; EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const; void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const; -#if !OPENSSL_X25519 - void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519 - void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; -#endif + void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 @@ -115,11 +112,6 @@ namespace crypto BIGNUM * DecodeBN (const uint8_t * buf) const; void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const; -#if !OPENSSL_X25519 - // for x25519 - BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const; -#endif - private: BIGNUM * q, * l, * d, * I; diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 7334550f..3f5fc6b9 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,7 +7,17 @@ */ #include -#include + +#if defined(MAC_OSX) +#if !STD_FILESYSTEM +#include +#endif +#include +#endif + +#if defined(__HAIKU__) +#include +#endif #ifdef _WIN32 #include @@ -20,6 +30,14 @@ #include "Log.h" #include "Garlic.h" +#if STD_FILESYSTEM +#include +namespace fs_lib = std::filesystem; +#else +#include +namespace fs_lib = boost::filesystem; +#endif + namespace i2p { namespace fs { std::string appName = "i2pd"; @@ -49,11 +67,17 @@ namespace fs { const std::string GetUTF8DataDir () { #ifdef _WIN32 - boost::filesystem::wpath path (dataDir); - auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16() ) ); // convert path to UTF-8 - auto dataDirUTF8 = path.string(); - boost::filesystem::path::imbue(loc); // Return locale settings back - return dataDirUTF8; + int size = MultiByteToWideChar(CP_ACP, 0, + dataDir.c_str(), dataDir.size(), nullptr, 0); + std::wstring utf16Str(size, L'\0'); + MultiByteToWideChar(CP_ACP, 0, + dataDir.c_str(), dataDir.size(), &utf16Str[0], size); + int utf8Size = WideCharToMultiByte(CP_UTF8, 0, + utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr); + std::string utf8Str(utf8Size, '\0'); + WideCharToMultiByte(CP_UTF8, 0, + utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr); + return utf8Str; #else return dataDir; // linux, osx, android uses UTF-8 by default #endif @@ -82,7 +106,11 @@ namespace fs { } else { - dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName; +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + dataDir = fs_lib::path(commonAppData).string() + "\\" + appName; +#else + dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName; +#endif } #else dataDir = "/var/lib/" + appName; @@ -107,10 +135,14 @@ namespace fs { } else { - auto execPath = boost::filesystem::wpath(localAppData).parent_path(); +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + auto execPath = fs_lib::path(localAppData).parent_path(); +#else + auto execPath = fs_lib::wpath(localAppData).parent_path(); +#endif // if config file exists in .exe's folder use it - if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string { dataDir = execPath.string (); } else // otherwise %appdata% @@ -126,7 +158,11 @@ namespace fs { } else { - dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; +#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) + dataDir = fs_lib::path(localAppData).string() + "\\" + appName; +#else + dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName; +#endif } } } @@ -136,11 +172,18 @@ namespace fs { dataDir = (home != NULL && strlen(home) > 0) ? home : ""; dataDir += "/Library/Application Support/" + appName; return; +#elif defined(__HAIKU__) + char home[PATH_MAX]; // /boot/home/config/settings + if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK) + dataDir = std::string(home) + "/" + appName; + else + dataDir = "/tmp/" + appName; + return; #else /* other unix */ #if defined(ANDROID) const char * ext = getenv("EXTERNAL_STORAGE"); if (!ext) ext = "/sdcard"; - if (boost::filesystem::exists(ext)) + if (fs_lib::exists(ext)) { dataDir = std::string (ext) + "/" + appName; return; @@ -173,16 +216,16 @@ namespace fs { } bool Init() { - if (!boost::filesystem::exists(dataDir)) - boost::filesystem::create_directory(dataDir); + if (!fs_lib::exists(dataDir)) + fs_lib::create_directory(dataDir); std::string destinations = DataDirPath("destinations"); - if (!boost::filesystem::exists(destinations)) - boost::filesystem::create_directory(destinations); + if (!fs_lib::exists(destinations)) + fs_lib::create_directory(destinations); std::string tags = DataDirPath("tags"); - if (!boost::filesystem::exists(tags)) - boost::filesystem::create_directory(tags); + if (!fs_lib::exists(tags)) + fs_lib::create_directory(tags); else i2p::garlic::CleanUpTagsFiles (); @@ -190,13 +233,13 @@ namespace fs { } bool ReadDir(const std::string & path, std::vector & files) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return false; - boost::filesystem::directory_iterator it(path); - boost::filesystem::directory_iterator end; + fs_lib::directory_iterator it(path); + fs_lib::directory_iterator end; for ( ; it != end; it++) { - if (!boost::filesystem::is_regular_file(it->status())) + if (!fs_lib::is_regular_file(it->status())) continue; files.push_back(it->path().string()); } @@ -205,29 +248,42 @@ namespace fs { } bool Exists(const std::string & path) { - return boost::filesystem::exists(path); + return fs_lib::exists(path); } uint32_t GetLastUpdateTime (const std::string & path) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return 0; +#if STD_FILESYSTEM + std::error_code ec; + auto t = std::filesystem::last_write_time (path, ec); + if (ec) return 0; +/*#if __cplusplus >= 202002L // C++ 20 or higher + const auto sctp = std::chrono::clock_cast(t); +#else */ // TODO: wait until implemented + const auto sctp = std::chrono::time_point_cast( + t - decltype(t)::clock::now() + std::chrono::system_clock::now()); +/*#endif */ + return std::chrono::system_clock::to_time_t(sctp); +#else boost::system::error_code ec; auto t = boost::filesystem::last_write_time (path, ec); return ec ? 0 : t; +#endif } bool Remove(const std::string & path) { - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return false; - return boost::filesystem::remove(path); + return fs_lib::remove(path); } bool CreateDirectory (const std::string& path) { - if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path))) + if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path))) return true; - return boost::filesystem::create_directory(path); + return fs_lib::create_directory(path); } void HashedStorage::SetPlace(const std::string &path) { @@ -235,16 +291,30 @@ namespace fs { } bool HashedStorage::Init(const char * chars, size_t count) { - if (!boost::filesystem::exists(root)) { - boost::filesystem::create_directories(root); + if (!fs_lib::exists(root)) { + fs_lib::create_directories(root); } for (size_t i = 0; i < count; i++) { auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; - if (boost::filesystem::exists(p)) + if (fs_lib::exists(p)) continue; - if (boost::filesystem::create_directory(p)) +#if TARGET_OS_SIMULATOR + // ios simulator fs says it is case sensitive, but it is not + boost::system::error_code ec; + if (fs_lib::create_directory(p, ec)) + continue; + switch (ec.value()) { + case boost::system::errc::file_exists: + case boost::system::errc::success: + continue; + default: + throw boost::system::system_error( ec, __func__ ); + } +#else + if (fs_lib::create_directory(p)) continue; /* ^ throws exception on failure */ +#endif return false; } return true; @@ -265,9 +335,9 @@ namespace fs { void HashedStorage::Remove(const std::string & ident) { std::string path = Path(ident); - if (!boost::filesystem::exists(path)) + if (!fs_lib::exists(path)) return; - boost::filesystem::remove(path); + fs_lib::remove(path); } void HashedStorage::Traverse(std::vector & files) { @@ -278,12 +348,12 @@ namespace fs { void HashedStorage::Iterate(FilenameVisitor v) { - boost::filesystem::path p(root); - boost::filesystem::recursive_directory_iterator it(p); - boost::filesystem::recursive_directory_iterator end; + fs_lib::path p(root); + fs_lib::recursive_directory_iterator it(p); + fs_lib::recursive_directory_iterator end; for ( ; it != end; it++) { - if (!boost::filesystem::is_regular_file( it->status() )) + if (!fs_lib::is_regular_file( it->status() )) continue; const std::string & t = it->path().string(); v(t); diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 7911c6a0..7af8f494 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,16 @@ #include #include +#ifndef STD_FILESYSTEM +# if (_WIN32 && __GNUG__) // MinGW GCC somehow incorrectly converts paths +# define STD_FILESYSTEM 0 +# elif (!TARGET_OS_SIMULATOR && __has_include()) // supports std::filesystem +# define STD_FILESYSTEM 1 +# else +# define STD_FILESYSTEM 0 +# endif +#endif + namespace i2p { namespace fs { extern std::string dirSep; diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index 9a0700d0..300a50ab 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,7 +7,6 @@ */ #include -#include #include #include "Crypto.h" #include "FS.h" @@ -25,6 +24,8 @@ namespace data Families::~Families () { + for (auto it : m_SigningKeys) + if (it.second.first) EVP_PKEY_free (it.second.first); } void Families::LoadCertificate (const std::string& filename) @@ -47,48 +48,16 @@ namespace data cn += 3; char * family = strstr (cn, ".family"); if (family) family[0] = 0; - } - auto pkey = X509_get_pubkey (cert); - int keyType = EVP_PKEY_base_id (pkey); - switch (keyType) - { - case EVP_PKEY_DSA: - // TODO: - break; - case EVP_PKEY_EC: - { - EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); - if (ecKey) + auto pkey = X509_get_pubkey (cert); + if (pkey) + { + if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second) { - auto group = EC_KEY_get0_group (ecKey); - if (group) - { - int curve = EC_GROUP_get_curve_name (group); - if (curve == NID_X9_62_prime256v1) - { - uint8_t signingKey[64]; - BIGNUM * x = BN_new(), * y = BN_new(); - EC_POINT_get_affine_coordinates_GFp (group, - EC_KEY_get0_public_key (ecKey), x, y, NULL); - i2p::crypto::bn2buf (x, signingKey, 32); - i2p::crypto::bn2buf (y, signingKey + 32, 32); - BN_free (x); BN_free (y); - verifier = std::make_shared(); - verifier->SetPublicKey (signingKey); - } - else - LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); - } - EC_KEY_free (ecKey); - } - break; - } - default: - LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); + EVP_PKEY_free (pkey); + LogPrint (eLogError, "Family: Duplicated family name ", cn); + } + } } - EVP_PKEY_free (pkey); - if (verifier && cn) - m_SigningKeys.emplace (cn, std::make_pair(verifier, m_SigningKeys.size () + 1)); } SSL_free (ssl); } @@ -121,23 +90,31 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key) const + std::string_view signature, const char * key) const { uint8_t buf[100], signatureBuf[64]; - size_t len = family.length (), signatureLen = strlen (signature); + size_t len = family.length (); if (len + 32 > 100) { LogPrint (eLogError, "Family: ", family, " is too long"); return false; } - - memcpy (buf, family.c_str (), len); - memcpy (buf + len, (const uint8_t *)ident, 32); - len += 32; - Base64ToByteStream (signature, signatureLen, signatureBuf, 64); auto it = m_SigningKeys.find (family); - if (it != m_SigningKeys.end ()) - return it->second.first->Verify (buf, len, signatureBuf); + if (it != m_SigningKeys.end () && it->second.first) + { + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + auto signatureBufLen = Base64ToByteStream (signature, signatureBuf, 64); + if (signatureBufLen) + { + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, it->second.first); + auto ret = EVP_DigestVerify (ctx, signatureBuf, signatureBufLen, buf, len); + EVP_MD_CTX_destroy (ctx); + return ret; + } + } // TODO: process key return true; } @@ -177,12 +154,7 @@ namespace data memcpy (buf + len, (const uint8_t *)ident, 32); len += 32; signer.Sign (buf, len, signature); - len = Base64EncodingBufferSize (64); - char * b64 = new char[len+1]; - len = ByteStreamToBase64 (signature, 64, b64, len); - b64[len] = 0; - sig = b64; - delete[] b64; + sig = ByteStreamToBase64 (signature, 64); } else LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); diff --git a/libi2pd/Family.h b/libi2pd/Family.h index b19ea142..fcf61082 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,8 +11,9 @@ #include #include +#include #include -#include "Signature.h" +#include #include "Identity.h" namespace i2p @@ -28,7 +29,7 @@ namespace data ~Families (); void LoadCertificates (); bool VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key = nullptr) const; + std::string_view signature, const char * key = nullptr) const; FamilyID GetFamilyID (const std::string& family) const; private: @@ -37,7 +38,7 @@ namespace data private: - std::map, FamilyID> > m_SigningKeys; // family -> (verifier, id) + std::map > m_SigningKeys; // family -> (verification pkey, id) }; std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 9daea1f0..8c8602e8 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,22 +45,17 @@ namespace garlic { if (!m_SharedRoutingPath) return nullptr; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED || - !m_SharedRoutingPath->outboundTunnel->IsEstablished () || + if (!m_SharedRoutingPath->outboundTunnel->IsEstablished () || ts*1000LL > m_SharedRoutingPath->remoteLease->endDate || ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT) m_SharedRoutingPath = nullptr; - if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++; return m_SharedRoutingPath; } void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr path) { if (path && path->outboundTunnel && path->remoteLease) - { path->updateTime = i2p::util::GetSecondsSinceEpoch (); - path->numTimesUsed = 0; - } else path = nullptr; m_SharedRoutingPath = path; @@ -80,7 +75,7 @@ namespace garlic void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts) { - if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASESET_CONFIRMATION_TIMEOUT) { if (GetOwner ()) GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); @@ -165,7 +160,7 @@ namespace garlic uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); m_Destination->Encrypt ((uint8_t *)&elGamal, buf); - m_Encryption.SetIV (iv); + m_IV = iv; buf += 514; len += 514; } @@ -175,7 +170,7 @@ namespace garlic memcpy (buf, tag, 32); uint8_t iv[32]; // IV is first 16 bytes SHA256(tag, 32, iv); - m_Encryption.SetIV (iv); + m_IV = iv; buf += 32; len += 32; } @@ -215,7 +210,7 @@ namespace garlic size_t rem = blockSize % 16; if (rem) blockSize += (16-rem); //padding - m_Encryption.Encrypt(buf, blockSize, buf); + m_Encryption.Encrypt(buf, blockSize, m_IV, buf); return blockSize; } @@ -232,7 +227,7 @@ namespace garlic if (GetOwner ()) { // resubmit non-confirmed LeaseSet - if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT) { SetLeaseSetUpdateStatus (eLeaseSetUpdated); SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed @@ -431,7 +426,8 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0), + m_NumRatchetInboundTags (0) // 0 means standard { } @@ -502,7 +498,8 @@ namespace garlic buf += 4; // length bool found = false; - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + bool supportsRatchets = SupportsRatchets (); + if (supportsRatchets) // try ECIESx25519 tag found = HandleECIESx25519TagMessage (buf, length); if (!found) @@ -518,8 +515,7 @@ namespace garlic { uint8_t iv[32]; // IV is first 16 bytes SHA256(buf, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt (buf + 32, length - 32, buf + 32); + decryption->Decrypt (buf + 32, length - 32, iv, buf + 32); HandleAESBlock (buf + 32, length - 32, decryption, msg->from); found = true; } @@ -537,43 +533,23 @@ namespace garlic auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt(buf + 514, length - 514, buf + 514); + decryption->Decrypt(buf + 514, length - 514, iv, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } - else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + else if (supportsRatchets) { // otherwise ECIESx25519 - auto session = std::make_shared (this, false); // incoming - if (!session->HandleNextMessage (buf, length, nullptr, 0)) - { - // try to generate more tags for last tagset - if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) - { - uint64_t missingTag; memcpy (&missingTag, buf, 8); - auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); - LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags"); - for (int i = 0; i < maxTags; i++) - { - auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); - if (!nextTag) - { - LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); - break; - } - if (nextTag == missingTag) - { - LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); - if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) - found = true; - break; - } - } - if (!found) m_LastTagset = nullptr; - } - if (!found) + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > m_LastIncomingSessionTimestamp + INCOMING_SESSIONS_MINIMAL_INTERVAL) + { + auto session = std::make_shared (this, false); // incoming + if (session->HandleNextMessage (buf, length, nullptr, 0)) + m_LastIncomingSessionTimestamp = ts; + else LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); } + else + LogPrint (eLogWarning, "Garlic: Incoming sessions come too often"); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -588,9 +564,7 @@ namespace garlic auto it = m_ECIESx25519Tags.find (tag); if (it != m_ECIESx25519Tags.end ()) { - if (it->second.tagset->HandleNextMessage (buf, len, it->second.index)) - m_LastTagset = it->second.tagset; - else + if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index)) LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it); return true; @@ -709,7 +683,7 @@ namespace garlic else LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); if (tunnel) // we have sent it through an outbound tunnel - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); + tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, msg); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -771,32 +745,38 @@ namespace garlic } std::shared_ptr GarlicDestination::GetRoutingSession ( - std::shared_ptr destination, bool attachLeaseSet) + std::shared_ptr destination, bool attachLeaseSet, + bool requestNewIfNotFound) { - if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && - SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { - ECIESX25519AEADRatchetSessionPtr session; - uint8_t staticKey[32]; - destination->Encrypt (nullptr, staticKey); // we are supposed to get static key - auto it = m_ECIESx25519Sessions.find (staticKey); - if (it != m_ECIESx25519Sessions.end ()) - { - session = it->second; - if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) + if (SupportsEncryptionType (destination->GetEncryptionType ())) + { + ECIESX25519AEADRatchetSessionPtr session; + uint8_t staticKey[32]; + destination->Encrypt (nullptr, staticKey); // we are supposed to get static key + auto it = m_ECIESx25519Sessions.find (staticKey); + if (it != m_ECIESx25519Sessions.end ()) { - LogPrint (eLogDebug, "Garlic: Session restarted"); - session = nullptr; + session = it->second; + if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) + { + LogPrint (eLogDebug, "Garlic: Session restarted"); + requestNewIfNotFound = true; // it's not a new session + session = nullptr; + } } + if (!session && requestNewIfNotFound) + { + session = std::make_shared (this, true); + session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey); + } + if (session && destination->IsDestination ()) + session->SetDestination (destination->GetIdentHash ()); // NS or NSR + return session; } - if (!session) - { - session = std::make_shared (this, true); - session->SetRemoteStaticKey (staticKey); - } - if (destination->IsDestination ()) - session->SetDestination (destination->GetIdentHash ()); // TODO: remove - return session; + else + LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ()); } else { @@ -887,8 +867,7 @@ namespace garlic } else { - auto session = it->second.tagset->GetSession (); - if (!session || session->IsTerminated()) + if (it->second.tagset->IsSessionTerminated ()) { it = m_ECIESx25519Tags.erase (it); numExpiredTags++; @@ -899,8 +878,6 @@ namespace garlic } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); - if (m_LastTagset && m_LastTagset->IsExpired (ts)) - m_LastTagset = nullptr; } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -934,7 +911,7 @@ namespace garlic } } - void GarlicDestination::SetLeaseSetUpdated () + void GarlicDestination::SetLeaseSetUpdated (bool post) { { std::unique_lock l(m_SessionsMutex); @@ -1027,7 +1004,8 @@ namespace garlic i2p::fs::Remove (it); } - void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) + void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, + ECIESX25519AEADRatchetSession * from) { const uint8_t * buf1 = buf; uint8_t flag = buf[0]; buf++; // flag @@ -1037,9 +1015,7 @@ namespace garlic case eGarlicDeliveryTypeDestination: LogPrint (eLogDebug, "Garlic: Type destination"); buf += 32; // TODO: check destination -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here case eGarlicDeliveryTypeLocal: { @@ -1049,7 +1025,7 @@ namespace garlic buf += 4; // expiration ptrdiff_t offset = buf - buf1; if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); + HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from); else LogPrint (eLogError, "Garlic: Clove is too long"); break; @@ -1075,7 +1051,7 @@ namespace garlic { auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); + tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -1133,5 +1109,17 @@ namespace garlic m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; return m_PayloadBuffer; } + + bool GarlicDestination::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + bool GarlicDestination::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index b926abda..25106c45 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -50,9 +50,9 @@ namespace garlic const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds - const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds - const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds - const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used + const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds + const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds + const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds struct SessionTag: public i2p::data::Tag<32> { @@ -89,7 +89,6 @@ namespace garlic std::shared_ptr remoteLease; int rtt; // RTT uint32_t updateTime; // seconds since epoch - int numTimesUsed; }; class GarlicDestination; @@ -111,13 +110,14 @@ namespace garlic GarlicRoutingSession (); virtual ~GarlicRoutingSession (); virtual std::shared_ptr WrapSingleMessage (std::shared_ptr msg) = 0; - virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession + virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession and ECIESX25519AEADRatchetSession virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; virtual bool IsReadyToSend () const { return true; }; virtual bool IsTerminated () const { return !GetOwner (); }; virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only - + virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -206,6 +206,7 @@ namespace garlic std::map > m_UnconfirmedTagsMsgs; // msgID->tags i2p::crypto::CBCEncryption m_Encryption; + i2p::data::Tag<16> m_IV; public: @@ -221,7 +222,7 @@ namespace garlic struct ECIESX25519AEADRatchetIndexTagset { int index; - ReceiveRatchetTagSetPtr tagset; + ReceiveRatchetTagSetPtr tagset; // null if used }; class GarlicDestination: public i2p::data::LocalDestination @@ -236,11 +237,17 @@ namespace garlic int GetNumTags () const { return m_NumTags; }; void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; }; int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; }; - std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); + std::shared_ptr GetRoutingSession (std::shared_ptr destination, + bool attachLeaseSet, bool requestNewIfNotFound = true); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessageForRouter (std::shared_ptr router, std::shared_ptr msg); + + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag @@ -250,30 +257,36 @@ namespace garlic uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); - void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); + void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, ECIESX25519AEADRatchetSession * from); uint8_t * GetPayloadBuffer (); virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); - virtual void SetLeaseSetUpdated (); + virtual void SetLeaseSetUpdated (bool post = false); virtual std::shared_ptr GetLeaseSet () = 0; // TODO virtual std::shared_ptr GetTunnelPool () const = 0; + virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const + { + return GetIdentity ()->GetCryptoKeyType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? GetIdentity ()->GetCryptoKeyType () : 0; + } protected: void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only - virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0; + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0; void HandleGarlicMessage (std::shared_ptr msg); void HandleDeliveryStatusMessage (uint32_t msgID); void SaveTags (); void LoadTags (); - + private: + bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; } void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, std::shared_ptr from); void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr from); @@ -286,15 +299,18 @@ namespace garlic std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet + uint64_t m_LastIncomingSessionTimestamp; // in milliseconds // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session - ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session - + // encryption + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + public: // for HTTP only diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 07c6a96e..4be8684c 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.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 * @@ -57,7 +57,8 @@ namespace data if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) return outLen - m_Inflator.avail_out; // else - LogPrint (eLogError, "Gzip: Inflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Inflate error ", err); return 0; } } @@ -128,7 +129,8 @@ namespace data return outLen - m_Deflator.avail_out; } // else - LogPrint (eLogError, "Gzip: Deflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } @@ -137,7 +139,7 @@ namespace data if (m_IsDirty) deflateReset (&m_Deflator); m_IsDirty = true; size_t offset = 0; - int err; + int err = 0; for (const auto& it: bufs) { m_Deflator.next_in = const_cast(it.first); @@ -158,7 +160,8 @@ namespace data offset = outLen - m_Deflator.avail_out; } // else - LogPrint (eLogError, "Gzip: Deflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index e994b9b3..3cd5c193 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #include #include #include +#include #include "util.h" #include "Base.h" #include "HTTP.h" @@ -18,58 +19,68 @@ namespace i2p { namespace http { - const std::vector HTTP_METHODS = { + // list of valid HTTP methods + static constexpr std::array HTTP_METHODS = + { "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 }; - const std::vector HTTP_VERSIONS = { + + // list of valid HTTP versions + static constexpr std::array HTTP_VERSIONS = + { "HTTP/1.0", "HTTP/1.1" }; - const std::vector weekdays = { + + static constexpr std::array weekdays = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - const std::vector months = { + + static constexpr std::array months = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - inline bool is_http_version(const std::string & str) { + static inline bool is_http_version(std::string_view str) + { return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); } - inline bool is_http_method(const std::string & str) { + static inline bool is_http_method(std::string_view str) + { return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); } - - void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { - std::size_t count = 0; - std::stringstream ss(line); - std::string token; - while (1) { + + static void strsplit(std::string_view line, std::vector &tokens, char delim, std::size_t limit = 0) + { + size_t count = 0, pos; + while ((pos = line.find (delim)) != line.npos) + { count++; - if (limit > 0 && count >= limit) - delim = '\n'; /* reset delimiter */ - if (!std::getline(ss, token, delim)) - break; - tokens.push_back(token); + if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter + tokens.push_back (line.substr (0, pos)); + line = line.substr (pos + 1); } + if (!line.empty ()) tokens.push_back (line); } - - static std::pair parse_header_line(const std::string& line) + + static std::pair parse_header_line(std::string_view line) { std::size_t pos = 0; std::size_t len = 1; /*: */ std::size_t max = line.length(); if ((pos = line.find(':', pos)) == std::string::npos) - return std::make_pair("", ""); // no ':' found + return std::pair{"", ""}; // no ':' found if (pos + 1 < max) // ':' at the end of header is valid { while ((pos + len) < max && isspace(line.at(pos + len))) len++; if (len == 1) - return std::make_pair("", ""); // no following space, but something else + return std::pair{"", ""}; // no following space, but something else } - return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))}; } void gen_rfc7231_date(std::string & out) { @@ -83,25 +94,31 @@ namespace http out = buf; } - bool URL::parse(const char *str, std::size_t len) { - std::string url(str, len ? len : strlen(str)); - return parse(url); + bool URL::parse(const char *str, std::size_t len) + { + return parse({str, len ? len : strlen(str)}); } - bool URL::parse(const std::string& url) { + bool URL::parse(std::string_view url) + { + if (url.empty ()) return false; std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) { + if(url.at(0) != '/' || pos_p > 0) + { std::size_t pos_s = 0; + /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { schema = url.substr(0, pos_c); pos_p = pos_c + 3; } + /* user[:pass] */ pos_s = url.find('/', pos_p); /* find first slash */ pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ + if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { std::size_t delim = url.find(':', pos_p); if (delim && delim != std::string::npos && delim < pos_c) { @@ -113,28 +130,36 @@ namespace http } pos_p = pos_c + 1; } + /* hostname[:port][/path] */ - if (url[pos_p] == '[') // ipv6 + if (url.at(pos_p) == '[') // ipv6 { auto pos_b = url.find(']', pos_p); if (pos_b == std::string::npos) return false; + ipv6 = true; pos_c = url.find_first_of(":/", pos_b); } else pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { /* only hostname, without post and path */ - host = url.substr(pos_p, std::string::npos); + host = ipv6 ? + url.substr(pos_p + 1, url.length() - 1) : + url.substr(pos_p, std::string::npos); return true; } else if (url.at(pos_c) == ':') { - host = url.substr(pos_p, pos_c - pos_p); + host = ipv6 ? + url.substr(pos_p + 1, pos_c - pos_p - 2) : + url.substr(pos_p, pos_c - pos_p); /* port[/path] */ pos_p = pos_c + 1; pos_c = url.find('/', pos_p); - std::string port_str = (pos_c == std::string::npos) + std::string_view port_str = (pos_c == std::string::npos) ? url.substr(pos_p, std::string::npos) : url.substr(pos_p, pos_c - pos_p); /* stoi throws exception on failure, we don't need it */ + port = 0; for (char c : port_str) { if (c < '0' || c > '9') return false; @@ -146,7 +171,9 @@ namespace http pos_p = pos_c; } else { /* start of path part found */ - host = url.substr(pos_p, pos_c - pos_p); + host = ipv6 ? + url.substr(pos_p + 1, pos_c - pos_p - 2) : + url.substr(pos_p, pos_c - pos_p); pos_p = pos_c; } } @@ -159,6 +186,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); @@ -181,8 +209,9 @@ namespace http return true; } - bool URL::parse_query(std::map & params) { - std::vector tokens; + bool URL::parse_query(std::map & params) + { + std::vector tokens; strsplit(query, tokens, '&'); params.clear(); @@ -210,15 +239,25 @@ namespace http } else if (user != "") { out += user + "@"; } - if (port) { - out += host + ":" + std::to_string(port); + if (ipv6) { + if (port) { + out += "[" + host + "]:" + std::to_string(port); + } else { + out += "[" + host + "]"; + } } else { - out += host; + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } } } out += path; + if (hasquery) // add query even if it was empty + out += "?"; if (query != "") - out += "?" + query; + out += query; if (frag != "") out += "#" + frag; return out; @@ -229,7 +268,7 @@ namespace http return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) { add_header(name, value.c_str(), replace); } @@ -248,12 +287,13 @@ namespace http headers.erase(name); } - int HTTPReq::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); + int HTTPReq::parse(const char *buf, size_t len) + { + return parse({buf, len}); } - int HTTPReq::parse(const std::string& str) { + int HTTPReq::parse(std::string_view str) + { enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; std::size_t eoh = str.find(HTTP_EOH); /* request head size */ std::size_t eol = 0, pos = 0; @@ -262,11 +302,14 @@ namespace http if (eoh == std::string::npos) return 0; /* str not contains complete request */ - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == REQ_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; + while ((eol = str.find(CRLF, pos)) != std::string::npos) + { + if (expect == REQ_LINE) + { + std::string_view line = str.substr(pos, eol - pos); + std::vector tokens; strsplit(line, tokens, ' '); + if (tokens.size() != 3) return -1; if (!is_http_method(tokens[0])) @@ -283,18 +326,18 @@ namespace http } else { - std::string line = str.substr(pos, eol - pos); + std::string_view line = str.substr(pos, eol - pos); auto p = parse_header_line(line); if (p.first.length () > 0) headers.push_back (p); else return -1; } - pos = eol + strlen(CRLF); + pos = eol + CRLF.length(); if (pos >= eoh) break; } - return eoh + strlen(HTTP_EOH); + return eoh + HTTP_EOH.length(); } void HTTPReq::write(std::ostream & o) @@ -338,7 +381,7 @@ namespace http } } - std::string HTTPReq::GetHeader (const std::string& name) const + std::string HTTPReq::GetHeader (std::string_view name) const { for (auto& it : headers) if (it.first == name) @@ -346,6 +389,14 @@ namespace http return ""; } + size_t HTTPReq::GetNumHeaders (std::string_view 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"); @@ -381,12 +432,13 @@ namespace http return length; } - int HTTPRes::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); + int HTTPRes::parse(const char *buf, size_t len) + { + return parse({buf,len}); } - int HTTPRes::parse(const std::string& str) { + int HTTPRes::parse(std::string_view str) + { enum { RES_LINE, HEADER_LINE } expect = RES_LINE; std::size_t eoh = str.find(HTTP_EOH); /* request head size */ std::size_t eol = 0, pos = 0; @@ -394,35 +446,41 @@ namespace http if (eoh == std::string::npos) return 0; /* str not contains complete request */ - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == RES_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; + while ((eol = str.find(CRLF, pos)) != std::string::npos) + { + if (expect == RES_LINE) + { + std::string_view line = str.substr(pos, eol - pos); + std::vector tokens; strsplit(line, tokens, ' ', 3); if (tokens.size() != 3) return -1; if (!is_http_version(tokens[0])) return -1; - code = atoi(tokens[1].c_str()); + auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); + if (res.ec != std::errc()) + return -1; if (code < 100 || code >= 600) return -1; /* all ok */ version = tokens[0]; status = tokens[2]; expect = HEADER_LINE; - } else { - std::string line = str.substr(pos, eol - pos); + } + else + { + std::string_view line = str.substr(pos, eol - pos); auto p = parse_header_line(line); if (p.first.length () > 0) headers.insert (p); else return -1; } - pos = eol + strlen(CRLF); + pos = eol + CRLF.length(); if (pos >= eoh) break; } - return eoh + strlen(HTTP_EOH); + return eoh + HTTP_EOH.length(); } std::string HTTPRes::to_string() { @@ -447,9 +505,11 @@ namespace http return ss.str(); } - const char * HTTPCodeToStatus(int code) { - const char *ptr; - switch (code) { + std::string_view HTTPCodeToStatus(int code) + { + std::string_view ptr; + switch (code) + { case 105: ptr = "Name Not Resolved"; break; /* success */ case 200: ptr = "OK"; break; @@ -476,14 +536,14 @@ namespace http return ptr; } - std::string UrlDecode(const std::string& data, bool allow_null) + std::string UrlDecode(std::string_view data, bool allow_null) { std::string decoded(data); size_t pos = 0; while ((pos = decoded.find('%', pos)) != std::string::npos) { - char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); - if (c == '\0' && !allow_null) + char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16); + if (!c && !allow_null) { pos += 3; continue; diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 9445a01a..c65c1ce4 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,16 +14,15 @@ #include #include #include +#include #include namespace i2p { namespace http { - const char CRLF[] = "\r\n"; /**< HTTP line terminator */ - const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ - extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ + constexpr std::string_view CRLF = "\r\n"; /**< HTTP line terminator */ + constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */ struct URL { @@ -33,17 +32,19 @@ namespace http std::string host; unsigned short int port; std::string path; + bool hasquery; std::string query; std::string frag; + bool ipv6; - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), query(""), frag(""), ipv6(false) {}; /** * @brief Tries to parse url from string * @return true on success, false on invalid url */ bool parse (const char *str, std::size_t len = 0); - bool parse (const std::string& url); + bool parse (std::string_view url); /** * @brief Parse query part of url to key/value map @@ -67,7 +68,7 @@ namespace http { std::map headers; - void add_header(const char *name, std::string & value, bool replace = false); + void add_header(const char *name, const std::string & value, bool replace = false); void add_header(const char *name, const char *value, bool replace = false); void del_header(const char *name); @@ -90,7 +91,7 @@ namespace http * @note Positive return value is a size of header */ int parse(const char *buf, size_t len); - int parse(const std::string& buf); + int parse(std::string_view buf); /** @brief Serialize HTTP request to string */ std::string to_string(); @@ -100,7 +101,9 @@ namespace http void UpdateHeader (const std::string& name, const std::string& value); void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; - std::string GetHeader (const std::string& name) const; + std::string GetHeader (std::string_view name) const; + size_t GetNumHeaders (std::string_view name) const; + size_t GetNumHeaders () const { return headers.size (); }; }; struct HTTPRes : HTTPMsg { @@ -124,7 +127,7 @@ namespace http * @note Positive return value is a size of header */ int parse(const char *buf, size_t len); - int parse(const std::string& buf); + int parse(const std::string_view buf); /** * @brief Serialize HTTP response to string @@ -149,7 +152,7 @@ namespace http * @param code HTTP code [100, 599] * @return Immutable string with status */ - const char * HTTPCodeToStatus(int code); + std::string_view HTTPCodeToStatus(int code); /** * @brief Replaces %-encoded characters in string with their values @@ -157,7 +160,7 @@ namespace http * @param null If set to true - decode also %00 sequence, otherwise - skip * @return Decoded string */ - std::string UrlDecode(const std::string& data, bool null = false); + std::string UrlDecode(std::string_view data, bool null = false); /** * @brief Merge HTTP response content with Transfer-Encoding: chunked diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e19e782d..e97a3596 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,20 +10,15 @@ #include #include "Base.h" #include "Log.h" -#include "Crypto.h" #include "I2PEndian.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.hpp" #include "Tunnel.h" -#include "Transports.h" -#include "Garlic.h" -#include "ECIESX25519AEADRatchetSession.h" +#include "TransitTunnel.h" #include "I2NPProtocol.h" #include "version.h" -using namespace i2p::transport; - namespace i2p { std::shared_ptr NewI2NPMessage () @@ -36,6 +31,11 @@ namespace i2p return std::make_shared >(); } + std::shared_ptr NewI2NPMediumMessage () + { + return std::make_shared >(); + } + std::shared_ptr NewI2NPTunnelMessage (bool endpoint) { return i2p::tunnel::tunnels.NewI2NPTunnelMessage (endpoint); @@ -43,7 +43,10 @@ namespace i2p std::shared_ptr NewI2NPMessage (size_t len) { - return (len < I2NP_MAX_SHORT_MESSAGE_SIZE - I2NP_HEADER_SIZE - 2) ? NewI2NPShortMessage () : NewI2NPMessage (); + len += I2NP_HEADER_SIZE + 2; + if (len <= I2NP_MAX_SHORT_MESSAGE_SIZE) return NewI2NPShortMessage (); + if (len <= I2NP_MAX_MEDIUM_MESSAGE_SIZE) return NewI2NPMediumMessage (); + return NewI2NPMessage (); } void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID, bool checksum) @@ -64,11 +67,15 @@ namespace i2p SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); } - bool I2NPMessage::IsExpired () const + bool I2NPMessage::IsExpired (uint64_t ts) const { - auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto exp = GetExpiration (); return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future + } + + bool I2NPMessage::IsExpired () const + { + return IsExpired (i2p::util::GetMillisecondsSinceEpoch ()); } std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) @@ -103,6 +110,17 @@ namespace i2p return newMsg; } + std::shared_ptr CreateTunnelTestMsg (uint32_t msgID) + { + auto m = NewI2NPShortMessage (); + uint8_t * buf = m->GetPayload (); + htobe32buf (buf + TUNNEL_TEST_MSGID_OFFSET, msgID); + htobe64buf (buf + TUNNEL_TEST_TIMESTAMP_OFFSET, i2p::util::GetMonotonicMicroseconds ()); + m->len += TUNNEL_TEST_SIZE; + m->FillI2NPMessageHeader (eI2NPTunnelTest); + return m; + } + std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID) { auto m = NewI2NPShortMessage (); @@ -124,9 +142,10 @@ namespace i2p } std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, - uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) + uint32_t replyTunnelID, bool exploratory, std::unordered_set * excludedPeers) { - auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); + int cnt = excludedPeers ? excludedPeers->size () : 0; + auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -147,7 +166,6 @@ namespace i2p if (excludedPeers) { - int cnt = excludedPeers->size (); htobe16buf (buf, cnt); buf += 2; for (auto& it: *excludedPeers) @@ -169,7 +187,7 @@ namespace i2p } std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::set& excludedFloodfills, + const std::unordered_set& excludedFloodfills, std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES) { @@ -353,305 +371,6 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } - static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: - void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) - { - if (maxNumTransitTunnels > 0 && g_MaxNumTransitTunnels != maxNumTransitTunnels) - { - LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); - g_MaxNumTransitTunnels = maxNumTransitTunnels; - } - } - - uint16_t GetMaxNumTransitTunnels () - { - return g_MaxNumTransitTunnels; - } - - static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) - { - for (int i = 0; i < num; i++) - { - uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; - if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) - { - LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); - if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; - uint8_t retCode = 0; - // replace record to reply - if (i2p::context.AcceptsTunnels () && - i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && - !i2p::transport::transports.IsBandwidthExceeded () && - !i2p::transport::transports.IsTransitBandwidthExceeded ()) - { - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - } - else - retCode = 30; // always reject with bandwidth reason (30) - - memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - // encrypt reply - i2p::crypto::CBCEncryption encryption; - for (int j = 0; j < num; j++) - { - uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - if (j == i) - { - uint8_t nonce[12]; - memset (nonce, 0, 12); - auto& noiseState = i2p::context.GetCurrentNoiseState (); - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); - return false; - } - } - else - { - encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); - } - } - return true; - } - } - return false; - } - - static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); - if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) - { - LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); - return; - } - - auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - } - else - { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) - { - if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - } - } - - static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) - { - LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); - } - - static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); - size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; - if (len < num*recordSize + 1) - { - LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); - return; - } - - auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); - if (tunnel) - { - // reply for outbound tunnel - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddOutboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - } - else - LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); - } - - static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); - if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) - { - LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); - return; - } - auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - return; - } - const uint8_t * record = buf + 1; - for (int i = 0; i < num; i++) - { - if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) - { - LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); - uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) - { - LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); - return; - } - if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES - { - LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); - return; - } - auto& noiseState = i2p::context.GetCurrentNoiseState (); - uint8_t replyKey[32], layerKey[32], ivKey[32]; - 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); - memcpy (layerKey, noiseState.m_CK + 32, 32); - bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; - if (isEndpoint) - { - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); - memcpy (ivKey, noiseState.m_CK + 32, 32); - } - else - memcpy (ivKey, noiseState.m_CK , 32); - - // check if we accept this tunnel - uint8_t retCode = 0; - if (!i2p::context.AcceptsTunnels () || - i2p::tunnel::tunnels.GetTransitTunnels ().size () > g_MaxNumTransitTunnels || - i2p::transport::transports.IsBandwidthExceeded () || - i2p::transport::transports.IsTransitBandwidthExceeded ()) - retCode = 30; - if (!retCode) - { - // create new transit tunnel - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - layerKey, ivKey, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - } - - // encrypt reply - uint8_t nonce[12]; - memset (nonce, 0, 12); - uint8_t * reply = buf + 1; - for (int j = 0; j < num; j++) - { - nonce[4] = j; // nonce is record # - if (j == i) - { - memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); - return; - } - } - else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - // send reply - if (isEndpoint) - { - auto replyMsg = NewI2NPShortMessage (); - replyMsg->Concat (buf, len); - replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); - if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? - { - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); - uint64_t tag; - memcpy (&tag, noiseState.m_CK, 8); - // we send it to reply tunnel - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); - } - else - { - // IBGW is local - uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); - auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); - if (tunnel) - tunnel->SendTunnelDataMsg (replyMsg); - else - LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); - } - } - else - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - return; - } - record += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - } - std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPTunnelMessage (false); @@ -705,7 +424,11 @@ namespace i2p return msg; } else - return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); + { + auto newMsg = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); + if (msg->onDrop) newMsg->onDrop = msg->onDrop; + return newMsg; + } } std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, @@ -743,49 +466,6 @@ namespace i2p return l; } - void HandleI2NPMessage (uint8_t * msg, size_t len) - { - if (len < I2NP_HEADER_SIZE) - { - LogPrint (eLogError, "I2NP: Message length ", len, " is smaller than header"); - return; - } - uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; - uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); - LogPrint (eLogDebug, "I2NP: Msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); - uint8_t * buf = msg + I2NP_HEADER_SIZE; - auto size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); - len -= I2NP_HEADER_SIZE; - if (size > len) - { - LogPrint (eLogError, "I2NP: Payload size ", size, " exceeds buffer length ", len); - size = len; - } - switch (typeID) - { - case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msgID, buf, size); - break; - case eI2NPShortTunnelBuild: - HandleShortTunnelBuildMsg (msgID, buf, size); - break; - case eI2NPVariableTunnelBuildReply: - HandleTunnelBuildReplyMsg (msgID, buf, size, false); - break; - case eI2NPShortTunnelBuildReply: - HandleTunnelBuildReplyMsg (msgID, buf, size, true); - break; - case eI2NPTunnelBuild: - HandleTunnelBuildMsg (buf, size); - break; - case eI2NPTunnelBuildReply: - // TODO: - break; - default: - LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID); - } - } - void HandleI2NPMessage (std::shared_ptr msg) { if (msg) @@ -795,10 +475,12 @@ namespace i2p switch (typeID) { case eI2NPTunnelData: - i2p::tunnel::tunnels.PostTunnelData (msg); + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPTunnelGateway: - i2p::tunnel::tunnels.PostTunnelData (msg); + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: { @@ -809,10 +491,18 @@ namespace i2p break; } case eI2NPDatabaseStore: + // forward to netDb if came directly or through exploratory tunnel as response to our request + if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ()) + i2p::data::netdb.PostI2NPMsg (msg); + break; case eI2NPDatabaseSearchReply: + if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ()) + i2p::data::netdb.PostDatabaseSearchReplyMsg (msg); + break; case eI2NPDatabaseLookup: - // forward to netDb - i2p::data::netdb.PostI2NPMsg (msg); + // forward to netDb if floodfill and came directly + if (!msg->from && i2p::context.IsFloodfill ()) + i2p::data::netdb.PostI2NPMsg (msg); break; case eI2NPDeliveryStatus: { @@ -822,17 +512,25 @@ namespace i2p i2p::context.ProcessDeliveryStatusMessage (msg); break; } + case eI2NPTunnelTest: + if (msg->from && msg->from->GetTunnelPool ()) + msg->from->GetTunnelPool ()->ProcessTunnelTest (msg); + break; case eI2NPVariableTunnelBuild: - case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: - case eI2NPTunnelBuildReply: case eI2NPShortTunnelBuild: + // forward to tunnel thread + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); + break; + case eI2NPVariableTunnelBuildReply: + case eI2NPTunnelBuildReply: case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; default: - HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + LogPrint(eLogError, "I2NP: Unexpected I2NP message with type ", int(typeID), " during handling; skipping"); } } } @@ -863,14 +561,8 @@ namespace i2p void I2NPMessagesHandler::Flush () { if (!m_TunnelMsgs.empty ()) - { i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs); - m_TunnelMsgs.clear (); - } if (!m_TunnelGatewayMsgs.empty ()) - { i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs); - m_TunnelGatewayMsgs.clear (); - } } } diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index e60f6a9c..911a53bf 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,8 +11,10 @@ #include #include -#include +#include #include +#include +#include #include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" @@ -47,6 +49,11 @@ namespace i2p const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4; const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8; + // TunnelTest + const size_t TUNNEL_TEST_MSGID_OFFSET = 0; + const size_t TUNNEL_TEST_TIMESTAMP_OFFSET = TUNNEL_TEST_MSGID_OFFSET + 4; + const size_t TUNNEL_TEST_SIZE = TUNNEL_TEST_TIMESTAMP_OFFSET + 8; + // DatabaseStore const size_t DATABASE_STORE_KEY_OFFSET = 0; const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32; @@ -101,7 +108,6 @@ namespace i2p enum I2NPMessageType { - eI2NPDummyMsg = 0, eI2NPDatabaseStore = 1, eI2NPDatabaseLookup = 2, eI2NPDatabaseSearchReply = 3, @@ -115,7 +121,8 @@ namespace i2p eI2NPVariableTunnelBuild = 23, eI2NPVariableTunnelBuildReply = 24, eI2NPShortTunnelBuild = 25, - eI2NPShortTunnelBuildReply = 26 + eI2NPShortTunnelBuildReply = 26, + eI2NPTunnelTest = 231 }; const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; @@ -138,8 +145,16 @@ namespace tunnel class TunnelPool; } + const int CONGESTION_LEVEL_MEDIUM = 70; + const int CONGESTION_LEVEL_HIGH = 90; + const int CONGESTION_LEVEL_FULL = 100; + const size_t I2NP_MAX_MESSAGE_SIZE = 62708; const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096; + const size_t I2NP_MAX_MEDIUM_MESSAGE_SIZE = 16384; + const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR = 3; // multiples of RTT + const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN = 200000; // in microseconds + const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX = 2000000; // in microseconds const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT) const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds @@ -148,9 +163,11 @@ namespace tunnel uint8_t * buf; size_t len, offset, maxLen; std::shared_ptr from; + std::function onDrop; + uint64_t enqueueTime; // monotonic microseconds - I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2), - offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header + I2NPMessage (): buf (nullptr), len (I2NP_HEADER_SIZE + 2), + offset(2), maxLen (0), from (nullptr), enqueueTime (0) {}; // reserve 2 bytes for NTCP header // header accessors uint8_t * GetHeader () { return GetBuffer (); }; @@ -160,7 +177,9 @@ namespace tunnel void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); }; uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); }; void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); }; + void SetEnqueueTime (uint64_t mts) { enqueueTime = mts; }; uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); }; + uint64_t GetEnqueueTime () const { return enqueueTime; }; void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); }; uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); }; void UpdateSize () { SetSize (GetPayloadLength ()); }; @@ -240,7 +259,6 @@ namespace tunnel SetSize (len - offset - I2NP_HEADER_SIZE); SetChks (0); } - void ToNTCP2 () { uint8_t * ntcp2 = GetNTCP2Header (); @@ -251,6 +269,9 @@ namespace tunnel void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0, bool checksum = true); void RenewI2NPMessageHeader (); bool IsExpired () const; + bool IsExpired (uint64_t ts) const; // in milliseconds + + void Drop () { if (onDrop) { onDrop (); onDrop = nullptr; }; } }; template @@ -262,6 +283,7 @@ namespace tunnel std::shared_ptr NewI2NPMessage (); std::shared_ptr NewI2NPShortMessage (); + std::shared_ptr NewI2NPMediumMessage (); std::shared_ptr NewI2NPTunnelMessage (bool endpoint); std::shared_ptr NewI2NPMessage (size_t len); @@ -269,11 +291,12 @@ namespace tunnel std::shared_ptr CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from = nullptr); std::shared_ptr CopyI2NPMessage (std::shared_ptr msg); + std::shared_ptr CreateTunnelTestMsg (uint32_t msgID); std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, - uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); + uint32_t replyTunnelID, bool exploratory = false, std::unordered_set * excludedPeers = nullptr); std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::set& excludedFloodfills, + const std::unordered_set& excludedFloodfills, std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); @@ -293,7 +316,6 @@ namespace tunnel std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); size_t GetI2NPMessageLength (const uint8_t * msg, size_t len); - void HandleI2NPMessage (uint8_t * msg, size_t len); void HandleI2NPMessage (std::shared_ptr msg); class I2NPMessagesHandler @@ -306,12 +328,8 @@ namespace tunnel private: - std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; + std::list > m_TunnelMsgs, m_TunnelGatewayMsgs; }; - - const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; - void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); - uint16_t GetMaxNumTransitTunnels (); } #endif diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index d97bd055..681a4999 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -14,7 +14,7 @@ #if defined(__FreeBSD__) || defined(__NetBSD__) #include -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__) +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__) || defined(__HAIKU__) #include #elif defined(__APPLE__) && defined(__MACH__) @@ -36,6 +36,23 @@ #define le64toh(x) OSSwapLittleToHostInt64(x) #elif defined(_WIN32) +#if defined(_MSC_VER) +#include +#define htobe16(x) _byteswap_ushort(x) +#define htole16(x) (x) +#define be16toh(x) _byteswap_ushort(x) +#define le16toh(x) (x) + +#define htobe32(x) _byteswap_ulong(x) +#define htole32(x) (x) +#define be32toh(x) _byteswap_ulong(x) +#define le32toh(x) (x) + +#define htobe64(x) _byteswap_uint64(x) +#define htole64(x) (x) +#define be64toh(x) _byteswap_uint64(x) +#define le64toh(x) (x) +#else #define htobe16(x) __builtin_bswap16(x) #define htole16(x) (x) #define be16toh(x) __builtin_bswap16(x) @@ -50,6 +67,7 @@ #define htole64(x) (x) #define be64toh(x) __builtin_bswap64(x) #define le64toh(x) (x) +#endif #else #define NEEDS_LOCAL_ENDIAN diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index cff0c37d..865beeb8 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" +#include "CryptoKey.h" #include "Identity.h" namespace i2p @@ -27,18 +28,15 @@ namespace data size_t Identity::FromBuffer (const uint8_t * buf, size_t len) { - if ( len < DEFAULT_IDENTITY_SIZE ) { - // buffer too small, don't overflow - return 0; - } - memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE); + if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow + memcpy (this, buf, DEFAULT_IDENTITY_SIZE); return DEFAULT_IDENTITY_SIZE; } IdentHash Identity::Hash () const { IdentHash hash; - SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash); + SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash); return hash; } @@ -49,13 +47,22 @@ namespace data IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { + uint8_t randomPaddingBlock[32]; + RAND_bytes (randomPaddingBlock, 32); if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { - memcpy (m_StandardIdentity.publicKey, publicKey, 32); - RAND_bytes (m_StandardIdentity.publicKey + 32, 224); + memcpy (m_StandardIdentity.publicKey, publicKey ? publicKey : randomPaddingBlock, 32); + for (int i = 0; i < 7; i++) // 224 bytes + memcpy (m_StandardIdentity.publicKey + 32*(i + 1), randomPaddingBlock, 32); } else - memcpy (m_StandardIdentity.publicKey, publicKey, 256); + { + if (publicKey) + memcpy (m_StandardIdentity.publicKey, publicKey, 256); + 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; @@ -93,7 +100,8 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - RAND_bytes (m_StandardIdentity.signingKey, padding); + for (int i = 0; i < 3; i++) // 96 bytes + memcpy (m_StandardIdentity.signingKey + 32*i, randomPaddingBlock, 32); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } @@ -112,6 +120,17 @@ namespace data memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH); break; } +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + { + memcpy (m_StandardIdentity, signingKey, 384); + excessLen = i2p::crypto::MLDSA44_PUBLIC_KEY_LENGTH - 384; + excessBuf = new uint8_t[excessLen]; + memcpy (excessBuf, signingKey + 384, excessLen); + cryptoType = 0xFF; // crypto key is not used + break; + } +#endif default: LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported"); } @@ -124,12 +143,15 @@ namespace data htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { - if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4) + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) { - LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen); - excessLen = MAX_EXTENDED_BUFFER_SIZE - 4; + auto newBuf = new uint8_t[m_ExtendedLen]; + memcpy (newBuf, m_ExtendedBuffer, 4); + memcpy (newBuf + 4, excessBuf, excessLen); + m_ExtendedBufferPtr = newBuf; } - memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); + else + memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } // calculate ident hash @@ -177,7 +199,8 @@ namespace data IdentityEx::~IdentityEx () { - delete m_Verifier; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) + delete[] m_ExtendedBufferPtr; } IdentityEx& IdentityEx::operator=(const IdentityEx& other) @@ -185,15 +208,32 @@ namespace data memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE); m_IdentHash = other.m_IdentHash; + size_t oldLen = m_ExtendedLen; m_ExtendedLen = other.m_ExtendedLen; if (m_ExtendedLen > 0) { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; - memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) + { + if (oldLen > MAX_EXTENDED_BUFFER_SIZE) + { + if (m_ExtendedLen > oldLen) + { + delete[] m_ExtendedBufferPtr; + m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen]; + } + } + else + m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen]; + memcpy (m_ExtendedBufferPtr, other.m_ExtendedBufferPtr, m_ExtendedLen); + } + else + { + if (oldLen > MAX_EXTENDED_BUFFER_SIZE) delete[] m_ExtendedBufferPtr; + memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); + } } - - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return *this; } @@ -202,11 +242,10 @@ namespace data { m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); - m_ExtendedLen = 0; - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return *this; } @@ -220,13 +259,28 @@ namespace data } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); + size_t oldLen = m_ExtendedLen; m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len) { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; - memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) + { + if (oldLen > MAX_EXTENDED_BUFFER_SIZE) + { + if (m_ExtendedLen > oldLen) + { + delete[] m_ExtendedBufferPtr; + m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen]; + } + } + else + m_ExtendedBufferPtr = new uint8_t[m_ExtendedLen]; + memcpy (m_ExtendedBufferPtr, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); + } + else + memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else { @@ -239,8 +293,8 @@ namespace data m_ExtendedLen = 0; SHA256(buf, GetFullLen (), m_IdentHash); - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return GetFullLen (); } @@ -251,32 +305,32 @@ namespace data if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); if (m_ExtendedLen > 0) - memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); + { + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) + memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBufferPtr, m_ExtendedLen); + else + memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); + } return fullLen; } - size_t IdentityEx::FromBase64(const std::string& s) + size_t IdentityEx::FromBase64(std::string_view s) { - const size_t slen = s.length(); - std::vector buf(slen); // binary data can't exceed base64 - const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen); + std::vector buf(s.length ()); // binary data can't exceed base64 + auto len = Base64ToByteStream (s, buf.data(), buf.size ()); return FromBuffer (buf.data(), len); } std::string IdentityEx::ToBase64 () const { const size_t bufLen = GetFullLen(); - const size_t strLen = Base64EncodingBufferSize(bufLen); std::vector buf(bufLen); - std::vector str(strLen); size_t l = ToBuffer (buf.data(), bufLen); - size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen); - return std::string (str.data(), l1); + return i2p::data::ByteStreamToBase64 (buf.data(), l); } size_t IdentityEx::GetSigningPublicKeyLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPublicKeyLen (); return 128; @@ -285,13 +339,12 @@ namespace data const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const { auto keyLen = GetSigningPublicKeyLen (); - if (keyLen > 128) return nullptr; // P521 + if (keyLen > 128) return nullptr; // P521 or PQ return m_StandardIdentity.signingKey + 128 - keyLen; } size_t IdentityEx::GetSigningPrivateKeyLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPrivateKeyLen (); return GetSignatureLen ()/2; @@ -299,14 +352,12 @@ namespace data size_t IdentityEx::GetSignatureLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetSignatureLen (); return i2p::crypto::DSA_SIGNATURE_LENGTH; } bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->Verify (buf, len, signature); return false; @@ -315,7 +366,7 @@ namespace data SigningKeyType IdentityEx::GetSigningKeyType () const { if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 2) - return bufbe16toh (m_ExtendedBuffer); // signing key + return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer : m_ExtendedBufferPtr); // signing key return SIGNING_KEY_TYPE_DSA_SHA1; } @@ -328,7 +379,7 @@ namespace data CryptoKeyType IdentityEx::GetCryptoKeyType () const { if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4) - return bufbe16toh (m_ExtendedBuffer + 2); // crypto key + return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer + 2 : m_ExtendedBufferPtr + 2); // crypto key return CRYPTO_KEY_TYPE_ELGAMAL; } @@ -352,6 +403,10 @@ namespace data return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512); case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: return new i2p::crypto::RedDSA25519Verifier (); +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + return new i2p::crypto::MLDSA44Verifier (); +#endif case SIGNING_KEY_TYPE_RSA_SHA256_2048: case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA512_4096: @@ -363,52 +418,41 @@ namespace data return nullptr; } - void IdentityEx::CreateVerifier () const + void IdentityEx::CreateVerifier () { - if (m_Verifier) return; // don't create again - auto verifier = CreateVerifier (GetSigningKeyType ()); - if (verifier) + if (!m_Verifier) { - auto keyLen = verifier->GetPublicKeyLen (); - if (keyLen <= 128) - verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); - else + auto verifier = CreateVerifier (GetSigningKeyType ()); + if (verifier) { - // for P521 - uint8_t * signingKey = new uint8_t[keyLen]; - memcpy (signingKey, m_StandardIdentity.signingKey, 128); - size_t excessLen = keyLen - 128; - memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - verifier->SetPublicKey (signingKey); - delete[] signingKey; + auto keyLen = verifier->GetPublicKeyLen (); + if (keyLen <= 128) + verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); +#if OPENSSL_PQ + else if (keyLen > 384) + { + // for post-quantum + uint8_t * signingKey = new uint8_t[keyLen]; + memcpy (signingKey, m_StandardIdentity, 384); + size_t excessLen = keyLen - 384; + memcpy (signingKey + 384, m_ExtendedBufferPtr + 4, excessLen); // right after signing and crypto key types + verifier->SetPublicKey (signingKey); + delete[] signingKey; + } +#endif + else + { + // for P521 + uint8_t * signingKey = new uint8_t[keyLen]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = keyLen - 128; + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + verifier->SetPublicKey (signingKey); + delete[] signingKey; + } } + m_Verifier.reset (verifier); } - UpdateVerifier (verifier); - } - - void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const - { - bool del = false; - { - std::lock_guard l(m_VerifierMutex); - if (!m_Verifier) - m_Verifier = verifier; - else - del = true; - } - if (del) - delete verifier; - } - - void IdentityEx::DropVerifier () const - { - i2p::crypto::Verifier * verifier; - { - std::lock_guard l(m_VerifierMutex); - verifier = m_Verifier; - m_Verifier = nullptr; - } - delete verifier; } std::shared_ptr IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key) @@ -419,15 +463,14 @@ namespace data return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); }; @@ -440,11 +483,21 @@ namespace data return CreateEncryptor (GetCryptoKeyType (), key); } + size_t GetIdentityBufferLen (const uint8_t * buf, size_t len) + { + if (len < DEFAULT_IDENTITY_SIZE) return 0; + size_t l = DEFAULT_IDENTITY_SIZE + bufbe16toh (buf + DEFAULT_IDENTITY_SIZE - 2); + if (l > len) return 0; + return l; + } + PrivateKeys& PrivateKeys::operator=(const Keys& keys) { m_Public = std::make_shared(Identity (keys)); memcpy (m_PrivateKey, keys.privateKey, 256); // 256 - memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ()); + size_t keyLen = m_Public->GetSigningPrivateKeyLen (); + if (keyLen > 128) m_SigningPrivateKey.resize (keyLen); + memcpy (m_SigningPrivateKey.data (), keys.signingPrivateKey, keyLen); m_OfflineSignature.resize (0); m_TransientSignatureLen = 0; m_TransientSigningPrivateKeyLen = 0; @@ -460,7 +513,7 @@ namespace data m_OfflineSignature = other.m_OfflineSignature; m_TransientSignatureLen = other.m_TransientSignatureLen; m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen; - memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_TransientSigningPrivateKeyLen > 0 ? m_TransientSigningPrivateKeyLen : m_Public->GetSigningPrivateKeyLen ()); + m_SigningPrivateKey = other.m_SigningPrivateKey; m_Signer = nullptr; CreateSigner (); return *this; @@ -483,8 +536,9 @@ namespace data memcpy (m_PrivateKey, buf + ret, cryptoKeyLen); ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); - if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow - memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); + if (signingPrivateKeySize + ret > len) return 0; // overflow + m_SigningPrivateKey.resize (signingPrivateKeySize); + memcpy (m_SigningPrivateKey.data (), buf + ret, signingPrivateKeySize); ret += signingPrivateKeySize; m_Signer = nullptr; // check if signing private key is all zeros @@ -499,7 +553,12 @@ namespace data { // offline information const uint8_t * offlineInfo = buf + ret; - ret += 4; // expires timestamp + uint32_t expires = bufbe32toh (buf + ret); ret += 4; // expires timestamp + if (expires < i2p::util::GetSecondsSinceEpoch ()) + { + LogPrint (eLogError, "Identity: Offline signature expired"); + return 0; + } SigningKeyType keyType = bufbe16toh (buf + ret); ret += 2; // key type std::unique_ptr transientVerifier (IdentityEx::CreateVerifier (keyType)); if (!transientVerifier) return 0; @@ -520,8 +579,9 @@ namespace data memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen); // override signing private key m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen (); - if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0; - memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen); + if (m_TransientSigningPrivateKeyLen + ret > len) return 0; + if (m_TransientSigningPrivateKeyLen > 128) m_SigningPrivateKey.resize (m_TransientSigningPrivateKeyLen); + memcpy (m_SigningPrivateKey.data (), buf + ret, m_TransientSigningPrivateKeyLen); ret += m_TransientSigningPrivateKeyLen; CreateSigner (keyType); } @@ -541,7 +601,7 @@ namespace data if (IsOfflineSignature ()) memset (buf + ret, 0, signingPrivateKeySize); else - memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize); + memcpy (buf + ret, m_SigningPrivateKey.data (), signingPrivateKeySize); ret += signingPrivateKeySize; if (IsOfflineSignature ()) { @@ -552,32 +612,24 @@ namespace data ret += offlineSignatureLen; // transient private key if (ret + m_TransientSigningPrivateKeyLen > len) return 0; - memcpy (buf + ret, m_SigningPrivateKey, m_TransientSigningPrivateKeyLen); + memcpy (buf + ret, m_SigningPrivateKey.data (), m_TransientSigningPrivateKeyLen); ret += m_TransientSigningPrivateKeyLen; } return ret; } - size_t PrivateKeys::FromBase64(const std::string& s) + size_t PrivateKeys::FromBase64(std::string_view s) { - uint8_t * buf = new uint8_t[s.length ()]; - size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ()); - size_t ret = FromBuffer (buf, l); - delete[] buf; - return ret; + std::vector buf(s.length ()); + size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ()); + return FromBuffer (buf.data (), l); } std::string PrivateKeys::ToBase64 () const { - uint8_t * buf = new uint8_t[GetFullLen ()]; - char * str = new char[GetFullLen ()*2]; - size_t l = ToBuffer (buf, GetFullLen ()); - size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2); - str[l1] = 0; - delete[] buf; - std::string ret(str); - delete[] str; - return ret; + std::vector buf(GetFullLen ()); + size_t l = ToBuffer (buf.data (), buf.size ()); + return i2p::data::ByteStreamToBase64 (buf.data (), l); } void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const @@ -599,13 +651,13 @@ namespace data { if (m_Signer) return; if (keyType == SIGNING_KEY_TYPE_DSA_SHA1) - m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey)); + m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey)); else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ()) - m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH)); // TODO: remove public key check + m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check else { // public key is not required - auto signer = CreateSigner (keyType, m_SigningPrivateKey); + auto signer = CreateSigner (keyType, m_SigningPrivateKey.data ()); if (signer) m_Signer.reset (signer); } } @@ -640,6 +692,11 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: return new i2p::crypto::RedDSA25519Signer (priv); break; +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + return new i2p::crypto::MLDSA44Signer (priv); + break; +#endif default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } @@ -653,8 +710,7 @@ namespace data size_t PrivateKeys::GetPrivateKeyLen () const { - // private key length always 256, but type 4 - return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256; + return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); } uint8_t * PrivateKeys::GetPadding() @@ -680,34 +736,38 @@ namespace data return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); }; return nullptr; } - PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType) + PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType, bool isDestination) { if (type != SIGNING_KEY_TYPE_DSA_SHA1) { PrivateKeys keys; // signature - uint8_t signingPublicKey[512]; // signing public key is 512 bytes max - GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey); + std::unique_ptr verifier (IdentityEx::CreateVerifier (type)); + std::vector signingPublicKey(verifier->GetPublicKeyLen ()); + keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ()); + GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), signingPublicKey.data ()); // encryption uint8_t publicKey[256]; - GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); + if (isDestination) + RAND_bytes (keys.m_PrivateKey, 256); + else + GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); // identity - keys.m_Public = std::make_shared (publicKey, signingPublicKey, type, cryptoType); + keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey.data (), type, cryptoType); keys.CreateSigner (); return keys; @@ -732,9 +792,7 @@ namespace data case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA512_4096: LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA"); -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); @@ -748,6 +806,11 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub); break; +#if OPENSSL_PQ + case SIGNING_KEY_TYPE_MLDSA44: + i2p::crypto::CreateMLDSA44RandomKeys (priv, pub); + break; +#endif default: LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1 @@ -762,13 +825,12 @@ namespace data i2p::crypto::GenerateElGamalKeyPair(priv, pub); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: - case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: i2p::crypto::CreateECIESP256RandomKeys (priv, pub); break; - case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: - i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub); - break; case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: + case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); break; default: @@ -786,9 +848,10 @@ namespace data keys.m_TransientSigningPrivateKeyLen = verifier->GetPrivateKeyLen (); keys.m_TransientSignatureLen = verifier->GetSignatureLen (); keys.m_OfflineSignature.resize (pubKeyLen + m_Public->GetSignatureLen () + 6); + keys.m_SigningPrivateKey.resize (verifier->GetPrivateKeyLen ()); htobe32buf (keys.m_OfflineSignature.data (), expires); // expires htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type - GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key + GenerateSigningKeyPair (type, keys.m_SigningPrivateKey.data (), keys.m_OfflineSignature.data () + 6); // public key Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature // recreate signer keys.m_Signer = nullptr; @@ -807,11 +870,14 @@ namespace data return keys; } - IdentHash CreateRoutingKey (const IdentHash& ident) + IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay) { uint8_t buf[41]; // ident + yyyymmdd memcpy (buf, (const uint8_t *)ident, 32); - i2p::util::GetCurrentDate ((char *)(buf + 32)); + if (nextDay) + i2p::util::GetNextDayDate ((char *)(buf + 32)); + else + i2p::util::GetCurrentDate ((char *)(buf + 32)); IdentHash key; SHA256(buf, 40, key); return key; @@ -820,29 +886,12 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; -#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) - if(i2p::cpu::avx) - { - __asm__ - ( - "vmovups %1, %%ymm0 \n" - "vmovups %2, %%ymm1 \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, %0 \n" - : "=m"(*m.metric) - : "m"(*key1), "m"(*key2) - : "memory", "%xmm0", "%xmm1" // should be replaced by %ymm0/1 once supported by compiler - ); - } - else -#endif - { - const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL (); - m.metric_ll[0] = hash1[0] ^ hash2[0]; - m.metric_ll[1] = hash1[1] ^ hash2[1]; - m.metric_ll[2] = hash1[2] ^ hash2[2]; - m.metric_ll[3] = hash1[3] ^ hash2[3]; - } + + const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL (); + m.metric_ll[0] = hash1[0] ^ hash2[0]; + m.metric_ll[1] = hash1[1] ^ hash2[1]; + m.metric_ll[2] = hash1[2] ^ hash2[2]; + m.metric_ll[3] = hash1[3] ^ hash2[3]; return m; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 10f1d5ed..c95ce000 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,16 +12,19 @@ #include #include #include +#include #include -#include #include -#include #include "Base.h" #include "Signature.h" -#include "CryptoKey.h" namespace i2p { +namespace crypto +{ + class CryptoKeyEncryptor; + class CryptoKeyDecryptor; +} namespace data { typedef Tag<32> IdentHash; @@ -56,6 +59,8 @@ namespace data Identity& operator=(const Keys& keys); size_t FromBuffer (const uint8_t * buf, size_t len); IdentHash Hash () const; + operator uint8_t * () { return reinterpret_cast(this); } + operator const uint8_t * () const { return reinterpret_cast(this); } }; Keys CreateRandomKeys (); @@ -65,9 +70,10 @@ namespace data const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; - const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later - const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES - + const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD = 5; + const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD = 6; + const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD = 7; + const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2; @@ -76,11 +82,12 @@ namespace data const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7; - const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented + const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // since openssl 3.0.0 const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9; const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only - + const uint16_t SIGNING_KEY_TYPE_MLDSA44 = 12; + typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; @@ -101,7 +108,7 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; - size_t FromBase64(const std::string& s); + size_t FromBase64(std::string_view s); std::string ToBase64 () const; const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; @@ -118,7 +125,6 @@ namespace data SigningKeyType GetSigningKeyType () const; bool IsRSA () const; // signing key type CryptoKeyType GetCryptoKeyType () const; - void DropVerifier () const; // to save memory bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } void RecalculateIdentHash(uint8_t * buff=nullptr); @@ -128,19 +134,23 @@ namespace data private: - void CreateVerifier () const; - void UpdateVerifier (i2p::crypto::Verifier * verifier) const; - + void CreateVerifier (); + private: Identity m_StandardIdentity; IdentHash m_IdentHash; - mutable i2p::crypto::Verifier * m_Verifier = nullptr; - mutable std::mutex m_VerifierMutex; + std::unique_ptr m_Verifier; size_t m_ExtendedLen; - uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; + union + { + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; + uint8_t * m_ExtendedBufferPtr; + }; }; + size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer + class PrivateKeys // for eepsites { public: @@ -154,7 +164,7 @@ namespace data std::shared_ptr GetPublic () const { return m_Public; }; const uint8_t * GetPrivateKey () const { return m_PrivateKey; }; - const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; }; + const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey.data (); }; size_t GetSignatureLen () const; // might not match identity bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; }; uint8_t * GetPadding(); @@ -165,13 +175,13 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; - size_t FromBase64(const std::string& s); + size_t FromBase64(std::string_view s); std::string ToBase64 () const; std::shared_ptr CreateDecryptor (const uint8_t * key) const; static std::shared_ptr CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key); - static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL); + static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL, bool isDestination = false); static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long static i2p::crypto::Signer * CreateSigner (SigningKeyType keyType, const uint8_t * priv); @@ -190,7 +200,7 @@ namespace data std::shared_ptr m_Public; uint8_t m_PrivateKey[256]; - uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes + std::vector m_SigningPrivateKey; mutable std::unique_ptr m_Signer; std::vector m_OfflineSignature; // non zero length, if applicable size_t m_TransientSignatureLen = 0; @@ -211,7 +221,7 @@ namespace data bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; }; }; - IdentHash CreateRoutingKey (const IdentHash& ident); + IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay = false); XORMetric operator^(const IdentHash& key1, const IdentHash& key2); // destination for delivery instructions diff --git a/libi2pd/KadDHT.cpp b/libi2pd/KadDHT.cpp new file mode 100644 index 00000000..0f9df8e4 --- /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; // something wrong + root->one = new DHTNode; + root = root->one; + } + else + { + if (root->zero) return; // something 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) const + { + 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) const + { + 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) const + { + 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) const + { + 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 (const 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..3bc31780 --- /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) const; + std::vector > FindClosest (const IdentHash& h, size_t num, const Filter& filter = nullptr) const; + + void Print (std::stringstream& s); + size_t GetSize () const { return m_Size; }; + void Clear (); + void Cleanup (const 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) const; + void FindClosest (const IdentHash& h, size_t num, DHTNode * root, int level, std::vector >& hashes) const; + void Cleanup (DHTNode * root); + void Print (std::stringstream& s, DHTNode * root, int level); + + private: + + DHTNode * m_Root; + size_t m_Size; + // transient + mutable Filter m_Filter; + }; +} +} + +#endif diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 387527e3..fc0e722d 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,6 +14,7 @@ #include "Timestamp.h" #include "NetDb.hpp" #include "Tunnel.h" +#include "CryptoKey.h" #include "LeaseSet.h" namespace i2p @@ -37,14 +38,7 @@ namespace data void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature) { - if (len > m_BufferLen) - { - auto oldBuffer = m_Buffer; - m_Buffer = new uint8_t[len]; - delete[] oldBuffer; - } - memcpy (m_Buffer, buf, len); - m_BufferLen = len; + SetBuffer (buf, len); ReadFromBuffer (false, verifySignature); } @@ -57,7 +51,7 @@ namespace data void LeaseSet::ReadFromBuffer (bool readIdentity, bool verifySignature) { if (readIdentity || !m_Identity) - m_Identity = std::make_shared(m_Buffer, m_BufferLen); + m_Identity = netdb.NewIdentity (m_Buffer, m_BufferLen); size_t size = m_Identity->GetFullLen (); if (size + 256 > m_BufferLen) { @@ -83,7 +77,7 @@ namespace data LogPrint (eLogDebug, "LeaseSet: Read num=", (int)num); if (!num || num > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: Rncorrect number of leases", (int)num); + LogPrint (eLogError, "LeaseSet: Incorrect number of leases", (int)num); m_IsValid = false; return; } @@ -264,8 +258,18 @@ namespace data void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) { - if (m_Buffer) delete[] m_Buffer; - m_Buffer = new uint8_t[len]; + if (len > MAX_LS_BUFFER_SIZE) + { + LogPrint (eLogError, "LeaseSet: Buffer is too long ", len); + len = MAX_LS_BUFFER_SIZE; + } + if (m_Buffer && len > m_BufferLen) + { + delete[] m_Buffer; + m_Buffer = nullptr; + } + if (!m_Buffer) + m_Buffer = new uint8_t[len]; m_BufferLen = len; memcpy (m_Buffer, buf, len); } @@ -312,9 +316,9 @@ namespace data { // standard LS2 header std::shared_ptr identity; - if (readIdentity) + if (readIdentity || !GetIdentity ()) { - identity = std::make_shared(buf, len); + identity = netdb.NewIdentity (buf, len); SetIdentity (identity); } else @@ -363,6 +367,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)); @@ -394,6 +400,7 @@ namespace data offset += propertiesLen; // skip for now. TODO: implement properties // key sections CryptoKeyType preferredKeyType = m_EncryptionType; + m_EncryptionType = 0; bool preferredKeyFound = false; if (offset + 1 > len) return 0; int numKeySections = buf[offset]; offset++; @@ -405,14 +412,22 @@ namespace data if (offset + encryptionKeyLen > len) return 0; if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only { - // we pick first valid key if preferred not found - auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); - if (encryptor && (!m_Encryptor || keyType == preferredKeyType)) - { - m_Encryptor = encryptor; // TODO: atomic - m_EncryptionType = keyType; - if (keyType == preferredKeyType) preferredKeyFound = true; - } + // we pick max key type if preferred not found +#if !OPENSSL_PQ + if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported +#endif + { + if (keyType == preferredKeyType || !m_Encryptor || keyType > m_EncryptionType) + { + auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); + if (encryptor) + { + m_Encryptor = encryptor; // TODO: atomic + m_EncryptionType = keyType; + if (keyType == preferredKeyType) preferredKeyFound = true; + } + } + } } offset += encryptionKeyLen; } @@ -420,6 +435,16 @@ namespace data if (offset + 1 > len) return 0; int numLeases = buf[offset]; offset++; auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (GetExpirationTime () > ts + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Expiration time is from future ", GetExpirationTime ()/1000LL); + return 0; + } + if (ts > m_PublishedTimestamp*1000LL + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Published time is too old ", m_PublishedTimestamp); + return 0; + } if (IsStoreLeases ()) { UpdateLeasesBegin (); @@ -430,6 +455,11 @@ namespace data lease.tunnelGateway = buf + offset; offset += 32; // gateway lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date + if (lease.endDate > ts + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Lease end date is from future ", lease.endDate); + return 0; + } UpdateLease (lease, ts); } UpdateLeasesEnd (); @@ -723,25 +753,41 @@ namespace data memset (m_Buffer + offset, 0, signingKeyLen); offset += signingKeyLen; // num leases + auto numLeasesPos = offset; m_Buffer[offset] = num; offset++; // leases m_Leases = m_Buffer + offset; auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); + int skipped = 0; for (int i = 0; i < num; i++) { + uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration + ts *= 1000; // in milliseconds + if (ts <= currentTime) + { + // already expired, skip + skipped++; + continue; + } + if (ts > m_ExpirationTime) m_ExpirationTime = ts; + // make sure leaseset is newer than previous, but adding some time to expiration date + ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32); offset += 32; // gateway id htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ()); offset += 4; // tunnel id - uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration - ts *= 1000; // in milliseconds - if (ts > m_ExpirationTime) m_ExpirationTime = ts; - // make sure leaseset is newer than previous, but adding some time to expiration date - ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs htobe64buf (m_Buffer + offset, ts); offset += 8; // end date } + if (skipped > 0) + { + // adjust num leases + if (skipped > num) skipped = num; + num -= skipped; + m_BufferLen -= skipped*LEASE_SIZE; + m_Buffer[numLeasesPos] = num; + } // we don't sign it yet. must be signed later on } @@ -802,8 +848,8 @@ namespace data } LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, const std::vector >& tunnels, - bool isPublic, bool isPublishedEncrypted): + const EncryptionKeys& encryptionKeys, const std::vector >& tunnels, + bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { auto identity = keys.GetPublic (); @@ -812,7 +858,7 @@ namespace data if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; size_t keySectionsLen = 0; for (const auto& it: encryptionKeys) - keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/; + keySectionsLen += 2/*key type*/ + 2/*key len*/ + it->pub.size()/*key*/; m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ + 1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); uint16_t flags = 0; @@ -832,8 +878,7 @@ namespace data m_Buffer[0] = storeType; // LS2 header auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1; - auto timestamp = i2p::util::GetSecondsSinceEpoch (); - htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds) + htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds) uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later htobe16buf (m_Buffer + offset, flags); offset += 2; // flags if (keys.IsOfflineSignature ()) @@ -848,35 +893,50 @@ namespace data m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key for (const auto& it: encryptionKeys) { - htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type - htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len - memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key + htobe16buf (m_Buffer + offset, it->keyType); offset += 2; // key type + htobe16buf (m_Buffer + offset, it->pub.size()); offset += 2; // key len + memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key } // leases uint32_t expirationTime = 0; // in seconds + int skipped = 0; auto numLeasesPos = offset; m_Buffer[offset] = num; offset++; // num leases for (int i = 0; i < num; i++) { + auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration + if (ts <= publishedTimestamp) + { + // already expired, skip + skipped++; + continue; + } + if (ts > expirationTime) expirationTime = ts; memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32); offset += 32; // gateway id htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ()); offset += 4; // tunnel id - auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration - if (ts > expirationTime) expirationTime = ts; htobe32buf (m_Buffer + offset, ts); offset += 4; // end date } + if (skipped > 0) + { + // adjust num leases + if (skipped > num) skipped = num; + num -= skipped; + m_BufferLen -= skipped*LEASE2_SIZE; + m_Buffer[numLeasesPos] = num; + } // update expiration if (expirationTime) { SetExpirationTime (expirationTime*1000LL); - auto expires = (int)expirationTime - timestamp; + auto expires = (int)expirationTime - publishedTimestamp; htobe16buf (expiresBuf, expires > 0 ? expires : 0); } else { // no tunnels or withdraw - SetExpirationTime (timestamp*1000LL); + SetExpirationTime (publishedTimestamp*1000LL); memset (expiresBuf, 0, 2); // expires immeditely } // sign diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index a79a5870..f5197eb5 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,12 +12,14 @@ #include #include #include +#include #include #include #include "Identity.h" #include "Timestamp.h" #include "I2PEndian.h" #include "Blinding.h" +#include "CryptoKey.h" namespace i2p { @@ -57,12 +59,16 @@ namespace data }; typedef std::function LeaseInspectFunc; - - const size_t MAX_LS_BUFFER_SIZE = 3072; +#if OPENSSL_PQ + const size_t MAX_LS_BUFFER_SIZE = 8192; +#else + const size_t MAX_LS_BUFFER_SIZE = 4096; +#endif const size_t LEASE_SIZE = 44; // 32 + 4 + 8 const size_t LEASE2_SIZE = 40; // 32 + 4 + 4 const uint8_t MAX_NUM_LEASES = 16; - + const uint64_t LEASESET_EXPIRATION_TIME_THRESHOLD = 12*60*1000; // in milliseconds + const uint8_t NETDB_STORE_TYPE_LEASESET = 1; class LeaseSet: public RoutingDestination { @@ -96,6 +102,9 @@ namespace data void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return true; }; + // used in webconsole + void ExpireLease () { m_ExpirationTime = i2p::util::GetSecondsSinceEpoch (); }; + protected: void UpdateLeasesBegin (); @@ -145,8 +154,9 @@ namespace data { public: - 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 + 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_ECIES_X25519_AEAD); + LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // store type 5, called from local netdb only uint8_t GetStoreType () const { return m_StoreType; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; bool IsPublic () const { return m_IsPublic; }; @@ -176,7 +186,7 @@ namespace data private: uint8_t m_StoreType; - uint32_t m_PublishedTimestamp = 0; + uint32_t m_PublishedTimestamp = 0; // seconds bool m_IsPublic = true, m_IsPublishedEncrypted = false; std::shared_ptr m_TransientVerifier; CryptoKeyType m_EncryptionType; @@ -242,17 +252,13 @@ namespace data { public: - struct KeySection - { - uint16_t keyType, keyLen; - const uint8_t * encryptionPublicKey; - }; - typedef std::vector KeySections; + typedef std::list > EncryptionKeys; LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, + const EncryptionKeys& encryptionKeys, const std::vector >& tunnels, - bool isPublic, bool isPublishedEncrypted = false); + bool isPublic, uint64_t publishedTimestamp, + bool isPublishedEncrypted = false); LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index e90b5e2b..76e85d4a 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,13 +18,14 @@ 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 - "info", // eLogInfo - "debug" // eLogDebug + "none", // eLogNone + "critical", // eLogCritical + "error", // eLogError + "warn", // eLogWarning + "info", // eLogInfo + "debug" // eLogDebug }; /** @@ -32,15 +33,16 @@ namespace log { * @note Using ISO 6429 (ANSI) color sequences */ #ifdef _WIN32 - static const char *LogMsgColors[] = { "", "", "", "", "", "" }; + 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;41m", /* critical: red background */ + "\033[1;31m", /* error: red */ + "\033[1;33m", /* warning: yellow */ + "\033[1;36m", /* info: cyan */ + "\033[1;34m", /* debug: blue */ + "\033[0m" /* reset */ }; #endif @@ -53,6 +55,7 @@ namespace log { int priority = LOG_DEBUG; switch (l) { case eLogNone : priority = LOG_CRIT; break; + case eLogCritical: priority = LOG_CRIT; break; case eLogError : priority = LOG_ERR; break; case eLogWarning : priority = LOG_WARNING; break; case eLogInfo : priority = LOG_INFO; break; @@ -123,13 +126,14 @@ namespace log { void Log::SetLogLevel (const std::string& level_) { std::string level=str_tolower(level_); - 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 == "debug") { m_MinLevel = eLogDebug; } + if (level == "none") { m_MinLevel = eLogNone; } + else if (level == "critical") { m_MinLevel = eLogCritical; } + else if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } else { - LogPrint(eLogError, "Log: Unknown loglevel: ", level); + LogPrint(eLogCritical, "Log: Unknown loglevel: ", level); return; } LogPrint(eLogInfo, "Log: Logging level set to ", level); @@ -212,7 +216,7 @@ namespace log { m_LogStream = os; return; } - LogPrint(eLogError, "Log: Can't open file ", path); + LogPrint(eLogCritical, "Log: Can't open file ", path); } void Log::SendTo (std::shared_ptr os) { diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 465e10bc..18592c9e 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,6 +27,7 @@ enum LogLevel { eLogNone = 0, + eLogCritical, eLogError, eLogWarning, eLogInfo, @@ -86,8 +87,8 @@ namespace log { Log (); ~Log (); - LogType GetLogType () { return m_Destination; }; - LogLevel GetLogLevel () { return m_MinLevel; }; + LogType GetLogType () const { return m_Destination; }; + LogLevel GetLogLevel () const { return m_MinLevel; }; void Start (); void Stop (); @@ -159,6 +160,11 @@ namespace log { } // log } // i2p +inline bool CheckLogLevel (LogLevel level) noexcept +{ + return level <= i2p::log::Logger().GetLogLevel (); +} + /** internal usage only -- folding args array to single string */ template void LogPrint (std::stringstream& s, TValue&& arg) noexcept @@ -166,16 +172,6 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept s << std::forward(arg); } -#if (__cplusplus < 201703L) // below C++ 17 -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept -{ - LogPrint (s, std::forward(arg)); - LogPrint (s, std::forward(args)...); -} -#endif - /** * @brief Create log message and send it to queue * @param level Message level (eLogError, eLogInfo, ...) @@ -184,22 +180,14 @@ void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept template void LogPrint (LogLevel level, TArgs&&... args) noexcept { - i2p::log::Log &log = i2p::log::Logger(); - if (level > log.GetLogLevel ()) - return; + if (!CheckLogLevel (level)) return; // fold message to single string std::stringstream ss; - -#if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - auto msg = std::make_shared(level, std::time(nullptr), std::move(ss).str()); msg->tid = std::this_thread::get_id(); - log.Append(msg); + i2p::log::Logger().Append(msg); } /** @@ -213,11 +201,7 @@ void ThrowFatal (TArgs&&... args) noexcept if (!f) return; // fold message to single string std::stringstream ss(""); -#if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif f (ss.str ()); } diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 8a2d6c7c..b3a51488 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,9 +19,10 @@ #include "RouterContext.h" #include "Transports.h" #include "NetDb.hpp" -#include "NTCP2.h" #include "HTTP.h" #include "util.h" +#include "Socks5.h" +#include "NTCP2.h" #if defined(__linux__) && !defined(_NETINET_IN_H) #include @@ -41,28 +42,29 @@ namespace transport delete[] m_SessionConfirmedBuffer; } - void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) + bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) { i2p::crypto::InitNoiseXKState (*this, rs); // h = SHA256(h || epub) MixHash (epub, 32); // x25519 between pub and priv uint8_t inputKeyMaterial[32]; - priv.Agree (pub, inputKeyMaterial); + if (!priv.Agree (pub, inputKeyMaterial)) return false; MixKey (inputKeyMaterial); + return true; } - void NTCP2Establisher::KDF1Alice () + bool NTCP2Establisher::KDF1Alice () { - KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); + return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); } - void NTCP2Establisher::KDF1Bob () + bool NTCP2Establisher::KDF1Bob () { - KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); + return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); } - void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) + bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) { MixHash (sessionRequest + 32, 32); // encrypted payload @@ -73,33 +75,35 @@ namespace transport // x25519 between remote pub and ephemaral priv uint8_t inputKeyMaterial[32]; - m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial); - + if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false; MixKey (inputKeyMaterial); + return true; } - void NTCP2Establisher::KDF2Alice () + bool NTCP2Establisher::KDF2Alice () { - KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ()); + return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ()); } - void NTCP2Establisher::KDF2Bob () + bool NTCP2Establisher::KDF2Bob () { - KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); + return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); } - void NTCP2Establisher::KDF3Alice () + bool NTCP2Establisher::KDF3Alice () { uint8_t inputKeyMaterial[32]; - i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial); + if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false; MixKey (inputKeyMaterial); + return true; } - void NTCP2Establisher::KDF3Bob () + bool NTCP2Establisher::KDF3Bob () { uint8_t inputKeyMaterial[32]; - m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial); + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false; MixKey (inputKeyMaterial); + return true; } void NTCP2Establisher::CreateEphemeralKey () @@ -107,20 +111,19 @@ namespace transport m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); } - void NTCP2Establisher::CreateSessionRequestMessage () + bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng) { // create buffer and fill padding - auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes + auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes m_SessionRequestBufferLen = paddingLength + 64; RAND_bytes (m_SessionRequestBuffer + 64, paddingLength); // encrypt X i2p::crypto::CBCEncryption encryption; encryption.SetKey (m_RemoteIdentHash); - encryption.SetIV (m_IV); - encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X - encryption.GetIV (m_IV); // save IV for SessionCreated + encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X + memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated // encryption key for next block - KDF1Alice (); + if (!KDF1Alice ()) return false; // fill options uint8_t options[32]; // actual options size is 16 bytes memset (options, 0, 16); @@ -128,7 +131,8 @@ namespace transport options[1] = 2; // ver htobe16buf (options + 2, paddingLength); // padLen // m3p2Len - auto bufLen = i2p::context.GetRouterInfo ().GetBufferLen (); + auto riBuffer = i2p::context.CopyRouterInfoBuffer (); + auto bufLen = riBuffer->GetBufferLen (); m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options htobe16buf (options + 4, m3p2Len); // fill m3p2 payload (RouterInfo block) @@ -137,40 +141,44 @@ namespace transport m3p2[0] = eNTCP2BlkRouterInfo; // block htobe16buf (m3p2 + 1, bufLen + 1); // flag + RI m3p2[3] = 0; // flag - memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex + memcpy (m3p2 + 4, riBuffer->data (), bufLen); // TODO: eliminate extra copy // 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]; - memset (nonce, 0, 12); // set nonce to zero - i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt + // encrypt options + if (!Encrypt (options, m_SessionRequestBuffer + 32, 16)) + { + LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options"); + return false; + } + return true; } - void NTCP2Establisher::CreateSessionCreatedMessage () + bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng) { - auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); + auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); m_SessionCreatedBufferLen = paddingLen + 64; RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen); // encrypt Y i2p::crypto::CBCEncryption encryption; encryption.SetKey (i2p::context.GetIdentHash ()); - encryption.SetIV (m_IV); - encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y + encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y // encryption key for next block (m_K) - KDF2Bob (); + if (!KDF2Bob ()) return false; uint8_t options[16]; memset (options, 0, 16); htobe16buf (options + 2, paddingLen); // padLen - htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsB - // sign and encrypt options, use m_H as AD - uint8_t nonce[12]; - memset (nonce, 0, 12); // set nonce to zero - i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt - + htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds + // encrypt options + if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16)) + { + LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options"); + return false; + } + return true; } - void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce) + bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 () { // update AD MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload @@ -178,21 +186,31 @@ namespace transport if (paddingLength > 0) MixHash (m_SessionCreatedBuffer + 64, paddingLength); - // part1 48 bytes - i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt + // part1 48 bytes, n = 1 + if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32)) + { + LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1"); + return false; + } + return true; } - void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce) + bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 () { // part 2 // update AD again MixHash (m_SessionConfirmedBuffer, 48); // encrypt m3p2, it must be filled in SessionRequest - KDF3Alice (); + if (!KDF3Alice ()) return false; // MixKey, n = 0 uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; - i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt + if (!Encrypt (m3p2, m3p2, m3p2Len - 16)) + { + LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2"); + return false; + } // update h again MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) + return true; } bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew) @@ -201,15 +219,17 @@ namespace transport // decrypt X i2p::crypto::CBCDecryption decryption; decryption.SetKey (i2p::context.GetIdentHash ()); - decryption.SetIV (i2p::context.GetNTCP2IV ()); - decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ()); - decryption.GetIV (m_IV); // save IV for SessionCreated + decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ()); + memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated // decryption key for next block - KDF1Bob (); - // verify MAC and decrypt options block (32 bytes), use m_H as AD - uint8_t nonce[12], options[16]; - memset (nonce, 0, 12); // set nonce to zero - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt + if (!KDF1Bob ()) + { + LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed"); + return false; + } + // verify MAC and decrypt options block (32 bytes) + uint8_t options[16]; + if (Decrypt (m_SessionRequestBuffer + 32, options, 16)) { // options if (options[0] && options[0] != i2p::context.GetNetID ()) @@ -257,15 +277,16 @@ namespace transport // decrypt Y i2p::crypto::CBCDecryption decryption; decryption.SetKey (m_RemoteIdentHash); - decryption.SetIV (m_IV); - decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ()); + decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ()); // decryption key for next block (m_K) - KDF2Alice (); + if (!KDF2Alice ()) + { + LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed"); + return false; + } // decrypt and verify MAC uint8_t payload[16]; - uint8_t nonce[12]; - memset (nonce, 0, 12); // set nonce to zero - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, GetH (), 32, GetK (), nonce, payload, 16, false)) // decrypt + if (Decrypt (m_SessionCreatedBuffer + 32, payload, 16)) { // options paddingLen = bufbe16toh(payload + 2); @@ -286,7 +307,7 @@ namespace transport return true; } - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce) + bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () { // update AD MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload @@ -294,7 +315,8 @@ namespace transport if (paddingLength > 0) MixHash (m_SessionCreatedBuffer + 64, paddingLength); - if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S + // decrypt S, n = 1 + if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32)) { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); return false; @@ -302,14 +324,18 @@ namespace transport return true; } - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf) + bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf) { // update AD again MixHash (m_SessionConfirmedBuffer, 48); - KDF3Bob (); - if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt - // caclulate new h again for KDF data + if (!KDF3Bob ()) // MixKey, n = 0 + { + LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed"); + return false; + } + if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16)) + // calculate new h again for KDF data MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) else { @@ -325,6 +351,7 @@ namespace transport m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_Establisher (new NTCP2Establisher), + m_SendKey (nullptr), m_ReceiveKey (nullptr), #if OPENSSL_SIPHASH m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), #else @@ -347,7 +374,7 @@ namespace transport LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); } m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + - rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } NTCP2Session::~NTCP2Session () @@ -373,11 +400,30 @@ namespace transport m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCP2Session (shared_from_this ()); + if (!m_IntermediateQueue.empty ()) + m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue); + for (auto& it: m_SendQueue) + it->Drop (); m_SendQueue.clear (); - LogPrint (eLogDebug, "NTCP2: Session terminated"); + SetSendQueueSize (0); + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity) + { + LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated"); + } + else + { + LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (), " terminated"); + } } } + void NTCP2Session::Close () + { + m_Socket.close (); + } + void NTCP2Session::TerminateByTimeout () { SendTerminationAndTerminate (eNTCP2IdleTimeout); @@ -385,14 +431,15 @@ namespace transport void NTCP2Session::Done () { - m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); } void NTCP2Session::Established () { m_IsEstablished = true; m_Establisher.reset (nullptr); - SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT); + SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE); + SendQueue (); transports.PeerConnected (shared_from_this ()); } @@ -418,7 +465,7 @@ namespace transport void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) { if (m_NextReceivedBuffer && !m_IsReceiving && - ts > m_LastActivityTimestamp + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) + ts > GetLastActivityTimestamp () + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) { delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; @@ -444,8 +491,14 @@ namespace transport void NTCP2Session::SendSessionRequest () { - m_Establisher->CreateSessionRequestMessage (); + if (!m_Establisher->CreateSessionRequestMessage (m_Server.GetRng ())) + { + LogPrint (eLogWarning, "NTCP2: Send SessionRequest KDF failed"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } // send message + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch (); boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionRequestBuffer, m_Establisher->m_SessionRequestBufferLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -468,7 +521,6 @@ namespace transport void NTCP2Session::HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - (void) bytes_transferred; if (ecode) { LogPrint (eLogWarning, "NTCP2: SessionRequest read error: ", ecode.message ()); @@ -476,38 +528,47 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred); - uint16_t paddingLen = 0; - bool clockSkew = false; - if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew)) - { - if (clockSkew) + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this (), bytes_transferred] () { - // we don't care about padding, send SessionCreated and close session - SendSessionCreated (); - m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - else if (paddingLen > 0) - { - if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max - { - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), - std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long"); - Terminate (); - } - } - else - SendSessionCreated (); - } - else - Terminate (); + s->ProcessSessionRequest (bytes_transferred);; + }); } } + void NTCP2Session::ProcessSessionRequest (size_t len) + { + LogPrint (eLogDebug, "NTCP2: SessionRequest received ", len); + uint16_t paddingLen = 0; + bool clockSkew = false; + if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew)) + { + if (clockSkew) + { + // we don't care about padding, send SessionCreated and close session + SendSessionCreated (); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + else if (paddingLen > 0) + { + if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max + { + boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), + std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + else + { + LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + } + else + SendSessionCreated (); + } + else + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -516,13 +577,25 @@ namespace transport Terminate (); } else - SendSessionCreated (); + { + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this ()] () + { + s->SendSessionCreated (); + }); + } } void NTCP2Session::SendSessionCreated () { - m_Establisher->CreateSessionCreatedMessage (); + if (!m_Establisher->CreateSessionCreatedMessage (m_Server.GetRng ())) + { + LogPrint (eLogWarning, "NTCP2: Send SessionCreated KDF failed"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } // send message + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch (); boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionCreatedBuffer, m_Establisher->m_SessionCreatedBufferLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -536,31 +609,45 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred); - uint16_t paddingLen = 0; - if (m_Establisher->ProcessSessionCreatedMessage (paddingLen)) - { - if (paddingLen > 0) + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this (), bytes_transferred] () { - if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max - { - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), - std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long"); - Terminate (); - } - } - else - SendSessionConfirmed (); - } - else - Terminate (); + s->ProcessSessionCreated (bytes_transferred); + }); } } + void NTCP2Session::ProcessSessionCreated (size_t len) + { + LogPrint (eLogDebug, "NTCP2: SessionCreated received ", len); + uint16_t paddingLen = 0; + if (m_Establisher->ProcessSessionCreatedMessage (paddingLen)) + { + if (paddingLen > 0) + { + if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max + { + boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), + std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + else + { + LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + } + else + SendSessionConfirmed (); + } + else + { + if (GetRemoteIdentity ()) + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + } + void NTCP2Session::HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -571,17 +658,27 @@ namespace transport else { m_Establisher->m_SessionCreatedBufferLen += bytes_transferred; - SendSessionConfirmed (); + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this ()] () + { + s->SendSessionConfirmed (); + }); } } void NTCP2Session::SendSessionConfirmed () { - uint8_t nonce[12]; - CreateNonce (1, nonce); // set nonce to 1 - m_Establisher->CreateSessionConfirmedMessagePart1 (nonce); - memset (nonce, 0, 12); // set nonce back to 0 - m_Establisher->CreateSessionConfirmedMessagePart2 (nonce); + if (!m_Establisher->CreateSessionConfirmedMessagePart1 ()) + { + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } + if (!m_Establisher->CreateSessionConfirmedMessagePart2 ()) + { + LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } // send message boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionConfirmedBuffer, m_Establisher->m3p2Len + 48), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionConfirmedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -633,6 +730,7 @@ namespace transport void NTCP2Session::HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogWarning, "NTCP2: SessionConfirmed read error: ", ecode.message ()); @@ -640,82 +738,150 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); - // part 1 - uint8_t nonce[12]; - CreateNonce (1, nonce); - if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce)) - { - // part 2 - std::vector buf(m_Establisher->m3p2Len - 16); // -MAC - memset (nonce, 0, 12); // set nonce to 0 again - if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf.data ())) + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this ()] () { - KeyDerivationFunctionDataPhase (); - // Bob data phase keys - m_SendKey = m_Kba; - m_ReceiveKey = m_Kab; - SetSipKeys (m_Sipkeysba, m_Sipkeysab); - memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8); - memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8); - // payload - // process RI - if (buf[0] != eNTCP2BlkRouterInfo) - { - LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed"); - Terminate (); - return; - } - auto size = bufbe16toh (buf.data () + 1); - if (size > buf.size () - 3) - { - LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); - Terminate (); - return; - } - // TODO: check flag - i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag - if (ri.IsUnreachable ()) - { - LogPrint (eLogError, "NTCP2: Signature verification failed in SessionConfirmed"); - SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail); - return; - } - if (i2p::util::GetMillisecondsSinceEpoch () > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes - { - LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed"); - SendTerminationAndTerminate (eNTCP2Message3Error); - return; - } - auto addr = ri.GetNTCP2AddressWithStaticKey (m_Establisher->m_RemoteStaticKey); - if (!addr) - { - LogPrint (eLogError, "NTCP2: No NTCP2 address with static key found in SessionConfirmed"); - Terminate (); - return; - } - i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice - // TODO: process options - - // ready to communicate - auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ()); - if (m_Server.AddNTCP2Session (shared_from_this (), true)) - { - Established (); - ReceiveLength (); - } - else - Terminate (); - } - else - Terminate (); - } - else - Terminate (); + s->ProcessSessionConfirmed ();; + }); } } + void NTCP2Session::ProcessSessionConfirmed () + { + // run on establisher thread + LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); + // part 1 + if (m_Establisher->ProcessSessionConfirmedMessagePart1 ()) + { + // part 2 + auto buf = std::make_shared > (m_Establisher->m3p2Len - 16); // -MAC + if (m_Establisher->ProcessSessionConfirmedMessagePart2 (buf->data ())) // TODO:handle in establisher thread + { + // payload + // RI block must be first + if ((*buf)[0] != eNTCP2BlkRouterInfo) + { + LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)(*buf)[0], " in SessionConfirmed"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } + auto size = bufbe16toh (buf->data () + 1); + if (size > buf->size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1) + { + LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + return; + } + boost::asio::post (m_Server.GetService (), + [s = shared_from_this (), buf, size] () + { + s->EstablishSessionAfterSessionConfirmed (buf, size); + }); + } + else + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + else + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + + void NTCP2Session::EstablishSessionAfterSessionConfirmed (std::shared_ptr > buf, size_t size) + { + // run on main NTCP2 thread + KeyDerivationFunctionDataPhase (); + // Bob data phase keys + m_SendKey = m_Kba; + m_ReceiveKey = m_Kab; + SetSipKeys (m_Sipkeysba, m_Sipkeysab); + memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8); + memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8); + // we need to set keys for SendTerminationAndTerminate + // TODO: check flag + i2p::data::RouterInfo ri (buf->data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag + if (ri.IsUnreachable ()) + { + LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ()); + SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail); + return; + } + LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")"); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes + { + LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds"); + SendTerminationAndTerminate (eNTCP2Message3Error); + return; + } + if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes + { + LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds"); + SendTerminationAndTerminate (eNTCP2Message3Error); + return; + } + // update RouterInfo in netdb + auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now + if (!ri1) + { + LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb"); + Terminate (); + return; + } + + bool isOlder = false; + if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) + { + // received RouterInfo is older than one in netdb + isOlder = true; + if (ri1->HasProfile ()) + { + auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile + if (profile && profile->IsDuplicated ()) + { + SendTerminationAndTerminate (eNTCP2Banned); + return; + } + } + } + + auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () : + (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ()); + if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) + { + LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); + 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 + { + if (isOlder) // older router? + i2p::data::UpdateRouterProfile (ri1->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->Duplicated (); // mark router as duplicated in profile + }); + else + LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); + SendTerminationAndTerminate (eNTCP2Banned); + return; + } + // TODO: process options block + + // ready to communicate + SetRemoteIdentity (ri1->GetRouterIdentity ()); + if (m_Server.AddNTCP2Session (shared_from_this (), true)) + { + Established (); + ReceiveLength (); + } + else + Terminate (); + } + void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey) { #if OPENSSL_SIPHASH @@ -741,11 +907,17 @@ namespace transport void NTCP2Session::ClientLogin () { m_Establisher->CreateEphemeralKey (); - SendSessionRequest (); + boost::asio::post (m_Server.GetEstablisherService (), + [s = shared_from_this ()] () + { + s->SendSessionRequest (); + }); } void NTCP2Session::ServerLogin () { + SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT); + SetLastActivityTimestamp (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 (), @@ -789,14 +961,19 @@ namespace transport CreateNextReceivedBuffer (m_NextReceivedLen); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); - if (!ec && moreBytes >= m_NextReceivedLen) - { - // read and process message immediately if available - moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec); - HandleReceived (ec, moreBytes); - } + if (!ec) + { + if (moreBytes >= m_NextReceivedLen) + { + // read and process message immediately if available + moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec); + HandleReceived (ec, moreBytes); + } + else + Receive (); + } else - Receive (); + LogPrint (eLogWarning, "NTCP2: Socket error: ", ec.message ()); } else { @@ -822,18 +999,17 @@ namespace transport { if (ecode) { - if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ()); Terminate (); } else { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumReceivedBytes += bytes_transferred + 2; // + length - i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); + UpdateNumReceivedBytes (bytes_transferred + 2); + i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2); uint8_t nonce[12]; CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++; - if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false)) + if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen)) { LogPrint (eLogDebug, "NTCP2: Received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); @@ -858,7 +1034,7 @@ namespace transport auto size = bufbe16toh (frame + offset); offset += 2; LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size); - if (size > len) + if (offset + size > len) { LogPrint (eLogError, "NTCP2: Unexpected block length ", size); break; @@ -866,15 +1042,39 @@ 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; case eNTCP2BlkRouterInfo: { - LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); - i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size)); + LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); + if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1) + { + auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1); + if (newRi) + { + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) + // peer's RouterInfo update + SetRemoteIdentity (newRi->GetIdentity ()); + } + } + else + LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size); break; } case eNTCP2BlkI2NPMessage: @@ -991,9 +1191,14 @@ namespace transport macBuf = m_NextSendBuffer + paddingLen; totalLen += paddingLen; } + if (totalLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) + { + LogPrint (eLogError, "NTCP2: Frame to send is too long ", totalLen); + return; + } uint8_t nonce[12]; CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers + m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block // send buffers @@ -1015,10 +1220,16 @@ namespace transport delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; return; } + if (payloadLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) + { + LogPrint (eLogError, "NTCP2: Buffer to send is too long ", payloadLen); + delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; + return; + } // encrypt uint8_t nonce[12]; CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); + m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer); // send m_IsSending = true; @@ -1039,40 +1250,53 @@ namespace transport } else { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumSentBytes += bytes_transferred; + UpdateNumSentBytes (bytes_transferred); i2p::transport::transports.UpdateSentBytes (bytes_transferred); LogPrint (eLogDebug, "NTCP2: Next frame sent ", bytes_transferred); - if (m_LastActivityTimestamp > m_NextRouterInfoResendTime) + if (GetLastActivityTimestamp () > m_NextRouterInfoResendTime) { m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + - rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; SendRouterInfo (); } else + { SendQueue (); + SetSendQueueSize (m_SendQueue.size ()); + } } } void NTCP2Session::SendQueue () { - if (!m_SendQueue.empty ()) + if (!m_SendQueue.empty () && m_IsEstablished) { std::vector > msgs; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); size_t s = 0; while (!m_SendQueue.empty ()) { auto msg = m_SendQueue.front (); + if (!msg || msg->IsExpired (ts)) + { + // drop null or expired message + if (msg) msg->Drop (); + m_SendQueue.pop_front (); + continue; + } size_t len = msg->GetNTCP2Length (); if (s + len + 3 <= NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) // 3 bytes block header { msgs.push_back (msg); s += (len + 3); m_SendQueue.pop_front (); + if (s >= NTCP2_SEND_AFTER_FRAME_SIZE) + break; // send frame right a way } else if (len + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) { LogPrint (eLogError, "NTCP2: I2NP message of size ", len, " can't be sent. Dropped"); + msg->Drop (); m_SendQueue.pop_front (); } else @@ -1082,13 +1306,33 @@ namespace transport } } + void NTCP2Session::MoveSendQueue (std::shared_ptr other) + { + if (!other || m_SendQueue.empty ()) return; + std::list > msgs; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + for (auto it: m_SendQueue) + if (!it->IsExpired (ts)) + msgs.push_back (it); + else + it->Drop (); + m_SendQueue.clear (); + if (!msgs.empty ()) + other->SendI2NPMessages (msgs); + } + size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len) { if (len < 3) return 0; len -= 3; if (msgLen < 256) msgLen = 256; // for short message padding should not be always zero size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100; - if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; + if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) + { + int l = (int)NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; + if (l <= 0) return 0; + paddingSize = l; + } if (paddingSize > len) paddingSize = len; if (paddingSize) { @@ -1097,7 +1341,7 @@ namespace transport RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); m_NextPaddingSize = 0; } - paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; + paddingSize = m_PaddingSizes[m_NextPaddingSize++] % (paddingSize + 1); } buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size @@ -1108,13 +1352,19 @@ namespace transport void NTCP2Session::SendRouterInfo () { if (!IsEstablished ()) return; - auto riLen = i2p::context.GetRouterInfo ().GetBufferLen (); - size_t payloadLen = riLen + 4; // 3 bytes block header + 1 byte RI flag + auto riBuffer = i2p::context.CopyRouterInfoBuffer (); + auto riLen = riBuffer->GetBufferLen (); + 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, riBuffer->data (), riLen); // TODO: eliminate extra copy // padding block auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); payloadLen += paddingSize; @@ -1146,20 +1396,47 @@ namespace transport void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason) { SendTermination (reason); - m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go } - void NTCP2Session::SendI2NPMessages (const std::vector >& msgs) + void NTCP2Session::SendI2NPMessages (std::list >& msgs) { - m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs)); + if (m_IsTerminated || msgs.empty ()) + { + msgs.clear (); + return; + } + bool empty = false; + { + std::lock_guard l(m_IntermediateQueueMutex); + empty = m_IntermediateQueue.empty (); + m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); + } + if (empty) + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ())); } - void NTCP2Session::PostI2NPMessages (std::vector > msgs) + void NTCP2Session::PostI2NPMessages () { if (m_IsTerminated) return; - for (auto it: msgs) - m_SendQueue.push_back (it); - if (!m_IsSending) + std::list > msgs; + { + std::lock_guard l(m_IntermediateQueueMutex); + m_IntermediateQueue.swap (msgs); + } + bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2; + if (isSemiFull) + { + for (auto it: msgs) + if (it->onDrop) + it->Drop (); // drop earlier because we can handle it + else + m_SendQueue.push_back (std::move (it)); + } + else + m_SendQueue.splice (m_SendQueue.end (), msgs); + + if (!m_IsSending && m_IsEstablished) SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { @@ -1167,17 +1444,25 @@ namespace transport GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } + SetSendQueueSize (m_SendQueue.size ()); } void NTCP2Session::SendLocalRouterInfo (bool update) { - if (update || !IsOutgoing ()) // we send it in SessionConfirmed for ougoing session - m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); + if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); } + i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const + { + if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4; + return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6; + } + NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_ProxyType(eNoProxy), m_Resolver(GetService ()) + m_ProxyType(eNoProxy), m_Resolver(GetService ()), + m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) { } @@ -1188,6 +1473,7 @@ namespace transport void NTCP2Server::Start () { + m_EstablisherService.Start (); if (!IsRunning ()) { StartIOService (); @@ -1195,14 +1481,13 @@ namespace transport { LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers"); // TODO: resolve proxy until it is resolved - boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort)); boost::system::error_code e; - auto itr = m_Resolver.resolve(q, e); + auto itr = m_Resolver.resolve(m_ProxyAddress, std::to_string(m_ProxyPort), e); if(e) - LogPrint(eLogError, "NTCP2: Failed to resolve proxy ", e.message()); + LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message()); else { - m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr)); + m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ())); if (m_ProxyEndpoint) LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint); } @@ -1210,8 +1495,9 @@ namespace transport else LogPrint(eLogInfo, "NTCP2: Proxy is not used"); // start acceptors - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address: addresses) + auto addresses = context.GetRouterInfo ().GetAddresses (); + if (!addresses) return; + for (const auto& address: *addresses) { if (!address) continue; if (address->IsPublishedNTCP2 () && address->port) @@ -1226,7 +1512,7 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); + LogPrint(eLogCritical, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); continue; } @@ -1269,7 +1555,7 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: Failed to bind to v6 port ", address->port, ": ", ex.what()); + LogPrint(eLogCritical, "NTCP2: Failed to bind to v6 port ", address->port, ": ", ex.what()); ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); continue; } @@ -1282,13 +1568,14 @@ namespace transport void NTCP2Server::Stop () { + m_EstablisherService.Stop (); { // we have to copy it because Terminate changes m_NTCP2Sessions auto ntcpSessions = m_NTCP2Sessions; for (auto& it: ntcpSessions) it.second->Terminate (); for (auto& it: m_PendingIncomingSessions) - it->Terminate (); + it.second->Terminate (); } m_NTCP2Sessions.clear (); @@ -1304,27 +1591,44 @@ 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; + s->MoveSendQueue (session); + 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; } void NTCP2Server::RemoveNTCP2Session (std::shared_ptr session) { if (session && session->GetRemoteIdentity ()) - m_NTCP2Sessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); + { + auto it = m_NTCP2Sessions.find (session->GetRemoteIdentity ()->GetIdentHash ()); + if (it != m_NTCP2Sessions.end () && it->second == session) + m_NTCP2Sessions.erase (it); + } } std::shared_ptr NTCP2Server::FindNTCP2Session (const i2p::data::IdentHash& ident) @@ -1342,8 +1646,9 @@ namespace transport LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; } - LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint ()); - GetService ().post([this, conn]() + LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); + boost::asio::post (GetService (), [this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1398,33 +1703,47 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint ()); + LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); conn->ClientLogin (); } } 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::transport::transports.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; + } + } if (error != boost::asio::error::operation_aborted) { @@ -1439,27 +1758,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::transport::transports.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; + } + } 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)); } @@ -1467,7 +1806,8 @@ namespace transport void NTCP2Server::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP2_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds( + NTCP2_TERMINATION_CHECK_TIMEOUT + m_Rng () % NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE)); m_TerminationTimer.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -1490,18 +1830,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 + 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)); + } } } @@ -1513,7 +1869,7 @@ namespace transport LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; } - GetService().post([this, conn]() + boost::asio::post (GetService(), [this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1558,47 +1914,18 @@ namespace transport case eSocksProxy: { // TODO: support username/password auth etc - static const uint8_t buff[3] = {0x05, 0x01, 0x00}; - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), - [] (const boost::system::error_code & ec, std::size_t transferred) - { - (void) transferred; - if(ec) - { - LogPrint(eLogWarning, "NTCP2: SOCKS5 write error ", ec.message()); - } - }); - auto readbuff = std::make_shared >(2); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2), - [this, readbuff, timer, conn](const boost::system::error_code & ec, std::size_t transferred) - { - if(ec) - { - LogPrint(eLogError, "NTCP2: SOCKS5 read error ", ec.message()); - timer->cancel(); - conn->Terminate(); - return; - } - else if(transferred == 2) - { - if((*readbuff)[1] == 0x00) - { - AfterSocksHandshake(conn, timer); - return; - } - else if ((*readbuff)[1] == 0xff) - { - LogPrint(eLogError, "NTCP2: SOCKS5 proxy rejected authentication"); - timer->cancel(); - conn->Terminate(); - return; - } - LogPrint(eLogError, "NTCP2:", (int)(*readbuff)[1]); - } - LogPrint(eLogError, "NTCP2: SOCKS5 server gave invalid response"); + Socks5Handshake (conn->GetSocket(), conn->GetRemoteEndpoint (), + [conn, timer](const boost::system::error_code& ec) + { timer->cancel(); - conn->Terminate(); - }); + if (!ec) + conn->ClientLogin(); + else + { + LogPrint(eLogError, "NTCP2: SOCKS proxy handshake error ", ec.message()); + conn->Terminate(); + } + }); break; } case eHTTPProxy: @@ -1626,7 +1953,7 @@ namespace transport LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message()); }); - boost::asio::streambuf * readbuff = new boost::asio::streambuf; + auto readbuff = std::make_shared(); boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", [readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) { @@ -1640,13 +1967,12 @@ namespace transport { readbuff->commit(transferred); i2p::http::HTTPRes res; - if(res.parse(boost::asio::buffer_cast(readbuff->data()), readbuff->size()) > 0) + if(res.parse(std::string {boost::asio::buffers_begin(readbuff->data ()), boost::asio::buffers_begin(readbuff->data ()) + readbuff->size ()}) > 0) { if(res.code == 200) { timer->cancel(); conn->ClientLogin(); - delete readbuff; return; } else @@ -1656,7 +1982,6 @@ namespace transport LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response"); timer->cancel(); conn->Terminate(); - delete readbuff; } }); break; @@ -1666,69 +1991,6 @@ namespace transport } } - void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer) - { - // build request - size_t sz = 6; // header + port - auto buff = std::make_shared >(256); - auto readbuff = std::make_shared >(256); - (*buff)[0] = 0x05; - (*buff)[1] = 0x01; - (*buff)[2] = 0x00; - - auto& ep = conn->GetRemoteEndpoint (); - if(ep.address ().is_v4 ()) - { - (*buff)[3] = 0x01; - auto addrbytes = ep.address ().to_v4().to_bytes(); - sz += 4; - memcpy(buff->data () + 4, addrbytes.data(), 4); - } - else if (ep.address ().is_v6 ()) - { - (*buff)[3] = 0x04; - auto addrbytes = ep.address ().to_v6().to_bytes(); - sz += 16; - memcpy(buff->data () + 4, addrbytes.data(), 16); - } - else - { - // We mustn't really fall here because all connections are made to IP addresses - LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy"); - return; - } - htobe16buf(buff->data () + sz - 2, ep.port ()); - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), - [buff](const boost::system::error_code & ec, std::size_t written) - { - if(ec) - { - LogPrint(eLogError, "NTCP2: Failed to write handshake to socks proxy ", ec.message()); - return; - } - }); - - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), - [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) - { - if(e) - { - LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message()); - } - else if(transferred == sz) - { - if((*readbuff)[1] == 0x00) - { - timer->cancel(); - conn->ClientLogin(); - return; - } - } - timer->cancel(); - conn->Terminate(); - }); - } - void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) { auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); @@ -1742,5 +2004,17 @@ namespace transport else m_Address4 = addr; } + + void NTCP2Server::AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac) + { + return m_Encryptor.Encrypt (bufs, key, nonce, mac); + } + + bool NTCP2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 754f5a6d..5ad5b955 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,14 +29,17 @@ namespace transport { const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519; + const size_t NTCP2_SEND_AFTER_FRAME_SIZE = 16386; // send frame when exceeds this size const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287; const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287; const int NTCP2_MAX_PADDING_RATIO = 6; // in % const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds - const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes - const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int NTCP2_TERMINATION_TIMEOUT = 115; // 2 minutes - 5 seconds + const int NTCP2_TERMINATION_TIMEOUT_VARIANCE = 10; // 10 seconds + const int NTCP2_TERMINATION_CHECK_TIMEOUT = 28; // 28 seconds + const int NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // 5 seconds const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes @@ -87,30 +91,29 @@ namespace transport const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set - const uint8_t * GetK () const { return m_CK + 32; }; const uint8_t * GetCK () const { return m_CK; }; const uint8_t * GetH () const { return m_H; }; - void KDF1Alice (); - void KDF1Bob (); - void KDF2Alice (); - void KDF2Bob (); - void KDF3Alice (); // for SessionConfirmed part 2 - void KDF3Bob (); + bool KDF1Alice (); + bool KDF1Bob (); + bool KDF2Alice (); + bool KDF2Bob (); + bool KDF3Alice (); // for SessionConfirmed part 2 + bool KDF3Bob (); - void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH - void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate + bool KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH + bool KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate void CreateEphemeralKey (); - void CreateSessionRequestMessage (); - void CreateSessionCreatedMessage (); - void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce); - void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce); + bool CreateSessionRequestMessage (std::mt19937& rng); + bool CreateSessionCreatedMessage (std::mt19937& rng); + bool CreateSessionConfirmedMessagePart1 (); + bool CreateSessionConfirmedMessagePart2 (); bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); bool ProcessSessionCreatedMessage (uint16_t& paddingLen); - bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce); - bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf); + bool ProcessSessionConfirmedMessagePart1 (); + bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf); std::shared_ptr m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 @@ -134,23 +137,25 @@ 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; }; + i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; 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 (std::list >& msgs) override; + void MoveSendQueue (std::shared_ptr other); + private: void Established (); @@ -167,13 +172,17 @@ namespace transport void HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void ProcessSessionRequest (size_t len); void HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void ProcessSessionCreated (size_t len); void HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - + void ProcessSessionConfirmed (); + void EstablishSessionAfterSessionConfirmed (std::shared_ptr > buf, size_t size); + // data void ReceiveLength (); void HandleReceivedLength (const boost::system::error_code& ecode, std::size_t bytes_transferred); @@ -191,7 +200,7 @@ namespace transport void SendRouterInfo (); void SendTermination (NTCP2TerminationReason reason); void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void PostI2NPMessages (std::vector > msgs); + void PostI2NPMessages (); private: @@ -224,13 +233,28 @@ namespace transport bool m_IsSending, m_IsReceiving; std::list > m_SendQueue; uint64_t m_NextRouterInfoResendTime; // seconds since epoch - + + std::list > m_IntermediateQueue; // from transports + mutable std::mutex m_IntermediateQueueMutex; + uint16_t m_PaddingSizes[16]; int m_NextPaddingSize; }; class NTCP2Server: private i2p::util::RunnableServiceWithWork { + private: + + class EstablisherService: public i2p::util::RunnableServiceWithWork + { + public: + + EstablisherService (): RunnableServiceWithWork ("NTCP2e") {}; + auto& GetService () { return GetIOService (); }; + void Start () { StartIOService (); }; + void Stop () { StopIOService (); }; + }; + public: enum ProxyType @@ -239,13 +263,20 @@ namespace transport eSocksProxy, eHTTPProxy }; - + NTCP2Server (); ~NTCP2Server (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return GetIOService (); }; + auto& GetService () { return GetIOService (); }; + auto& GetEstablisherService () { return m_EstablisherService.GetService (); }; + std::mt19937& GetRng () { return m_Rng; }; + void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, + const uint8_t * key, const uint8_t * nonce, uint8_t * mac); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AddNTCP2Session (std::shared_ptr session, bool incoming = false); void RemoveNTCP2Session (std::shared_ptr session); @@ -266,8 +297,7 @@ namespace transport void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); - void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer); - + // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); @@ -277,15 +307,20 @@ 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; uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; + std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; - + std::mt19937 m_Rng; + EstablisherService m_EstablisherService; + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + public: // for HTTP/I2PControl diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 7f31fc7d..e53738e5 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -36,7 +37,9 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), m_HiddenMode(false) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), + m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), + m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) { } @@ -54,42 +57,49 @@ namespace data m_Families.LoadCertificates (); Load (); + if (!m_Requests) + { + m_Requests = std::make_shared(); + m_Requests->Start (); + } + uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold); - if (m_RouterInfos.size () < threshold || m_Floodfills.size () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils + if (m_RouterInfos.size () < threshold || m_Floodfills.GetSize () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils { Reseed (); } - else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false)) + else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false)) Reseed (); // we don't have a router we can connect to. Trying to reseed auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); if (it != m_RouterInfos.end ()) { // remove own router - m_Floodfills.remove (it->second); + m_Floodfills.Remove (it->second->GetIdentHash ()); m_RouterInfos.erase (it); } // insert own router m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); if (i2p::context.IsFloodfill ()) - m_Floodfills.push_back (i2p::context.GetSharedRouterInfo ()); + m_Floodfills.Insert (i2p::context.GetSharedRouterInfo ()); i2p::config::GetOption("persist.profiles", m_PersistProfiles); - + m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); } void NetDb::Stop () { + if (m_Requests) + m_Requests->Stop (); if (m_IsRunning) { if (m_PersistProfiles) - for (auto& it: m_RouterInfos) - it.second->SaveProfile (); + SaveProfiles (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); - m_Floodfills.clear (); + m_Floodfills.Clear (); if (m_Thread) { m_IsRunning = false; @@ -99,133 +109,109 @@ namespace data m_Thread = 0; } m_LeaseSets.clear(); - m_Requests.Stop (); } + m_Requests = nullptr; } void NetDb::Run () { i2p::util::SetThreadName("NetDB"); - uint64_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; - uint64_t lastProfilesCleanup = i2p::util::GetSecondsSinceEpoch (); - int16_t profilesCleanupVariance = 0; + uint64_t lastManage = 0; + uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), + lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup; + int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0; + std::list > msgs; while (m_IsRunning) { try { - auto msg = m_Queue.GetNextWithTimeout (15000); // 15 sec - if (msg) + if (m_Queue.Wait (1,0)) // 1 sec { - int numMsgs = 0; - while (msg) + m_Queue.GetWholeQueue (msgs); + while (!msgs.empty ()) { + auto msg = msgs.front (); msgs.pop_front (); + if (!msg) continue; LogPrint(eLogDebug, "NetDb: Got request with type ", (int) msg->GetTypeID ()); switch (msg->GetTypeID ()) { case eI2NPDatabaseStore: HandleDatabaseStoreMsg (msg); break; - case eI2NPDatabaseSearchReply: - HandleDatabaseSearchReplyMsg (msg); - break; case eI2NPDatabaseLookup: HandleDatabaseLookupMsg (msg); break; - case eI2NPDeliveryStatus: - HandleDeliveryStatusMsg (msg); - break; - case eI2NPDummyMsg: - // plain RouterInfo from NTCP2 with flags for now - HandleNTCP2RouterInfoMsg (msg); - break; default: // WTF? LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ()); //i2p::HandleI2NPMessage (msg); } - if (numMsgs > 100) break; - msg = m_Queue.Get (); - numMsgs++; } } if (!m_IsRunning) break; - if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline + if (!i2p::transport::transports.IsOnline () || !i2p::transport::transports.IsRunning ()) + continue; // don't manage netdb when offline or transports are not running - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastManageRequest >= 15) // manage requests every 15 seconds + uint64_t mts = i2p::util::GetMonotonicMilliseconds (); + if (mts >= lastManage + 60000) // manage routers and leasesets every minute { - m_Requests.ManageRequests (); - lastManageRequest = ts; - } - - if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute - { - if (lastSave) + if (lastManage) { - SaveUpdated (); + ManageRouterInfos (); ManageLeaseSets (); } - lastSave = ts; + lastManage = mts; } - if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) + if (mts >= lastProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)*1000) { - i2p::context.CleanupDestination (); - lastDestinationCleanup = ts; + m_RouterProfilesPool.CleanUpMt (); + if (m_PersistProfiles) + { + bool isSaving = m_SavingProfiles.valid (); + if (isSaving && m_SavingProfiles.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + { + m_SavingProfiles.get (); + isSaving = false; + } + if (!isSaving) + m_SavingProfiles = PersistProfiles (); + else + LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk"); + } + lastProfilesCleanup = mts; + profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE; } - if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)) + if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) { - DeleteObsoleteProfiles (); - lastProfilesCleanup = ts; - profilesCleanupVariance = (rand () % (2 * i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE) - i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE); - } - - // publish - if (!m_HiddenMode && i2p::transport::transports.IsOnline ()) + bool isDeleting = m_DeletingProfiles.valid (); + if (isDeleting && m_DeletingProfiles.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + { + m_DeletingProfiles.get (); + isDeleting = false; + } + if (!isDeleting) + m_DeletingProfiles = DeleteObsoleteProfiles (); + else + LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk"); + lastObsoleteProfilesCleanup = mts; + obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; + } + if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance) { - bool publish = false; - if (m_PublishReplyToken) + bool isApplying = m_ApplyingProfileUpdates.valid (); + if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? { - // next publishing attempt - if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; - } - else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) - { - // new publish - m_PublishExcluded.clear (); - if (i2p::context.IsFloodfill ()) - m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // do publish to ourselves - publish = true; - } - if (publish) // update timestamp and publish - { - i2p::context.UpdateTimestamp (ts); - Publish (); - lastPublish = ts; - } - } - - if (ts - lastExploratory >= 30) // exploratory every 30 seconds - { - auto numRouters = m_RouterInfos.size (); - if (!numRouters) - throw std::runtime_error("No known routers, reseed seems to be totally failed"); - else // we have peers now - m_FloodfillBootstrap = nullptr; - if (numRouters < 2500 || ts - lastExploratory >= 90) - { - numRouters = 800/numRouters; - if (numRouters < 1) numRouters = 1; - if (numRouters > 9) numRouters = 9; - m_Requests.ManageRequests (); - if(!m_HiddenMode) - Explore (numRouters); - lastExploratory = ts; - } - } + m_ApplyingProfileUpdates.get (); + isApplying = false; + } + if (!isApplying) + m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates (); + lastApplyingProfileUpdates = mts; + applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE; + } } catch (std::exception& ex) { @@ -234,12 +220,6 @@ namespace data } } - void NetDb::SetHidden(bool hide) - { - // TODO: remove reachable addresses from router info - m_HiddenMode = hide; - } - std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len) { bool updated; @@ -258,7 +238,8 @@ namespace data bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) { bool updated; - AddRouterInfo (ident, buf, len, updated); + if (!AddRouterInfo (ident, buf, len, updated)) + updated = false; return updated; } @@ -272,43 +253,85 @@ namespace data { bool wasFloodfill = r->IsFloodfill (); { - std::unique_lock l(m_RouterInfosMutex); - r->Update (buf, len); - } - LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + std::lock_guard l(m_RouterInfosMutex); + if (!r->Update (buf, len)) + { + updated = false; + m_Requests->RequestComplete (ident, r); + return r; + } + if (r->IsUnreachable () || + i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ()) + { + // delete router as invalid or from future after update + m_RouterInfos.erase (ident); + if (wasFloodfill) + { + std::lock_guard l(m_FloodfillsMutex); + m_Floodfills.Remove (r->GetIdentHash ()); + } + m_Requests->RequestComplete (ident, nullptr); + return nullptr; + } + } + if (CheckLogLevel (eLogInfo)) + LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated { - LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); - std::unique_lock l(m_FloodfillsMutex); + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); + std::lock_guard l(m_FloodfillsMutex); if (wasFloodfill) - m_Floodfills.remove (r); + m_Floodfills.Remove (r->GetIdentHash ()); else if (r->IsEligibleFloodfill ()) - m_Floodfills.push_back (r); + { + if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || r->GetProfile ()->IsReal ()) + m_Floodfills.Insert (r); + else + r->ResetFloodfill (); + } } } else { - LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); + r->CancelBufferToDelete (); // since an update received + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); updated = false; } } else { r = std::make_shared (buf, len); - if (!r->IsUnreachable () && r->HasValidAddresses ()) + bool isValid = !r->IsUnreachable () && r->HasValidAddresses () && (!r->IsFloodfill () || !r->GetProfile ()->IsUnreachable ()); + if (isValid) + { + auto mts = i2p::util::GetMillisecondsSinceEpoch (); + isValid = mts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp () && // from future + (mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL || // too old + context.GetUptime () < NETDB_CHECK_FOR_EXPIRATION_UPTIME/10); // enough uptime + } + if (isValid) { bool inserted = false; { - std::unique_lock l(m_RouterInfosMutex); + std::lock_guard l(m_RouterInfosMutex); inserted = m_RouterInfos.insert ({r->GetIdentHash (), r}).second; } if (inserted) { - LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); + if (CheckLogLevel (eLogInfo)) + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); if (r->IsFloodfill () && r->IsEligibleFloodfill ()) { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.push_back (r); + if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || + r->GetProfile ()->IsReal ()) // don't insert floodfill until it's known real if we have enough + { + std::lock_guard l(m_FloodfillsMutex); + m_Floodfills.Insert (r); + } + else + r->ResetFloodfill (); } } else @@ -321,13 +344,13 @@ namespace data updated = false; } // take care about requested destination - m_Requests.RequestComplete (ident, r); + m_Requests->RequestComplete (ident, r); return r; } bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len) { - std::unique_lock lock(m_LeaseSetsMutex); + std::lock_guard lock(m_LeaseSetsMutex); bool updated = false; auto it = m_LeaseSets.find(ident); if (it != m_LeaseSets.end () && it->second->GetStoreType () == i2p::data::NETDB_STORE_TYPE_LEASESET) @@ -339,10 +362,11 @@ namespace data if(it->second->GetExpirationTime() < expires) { it->second->Update (buf, len, false); // signature is verified already - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + if (CheckLogLevel (eLogInfo)) + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); updated = true; } - else + else if (CheckLogLevel (eLogDebug)) LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); } else @@ -353,7 +377,8 @@ namespace data auto leaseSet = std::make_shared (buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { - LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); + if (CheckLogLevel (eLogInfo)) + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; updated = true; } @@ -365,10 +390,10 @@ 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::lock_guard lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find(ident); if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) @@ -376,13 +401,14 @@ namespace data if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) { // TODO: implement actual update - LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); + if (CheckLogLevel (eLogInfo)) + LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; return true; } 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); } } @@ -394,7 +420,7 @@ namespace data std::shared_ptr NetDb::FindRouter (const IdentHash& ident) const { - std::unique_lock l(m_RouterInfosMutex); + std::lock_guard l(m_RouterInfosMutex); auto it = m_RouterInfos.find (ident); if (it != m_RouterInfos.end ()) return it->second; @@ -404,7 +430,7 @@ namespace data std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { - std::unique_lock lock(m_LeaseSetsMutex); + std::lock_guard lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) return it->second; @@ -423,9 +449,34 @@ namespace data void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable) { - auto it = m_RouterInfos.find (ident); - if (it != m_RouterInfos.end ()) - return it->second->SetUnreachable (unreachable); + auto r = FindRouter (ident); + if (r) + { + r->SetUnreachable (unreachable); + auto profile = r->GetProfile (); + if (profile) + { + profile->Unreachable (unreachable); + if (!unreachable && r->IsDeclaredFloodfill () && !r->IsFloodfill () && + r->IsEligibleFloodfill () && profile->IsReal ()) + { + // enable previously disabled floodfill + r->SetFloodfill (); + std::lock_guard l(m_FloodfillsMutex); + m_Floodfills.Insert (r); + } + } + } + } + + void NetDb::ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports) + { + auto r = FindRouter (ident); + if (r) + { + std::lock_guard l(m_RouterInfosMutex); + r->ExcludeReachableTransports (transports); + } } void NetDb::Reseed () @@ -436,35 +487,13 @@ namespace data m_Reseeder->LoadCertificates (); // we need certificates for SU3 verification } - // try reseeding from floodfill first if specified - std::string riPath; - if(i2p::config::GetOption("reseed.floodfill", riPath)) - { - auto ri = std::make_shared(riPath); - if (ri->IsFloodfill()) - { - const uint8_t * riData = ri->GetBuffer(); - int riLen = ri->GetBufferLen(); - if (!i2p::data::netdb.AddRouterInfo(riData, riLen)) - { - // bad router info - LogPrint(eLogError, "NetDb: Bad router info"); - return; - } - m_FloodfillBootstrap = ri; - ReseedFromFloodfill(*ri); - // don't try reseed servers if trying to bootstrap from floodfill - return; - } - } - m_Reseeder->Bootstrap (); } void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills) { LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64()); - std::vector > requests; + std::list > requests; i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); i2p::data::IdentHash ih = ri.GetIdentHash(); @@ -487,20 +516,20 @@ namespace data } // send them off - i2p::transport::transports.SendMessages(ih, requests); + i2p::transport::transports.SendMessages(ih, std::move (requests)); } bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts) { 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) { if (r->IsFloodfill () && r->IsEligibleFloodfill ()) - m_Floodfills.push_back (r); + m_Floodfills.Insert (r); } } else @@ -513,7 +542,7 @@ namespace data void NetDb::VisitLeaseSets(LeaseSetVisitor v) { - std::unique_lock lock(m_LeaseSetsMutex); + std::lock_guard lock(m_LeaseSetsMutex); for ( auto & entry : m_LeaseSets) v(entry.first, entry.second); } @@ -529,7 +558,7 @@ namespace data void NetDb::VisitRouterInfos(RouterInfoVisitor v) { - std::unique_lock lock(m_RouterInfosMutex); + std::lock_guard lock(m_RouterInfosMutex); for ( const auto & item : m_RouterInfos ) v(item.second); } @@ -541,8 +570,8 @@ namespace data size_t iters = max_iters_per_cyle; while(n > 0) { - std::unique_lock lock(m_RouterInfosMutex); - uint32_t idx = rand () % m_RouterInfos.size (); + std::lock_guard lock(m_RouterInfosMutex); + uint32_t idx = m_Rng () % m_RouterInfos.size (); uint32_t i = 0; for (const auto & it : m_RouterInfos) { if(i >= idx) // are we at the random start point? @@ -584,7 +613,7 @@ namespace data { // make sure we cleanup netDb from previous attempts m_RouterInfos.clear (); - m_Floodfills.clear (); + m_Floodfills.Clear (); uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); std::vector files; @@ -592,62 +621,125 @@ namespace data for (const auto& path : files) LoadRouterInfo (path, ts); - LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " floodfils)"); + LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.GetSize (), " floodfils)"); } void NetDb::SaveUpdated () { + if (m_PersistingRouters.valid ()) + { + if (m_PersistingRouters.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + m_PersistingRouters.get (); + else + { + LogPrint (eLogWarning, "NetDb: Can't save updated routers. Routers are being saved to disk"); + return; + } + } + int updatedCount = 0, deletedCount = 0, deletedFloodfillsCount = 0; auto total = m_RouterInfos.size (); - auto totalFloodfills = m_Floodfills.size (); + auto totalFloodfills = m_Floodfills.GetSize (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); auto uptime = i2p::context.GetUptime (); + double minTunnelCreationSuccessRate; + i2p::config::GetOption("limits.zombies", minTunnelCreationSuccessRate); + bool isLowRate = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < minTunnelCreationSuccessRate; // routers don't expire if less than 90 or uptime is less than 1 hour - bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes - if (checkForExpiration && uptime > 3600) // 1 hour + bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // 10 minutes + if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; - + bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no transports + + std::list > > saveToDisk; + std::list removeFromDisk; + auto own = i2p::context.GetSharedRouterInfo (); - for (auto& it: m_RouterInfos) + for (auto [ident, r]: m_RouterInfos) { - if (it.second == own) continue; // skip own - std::string ident = it.second->GetIdentHashBase64(); - if (it.second->IsUpdated ()) + if (!r || r == own) continue; // skip own + if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete { - it.second->SaveToFile (m_Storage.Path(ident)); - it.second->SetUpdated (false); - it.second->SetUnreachable (false); - it.second->DeleteBuffer (); + std::lock_guard l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update + r->DeleteBuffer (); + } + if (r->IsUpdated ()) + { + if (r->GetBuffer () && !r->IsUnreachable ()) + { + // we have something to save + std::shared_ptr buffer; + { + std::lock_guard l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update + buffer = r->CopyBuffer (); + } + if (!i2p::transport::transports.IsConnected (ident)) + r->ScheduleBufferToDelete (); + if (buffer) + saveToDisk.emplace_back(ident.ToBase64 (), buffer); + } + r->SetUpdated (false); updatedCount++; continue; } + else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) + // since update was long time ago we assume that router is not connected anymore + r->ScheduleBufferToDelete (); + + if (r->HasProfile () && r->GetProfile ()->IsUnreachable ()) + r->SetUnreachable (true); // make router reachable back if too few routers or floodfills - if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || - (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) - it.second->SetUnreachable (false); - // find & mark expired routers - if (!it.second->IsReachable () && it.second->IsSSU (false)) + if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline || + (r->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) + r->SetUnreachable (false); + if (!r->IsUnreachable ()) { - if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) - // RouterInfo expires after 1 hour if uses introducer - it.second->SetUnreachable (true); + // find & mark expired routers + if (!r->GetCompatibleTransports (true)) // non reachable by any transport + r->SetUnreachable (true); + else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ()) + { + LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (r->GetTimestamp () - ts)/1000LL, " seconds"); + r->SetUnreachable (true); + } + else if (checkForExpiration) + { + if (ts > r->GetTimestamp () + expirationTimeout) + r->SetUnreachable (true); + else if ((ts > r->GetTimestamp () + expirationTimeout/2) && // more than half of expiration + total > NETDB_NUM_ROUTERS_THRESHOLD && !r->IsHighBandwidth() && // low bandwidth + !r->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill + (CreateRoutingKey (ident) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits + r->SetUnreachable (true); + } } - else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) - it.second->SetUnreachable (true); - - if (it.second->IsUnreachable ()) + // make router reachable back if connected now or trusted router + if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) || + i2p::transport::transports.IsTrustedRouter (ident))) + r->SetUnreachable (false); + + if (r->IsUnreachable ()) { - if (it.second->IsFloodfill ()) deletedFloodfillsCount++; + if (r->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file - m_Storage.Remove(ident); + removeFromDisk.emplace_back (ident.ToBase64()); deletedCount++; if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; } } // m_RouterInfos iteration + if (!saveToDisk.empty () || !removeFromDisk.empty ()) + { + m_PersistingRouters = std::async (std::launch::async, &NetDb::PersistRouters, + this, std::move (saveToDisk), std::move (removeFromDisk)); + } + m_RouterInfoBuffersPool.CleanUpMt (); + m_RouterInfoAddressesPool.CleanUpMt (); + m_RouterInfoAddressVectorsPool.CleanUpMt (); + m_IdentitiesPool.CleanUpMt (); if (updatedCount > 0) LogPrint (eLogInfo, "NetDb: Saved ", updatedCount, " new/updated routers"); @@ -656,80 +748,46 @@ namespace data LogPrint (eLogInfo, "NetDb: Deleting ", deletedCount, " unreachable routers"); // clean up RouterInfos table { - std::unique_lock l(m_RouterInfosMutex); + std::lock_guard l(m_RouterInfosMutex); for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { - if (it->second->IsUnreachable ()) - { - if (m_PersistProfiles) it->second->SaveProfile (); + if (!it->second || it->second->IsUnreachable ()) it = m_RouterInfos.erase (it); - continue; + else + { + it->second->DropProfile (); + it++; } - ++it; } } // clean up expired floodfills or not floodfills anymore { - std::unique_lock l(m_FloodfillsMutex); - for (auto it = m_Floodfills.begin (); it != m_Floodfills.end ();) - if ((*it)->IsUnreachable () || !(*it)->IsFloodfill ()) - it = m_Floodfills.erase (it); - else - ++it; + std::lock_guard l(m_FloodfillsMutex); + m_Floodfills.Cleanup ([](const std::shared_ptr& r)->bool + { + return r && r->IsFloodfill () && !r->IsUnreachable (); + }); } } } + void NetDb::PersistRouters (std::list > >&& update, + std::list&& remove) + { + for (auto it: update) + RouterInfo::SaveToFile (m_Storage.Path(it.first), it.second); + for (auto it: remove) + m_Storage.Remove (it); + } + void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct) { - auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory - if (!dest) - { - LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already"); - return; - } - - auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); - if (floodfill) - { - if (direct && !floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && - !i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) - direct = false; // floodfill can't be reached directly - if (direct) - transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); - else - { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; - auto inbound = pool ? pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; - if (outbound && inbound) - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, dest->CreateRequestMessage (floodfill, inbound)); - else - { - LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no tunnels found"); - m_Requests.RequestComplete (destination, nullptr); - } - } - } + if (direct && (i2p::transport::transports.RoutesRestricted () || i2p::context.IsLimitedConnectivity ())) + direct = false; // always use tunnels for restricted routes or limited connectivity + if (m_Requests) + m_Requests->PostRequestDestination (destination, requestComplete, direct); else - { - LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no floodfills found"); - m_Requests.RequestComplete (destination, nullptr); - } - } - - void NetDb::RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete) - { - - auto dest = m_Requests.CreateRequest (destination, exploritory, requestComplete); // non-exploratory - if (!dest) - { - LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already"); - return; - } - LogPrint(eLogInfo, "NetDb: Destination ", destination.ToBase64(), " being requested directly from ", from.ToBase64()); - // direct - transports.SendMessage (from, dest->CreateRequestMessage (nullptr, nullptr)); + LogPrint (eLogError, "NetDb: Requests is null"); } void NetDb::HandleNTCP2RouterInfoMsg (std::shared_ptr m) @@ -748,6 +806,11 @@ namespace data { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); + if (len < DATABASE_STORE_HEADER_SIZE) + { + LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped"); + return; + } IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); if (ident.IsZero ()) { @@ -758,19 +821,41 @@ namespace data size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { - auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); + if (len < offset + 36) // 32 + 4 + { + LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped"); + return; + } uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; - if (!tunnelID) // send response directly - transports.SendMessage (buf + offset, deliveryStatus); - else + if (replyToken != 0xFFFFFFFFU) // if not caught on OBEP or IBGW { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - if (outbound) - outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); + IdentHash replyIdent(buf + offset); + auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); + if (!tunnelID) // send response directly + transports.SendMessage (replyIdent, deliveryStatus); else - LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found"); + { + bool direct = true; + if (!i2p::transport::transports.IsConnected (replyIdent)) + { + auto r = FindRouter (replyIdent); + if (r && !r->IsReachableFrom (i2p::context.GetRouterInfo ())) + direct = false; + } + if (direct) // send response directly to IBGW + transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (tunnelID, deliveryStatus)); + else + { + // send response through exploratory tunnel + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsgTo (replyIdent, tunnelID, deliveryStatus); + else + LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found"); + } + } } offset += 32; } @@ -786,23 +871,36 @@ namespace data uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET]; if (storeType) // LeaseSet or LeaseSet2 { - if (!m->from) // unsolicited LS must be received directly + if (len > MAX_LS_BUFFER_SIZE + offset) + { + LogPrint (eLogError, "NetDb: Database store message is too long ", len); + return; + } + if (!context.IsFloodfill ()) + { + LogPrint (eLogInfo, "NetDb: Not Floodfill, LeaseSet store request ignored for ", ident.ToBase32()); + return; + } + else if (!m->from) // unsolicited LS must be received directly { if (storeType == NETDB_STORE_TYPE_LEASESET) // 1 { - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); updated = AddLeaseSet (ident, buf + offset, len - offset); } else // all others are considered as LeaseSet2 { - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", storeType, " for ", ident.ToBase32()); + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32()); updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType); } } } else // RouterInfo { - LogPrint (eLogDebug, "NetDb: Store request: RouterInfo"); + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDb: Store request: RouterInfo ", ident.ToBase64()); size_t size = bufbe16toh (buf + offset); offset += 2; if (size > MAX_RI_BUFFER_SIZE || size > len - offset) @@ -834,89 +932,16 @@ namespace data { memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + payloadOffset, msgLen); floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); - Flood (ident, floodMsg); + int minutesBeforeMidnight = 24*60 - i2p::util::GetMinutesSinceEpoch () % (24*60); + bool andNextDay = storeType ? minutesBeforeMidnight < NETDB_NEXT_DAY_LEASESET_THRESHOLD: + minutesBeforeMidnight < NETDB_NEXT_DAY_ROUTER_INFO_THRESHOLD; + Flood (ident, floodMsg, andNextDay); } else LogPrint (eLogError, "NetDb: Database store message is too long ", floodMsg->len); } } - - void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) - { - const uint8_t * buf = msg->GetPayload (); - char key[48]; - int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); - key[l] = 0; - int num = buf[32]; // num - LogPrint (eLogDebug, "NetDb: DatabaseSearchReply for ", key, " num=", num); - IdentHash ident (buf); - auto dest = m_Requests.FindRequest (ident); - if (dest) - { - bool deleteDest = true; - if (num > 0) - { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; - if (!dest->IsExploratory ()) - { - // reply to our destination. Try other floodfills - if (outbound && inbound) - { - auto count = dest->GetExcludedPeers ().size (); - if (count < 7) - { - auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); - if (nextFloodfill) - { - // request destination - LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); - outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (nextFloodfill, inbound)); - deleteDest = false; - } - } - else - LogPrint (eLogWarning, "NetDb: ", key, " was not found on ", count, " floodfills"); - } - } - - if (deleteDest) - // no more requests for the destinationation. delete it - m_Requests.RequestComplete (ident, nullptr); - } - else - // no more requests for destination possible. delete it - m_Requests.RequestComplete (ident, nullptr); - } - else if(!m_FloodfillBootstrap) - LogPrint (eLogWarning, "NetDb: Requested destination for ", key, " not found"); - - // try responses - for (int i = 0; i < num; i++) - { - const uint8_t * router = buf + 33 + i*32; - char peerHash[48]; - int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48); - peerHash[l1] = 0; - LogPrint (eLogDebug, "NetDb: ", i, ": ", peerHash); - - auto r = FindRouter (router); - if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) - { - // router with ident not found or too old (1 hour) - LogPrint (eLogDebug, "NetDb: Found new/outdated router. Requesting RouterInfo..."); - if(m_FloodfillBootstrap) - RequestDestinationFrom(router, m_FloodfillBootstrap->GetIdentHash(), true); - else - RequestDestination (router); - } - else - LogPrint (eLogDebug, "NetDb: [:|||:]"); - } - } - + void NetDb::HandleDatabaseLookupMsg (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); @@ -926,14 +951,13 @@ namespace data LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored"); return; } - char key[48]; - int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); - key[l] = 0; + std::string key; + if (CheckLogLevel (eLogInfo)) + key = i2p::data::ByteStreamToBase64 (buf, 32); IdentHash replyIdent(buf + 32); uint8_t flag = buf[64]; - LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; const uint8_t * excluded = buf + 65; @@ -954,37 +978,33 @@ namespace data std::shared_ptr replyMsg; if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { + if (!context.IsFloodfill ()) + { + LogPrint (eLogWarning, "NetDb: Exploratory lookup to non-floodfill dropped"); + return; + } LogPrint (eLogInfo, "NetDb: Exploratory close to ", key, " ", numExcluded, " excluded"); - std::set excludedRouters; + std::unordered_set excludedRouters; const uint8_t * excluded_ident = excluded; for (int i = 0; i < numExcluded; i++) { excludedRouters.insert (excluded_ident); excluded_ident += 32; } - std::vector routers; - for (int i = 0; i < 3; i++) - { - auto r = GetClosestNonFloodfill (ident, excludedRouters); - if (r) - { - routers.push_back (r->GetIdentHash ()); - excludedRouters.insert (r->GetIdentHash ()); - } - } - replyMsg = CreateDatabaseSearchReply (ident, routers); + replyMsg = CreateDatabaseSearchReply (ident, GetExploratoryNonFloodfill (ident, + NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES, excludedRouters)); } else { if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) { + // try to find router 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); } } @@ -992,31 +1012,40 @@ namespace data if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { - auto leaseSet = FindLeaseSet (ident); - if (!leaseSet) + // try to find leaseset + if (context.IsFloodfill ()) + { + auto leaseSet = FindLeaseSet (ident); + if (!leaseSet) + { + // no leaseset found + LogPrint(eLogDebug, "NetDb: Requested LeaseSet not found for ", ident.ToBase32()); + } + else if (!leaseSet->IsExpired ()) // we don't send back expired leasesets + { + LogPrint (eLogDebug, "NetDb: Requested LeaseSet ", key, " found"); + replyMsg = CreateDatabaseStoreMsg (ident, leaseSet); + } + } + else if (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP) { - // no lease set found - LogPrint(eLogDebug, "NetDb: Requested LeaseSet not found for ", ident.ToBase32()); - } - else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets - { - LogPrint (eLogDebug, "NetDb: Requested LeaseSet ", key, " found"); - replyMsg = CreateDatabaseStoreMsg (ident, leaseSet); - } + LogPrint (eLogWarning, "NetDb: Explicit LeaseSet lookup to non-floodfill dropped"); + return; + } } if (!replyMsg) { - std::set excludedRouters; + std::unordered_set excludedRouters; const uint8_t * exclude_ident = excluded; for (int i = 0; i < numExcluded; i++) { excludedRouters.insert (exclude_ident); exclude_ident += 32; } - auto closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); + auto closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, false); if (closestFloodfills.empty ()) - LogPrint (eLogWarning, "NetDb: Requested ", key, " not found, ", numExcluded, " peers excluded"); + LogPrint (eLogWarning, "NetDb: No more floodfills for ", key, " found. ", numExcluded, " peers excluded"); replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); } } @@ -1050,131 +1079,67 @@ namespace data else LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); } - auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; - if (outbound) - outbound->SendTunnelDataMsg (replyIdent, replyTunnelID, replyMsg); - else + bool direct = true; + if (!i2p::transport::transports.IsConnected (replyIdent)) + { + auto r = FindRouter (replyIdent); + if (r && !r->IsReachableFrom (i2p::context.GetRouterInfo ())) + direct = false; + } + if (direct) transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); + else + { + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsgTo (replyIdent, replyTunnelID, replyMsg); + else + LogPrint (eLogWarning, "NetDb: Can't send lookup reply to ", replyIdent.ToBase64 (), ". Non reachable and no outbound tunnels"); + } } else transports.SendMessage (replyIdent, replyMsg); } } - void NetDb::HandleDeliveryStatusMsg (std::shared_ptr msg) + void NetDb::Flood (const IdentHash& ident, std::shared_ptr floodMsg, bool andNextDay) { - if (m_PublishReplyToken == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) - { - LogPrint (eLogInfo, "NetDb: Publishing confirmed. reply token=", m_PublishReplyToken); - m_PublishExcluded.clear (); - m_PublishReplyToken = 0; - } - } - - void NetDb::Explore (int numDestinations) - { - // new requests - auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; - auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; - bool throughTunnels = outbound && inbound; - - uint8_t randomHash[32]; - std::vector msgs; - LogPrint (eLogInfo, "NetDb: Exploring new ", numDestinations, " routers ..."); - for (int i = 0; i < numDestinations; i++) - { - RAND_bytes (randomHash, 32); - auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory - if (!dest) - { - LogPrint (eLogWarning, "NetDb: Exploratory destination is requested already"); - return; - } - auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); - if (floodfill) - { - if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) - throughTunnels = false; - if (throughTunnels) - { - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - floodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg () // tell floodfill about us - }); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - floodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (floodfill, inbound) // explore - }); - } - else - i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); - } - else - m_Requests.RequestComplete (randomHash, nullptr); - } - if (throughTunnels && msgs.size () > 0) - outbound->SendTunnelDataMsg (msgs); - } - - void NetDb::Publish () - { - i2p::context.UpdateStats (); // for floodfill - - if (m_PublishExcluded.size () > NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS) - { - LogPrint (eLogError, "NetDb: Couldn't publish our RouterInfo to ", NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS, " closest routers. Try again"); - m_PublishExcluded.clear (); - } - - auto floodfill = GetClosestFloodfill (i2p::context.GetIdentHash (), m_PublishExcluded); - if (floodfill) - { - uint32_t replyToken; - RAND_bytes ((uint8_t *)&replyToken, 4); - LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); - m_PublishExcluded.insert (floodfill->GetIdentHash ()); - m_PublishReplyToken = replyToken; - if (floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) || // are we able to connect? - i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) // already connected ? - // send directly - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); - else - { - // otherwise through exploratory - auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; - auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; - if (inbound && outbound) - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound)); - } - } - } - - void NetDb::Flood (const IdentHash& ident, std::shared_ptr floodMsg) - { - std::set excluded; + std::unordered_set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't flood to itself excluded.insert (ident); // don't flood back for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (ident, excluded); + auto floodfill = GetClosestFloodfill (ident, excluded, false); // current day if (floodfill) { - auto h = floodfill->GetIdentHash(); - LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64()); + const auto& h = floodfill->GetIdentHash(); transports.SendMessage (h, CopyI2NPMessage(floodMsg)); excluded.insert (h); } else - break; + return; // no more floodfills } + if (andNextDay) + { + // flood to two more closest flodfills for next day + std::unordered_set excluded1; + excluded1.insert (i2p::context.GetIdentHash ()); // don't flood to itself + excluded1.insert (ident); // don't flood back + for (int i = 0; i < 2; i++) + { + auto floodfill = GetClosestFloodfill (ident, excluded1, true); // next day + if (floodfill) + { + const auto& h = floodfill->GetIdentHash(); + if (!excluded.count (h)) // we didn't send for current day, otherwise skip + transports.SendMessage (h, CopyI2NPMessage(floodMsg)); + excluded1.insert (h); + } + else + return; + } + } } std::shared_ptr NetDb::GetRandomRouter () const @@ -1186,29 +1151,24 @@ namespace data }); } - std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, bool reverse) const + std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, + bool reverse, bool endpoint, bool clientTunnel) const { + bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate + context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime return GetRandomRouter ( - [compatibleWith, reverse](std::shared_ptr router)->bool + [compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - (reverse ? compatibleWith->IsReachableFrom (*router) : - router->IsReachableFrom (*compatibleWith)) && - router->IsECIES (); + (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)): + router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && + router->IsECIES () && !router->IsHighCongestion (clientTunnel) && + (!checkIsReal || router->GetProfile ()->IsReal ()) && + (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) }); } - std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4, const std::set& excluded) const - { - return GetRandomRouter ( - [v4, &excluded](std::shared_ptr router)->bool - { - return !router->IsHidden () && router->IsECIES () && - router->IsPeerTesting (v4) && !excluded.count (router->GetIdentHash ()); - }); - } - - std::shared_ptr NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const + std::shared_ptr NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set& excluded) const { return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool @@ -1217,47 +1177,34 @@ namespace data router->IsSSU2PeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } - - std::shared_ptr NetDb::GetRandomSSUV6Router () const - { - return GetRandomRouter ( - [](std::shared_ptr router)->bool - { - return !router->IsHidden () && router->IsECIES () && router->IsSSUV6 (); - }); - } - std::shared_ptr NetDb::GetRandomIntroducer (bool v4, const std::set& excluded) const + std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::unordered_set& excluded) const { return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsECIES () && !router->IsFloodfill () && // floodfills don't send relay tag - router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()); - }); - } - - std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::set& excluded) const - { - return GetRandomRouter ( - [v4, &excluded](std::shared_ptr router)->bool - { - return !router->IsHidden () && router->IsSSU2Introducer (v4) && + return !router->IsHidden () && router->IsSSU2Introducer (v4) && !excluded.count (router->GetIdentHash ()); }); } - - std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const + + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, + bool reverse, bool endpoint) const { + bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate + context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime return GetRandomRouter ( - [compatibleWith, reverse](std::shared_ptr router)->bool + [compatibleWith, reverse, endpoint, checkIsReal](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - (reverse ? compatibleWith->IsReachableFrom (*router) : - router->IsReachableFrom (*compatibleWith)) && + (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)) : + router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && (router->GetCaps () & RouterInfo::eHighBandwidth) && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && - router->IsECIES (); + router->IsECIES () && !router->IsHighCongestion (true) && + (!checkIsReal || router->GetProfile ()->IsReal ()) && + (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) + }); } @@ -1265,11 +1212,13 @@ namespace data std::shared_ptr NetDb::GetRandomRouter (Filter filter) const { if (m_RouterInfos.empty()) - return 0; + return nullptr; uint16_t inds[3]; RAND_bytes ((uint8_t *)inds, sizeof (inds)); - std::unique_lock l(m_RouterInfosMutex); - inds[0] %= m_RouterInfos.size (); + std::lock_guard l(m_RouterInfosMutex); + auto count = m_RouterInfos.size (); + if(count == 0) return nullptr; + inds[0] %= count; auto it = m_RouterInfos.begin (); std::advance (it, inds[0]); // try random router @@ -1324,80 +1273,46 @@ namespace data if (msg) m_Queue.Put (msg); } - std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, - const std::set& excluded, bool closeThanUsOnly) const + void NetDb::PostDatabaseSearchReplyMsg (std::shared_ptr msg) { - std::shared_ptr r; - XORMetric minMetric; - IdentHash destKey = CreateRoutingKey (destination); - if (closeThanUsOnly) - minMetric = destKey ^ i2p::context.GetIdentHash (); - else - minMetric.SetMax (); - std::unique_lock l(m_FloodfillsMutex); - for (const auto& it: m_Floodfills) - { - if (!it->IsUnreachable ()) + if (msg && m_Requests) + m_Requests->PostDatabaseSearchReplyMsg (msg); + } + + std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, + const std::unordered_set& excluded, bool nextDay) const + { + IdentHash destKey = CreateRoutingKey (destination, nextDay); + std::lock_guard l(m_FloodfillsMutex); + return m_Floodfills.FindClosest (destKey, [&excluded](const std::shared_ptr& r)->bool { - XORMetric m = destKey ^ it->GetIdentHash (); - if (m < minMetric && !excluded.count (it->GetIdentHash ())) - { - minMetric = m; - r = it; - } - } - } - return r; + return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () && + !excluded.count (r->GetIdentHash ()); + }); } std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num, - std::set& excluded, bool closeThanUsOnly) const + std::unordered_set& excluded, bool closeThanUsOnly) const { - struct Sorted - { - std::shared_ptr r; - XORMetric metric; - bool operator< (const Sorted& other) const { return metric < other.metric; }; - }; - - std::set sorted; + std::vector res; IdentHash destKey = CreateRoutingKey (destination); + std::vector > v; + { + std::lock_guard l(m_FloodfillsMutex); + v = m_Floodfills.FindClosest (destKey, num, [&excluded](const std::shared_ptr& r)->bool + { + return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () && + !excluded.count (r->GetIdentHash ()); + }); + } + if (v.empty ()) return res; + XORMetric ourMetric; if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); + for (auto& it: v) { - std::unique_lock l(m_FloodfillsMutex); - for (const auto& it: m_Floodfills) - { - if (!it->IsUnreachable ()) - { - XORMetric m = destKey ^ it->GetIdentHash (); - if (closeThanUsOnly && ourMetric < m) continue; - if (sorted.size () < num) - sorted.insert ({it, m}); - else if (m < sorted.rbegin ()->metric) - { - sorted.insert ({it, m}); - sorted.erase (std::prev (sorted.end ())); - } - } - } - } - - std::vector res; - size_t i = 0; - for (const auto& it: sorted) - { - if (i < num) - { - const auto& ident = it.r->GetIdentHash (); - if (!excluded.count (ident)) - { - res.push_back (ident); - i++; - } - } - else - break; + if (closeThanUsOnly && ourMetric < (destKey ^ it->GetIdentHash ())) break; + res.push_back (it->GetIdentHash ()); } return res; } @@ -1411,27 +1326,61 @@ namespace data }); } - std::shared_ptr NetDb::GetClosestNonFloodfill (const IdentHash& destination, - const std::set& excluded) const + std::vector NetDb::GetExploratoryNonFloodfill (const IdentHash& destination, + size_t num, const std::unordered_set& excluded) { - std::shared_ptr r; - XORMetric minMetric; - IdentHash destKey = CreateRoutingKey (destination); - minMetric.SetMax (); - // must be called from NetDb thread only - for (const auto& it: m_RouterInfos) + std::vector ret; + if (!num || m_RouterInfos.empty ()) return ret; // empty list + auto ts = i2p::util::GetMonotonicSeconds (); + if (ts > m_LastExploratorySelectionUpdateTime + NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL) { - if (!it.second->IsFloodfill ()) + // update selection + m_ExploratorySelection.clear (); + std::vector > eligible; + eligible.reserve (m_RouterInfos.size ()); { - XORMetric m = destKey ^ it.first; - if (m < minMetric && !excluded.count (it.first)) - { - minMetric = m; - r = it.second; - } + // collect eligible from current netdb + bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD; // too low rate + std::lock_guard l(m_RouterInfosMutex); + for (const auto& it: m_RouterInfos) + if (!it.second->IsDeclaredFloodfill () && + (!checkIsReal || (it.second->HasProfile () && it.second->GetProfile ()->IsReal ()))) + eligible.push_back (it.second); } + if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE) + { + std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection), + NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng); + } + else + std::swap (m_ExploratorySelection, eligible); + m_LastExploratorySelectionUpdateTime = ts; + } + + // sort by distance + IdentHash destKey = CreateRoutingKey (destination); + std::map > sorted; + for (const auto& it: m_ExploratorySelection) + if (!excluded.count (it->GetIdentHash ())) + sorted.emplace (destKey ^ it->GetIdentHash (), it); + // return first num closest routers + for (const auto& it: sorted) + { + ret.push_back (it.second->GetIdentHash ()); + if (ret.size () >= num) break; + } + return ret; + } + + void NetDb::ManageRouterInfos () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + { + std::lock_guard l(m_RouterInfosMutex); + for (auto& it: m_RouterInfos) + it.second->UpdateIntroducers (ts); } - return r; + SaveUpdated (); } void NetDb::ManageLeaseSets () @@ -1450,10 +1399,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 b14b84e7..f2a7019b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,12 +10,13 @@ #define NETDB_H__ // this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs #include -#include +#include #include -#include #include #include #include +#include +#include #include "Base.h" #include "Gzip.h" @@ -31,6 +32,7 @@ #include "Family.h" #include "version.h" #include "util.h" +#include "KadDHT.h" namespace i2p { @@ -38,17 +40,25 @@ namespace data { const int NETDB_MIN_ROUTERS = 90; const int NETDB_MIN_FLOODFILLS = 5; + const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline + const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1200; + const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD; + const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in % + const int NETDB_CHECK_FOR_EXPIRATION_UPTIME = 600; // 10 minutes, in seconds const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds - const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60; 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_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_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59 const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62 + const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16; + const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500; + const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill + const int NETDB_NEXT_DAY_ROUTER_INFO_THRESHOLD = 45; // in minutes + const int NETDB_NEXT_DAY_LEASESET_THRESHOLD = 10; // in minutes /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; @@ -78,40 +88,29 @@ namespace data std::shared_ptr FindRouterProfile (const IdentHash& ident) const; void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true); - void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr); - - void HandleDatabaseStoreMsg (std::shared_ptr msg); - void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); - void HandleDatabaseLookupMsg (std::shared_ptr msg); - void HandleNTCP2RouterInfoMsg (std::shared_ptr m); - void HandleDeliveryStatusMsg (std::shared_ptr msg); - + std::shared_ptr GetRandomRouter () const; - std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; - std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; - std::shared_ptr GetRandomPeerTestRouter (bool v4, const std::set& excluded) const; - std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const; - std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later - std::shared_ptr GetRandomIntroducer (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 GetRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint, bool clientTunnel) const; + std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint) const; + std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set& excluded) const; + std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::unordered_set& excluded) const; + std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::unordered_set& excluded, bool nextDay = false) 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; + std::unordered_set& excluded, bool closeThanUsOnly = false) const; + std::vector GetExploratoryNonFloodfill (const IdentHash& destination, size_t num, const std::unordered_set& excluded); std::shared_ptr GetRandomRouterInFamily (FamilyID fam) const; void SetUnreachable (const IdentHash& ident, bool unreachable); + void ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports); void PostI2NPMsg (std::shared_ptr msg); - - /** set hidden mode, aka don't publish our RI to netdb and don't explore */ - void SetHidden(bool hide); + void PostDatabaseSearchReplyMsg (std::shared_ptr msg); // to NetdbReq thread void Reseed (); Families& GetFamilies () { return m_Families; }; // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; - int GetNumFloodfills () const { return m_Floodfills.size (); }; + int GetNumFloodfills () const { return m_Floodfills.GetSize (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; /** visit all lease sets we currently store */ @@ -124,21 +123,34 @@ namespace data size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n); void ClearRouterInfos () { m_RouterInfos.clear (); }; - std::shared_ptr NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); }; - void PopulateRouterInfoBuffer (std::shared_ptr r); - std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; - - uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; + template + std::shared_ptr NewRouterInfoBuffer (TArgs&&... args) + { + return m_RouterInfoBuffersPool.AcquireSharedMt (std::forward(args)...); + } + bool PopulateRouterInfoBuffer (std::shared_ptr r); + std::shared_ptr NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); }; + RouterInfo::AddressesPtr NewRouterInfoAddresses () + { + return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (), + std::bind ::*)(RouterInfo::Addresses *)> + (&i2p::util::MemoryPoolMt::ReleaseMt, + &m_RouterInfoAddressVectorsPool, std::placeholders::_1)}; + }; + std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; + std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); }; + std::shared_ptr NewRouterProfile () { return m_RouterProfilesPool.AcquireSharedMt (); }; private: void Load (); bool LoadRouterInfo (const std::string& path, uint64_t ts); void SaveUpdated (); - void Run (); // exploratory thread - void Explore (int numDestinations); - void Publish (); - void Flood (const IdentHash& ident, std::shared_ptr floodMsg); + void PersistRouters (std::list > >&& update, + std::list&& remove); + void Run (); + void Flood (const IdentHash& ident, std::shared_ptr floodMsg, bool andNextDay = false); + void ManageRouterInfos (); void ManageLeaseSets (); void ManageRequests (); @@ -150,6 +162,10 @@ namespace data template std::shared_ptr GetRandomRouter (Filter filter) const; + void HandleDatabaseStoreMsg (std::shared_ptr msg); + void HandleDatabaseLookupMsg (std::shared_ptr msg); + void HandleNTCP2RouterInfoMsg (std::shared_ptr m); + private: mutable std::mutex m_LeaseSetsMutex; @@ -157,7 +173,7 @@ namespace data mutable std::mutex m_RouterInfosMutex; std::unordered_map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; - std::list > m_Floodfills; + DHTTable m_Floodfills; bool m_IsRunning; std::thread * m_Thread; @@ -168,22 +184,21 @@ namespace data Families m_Families; i2p::fs::HashedStorage m_Storage; - friend class NetDbRequests; - NetDbRequests m_Requests; + std::shared_ptr m_Requests; bool m_PersistProfiles; + std::future m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters; - /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ - std::shared_ptr m_FloodfillBootstrap; - - /** true if in hidden mode */ - bool m_HiddenMode; - - std::set m_PublishExcluded; - uint32_t m_PublishReplyToken = 0; + std::vector > m_ExploratorySelection; + uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds + std::mt19937 m_Rng; i2p::util::MemoryPoolMt m_RouterInfoBuffersPool; + i2p::util::MemoryPoolMt m_RouterInfoAddressesPool; + i2p::util::MemoryPoolMt m_RouterInfoAddressVectorsPool; i2p::util::MemoryPoolMt m_LeasesPool; + i2p::util::MemoryPoolMt m_IdentitiesPool; + i2p::util::MemoryPoolMt m_RouterProfilesPool; }; extern NetDb netdb; diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index e7aab34c..94633e10 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,12 +10,30 @@ #include "I2NPProtocol.h" #include "Transports.h" #include "NetDb.hpp" +#include "ECIESX25519AEADRatchetSession.h" +#include "RouterContext.h" +#include "Timestamp.h" #include "NetDbRequests.h" namespace i2p { namespace data { + RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct): + m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), + m_IsActive (true), m_IsSentDirectly (false), + m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()), + m_LastRequestTime (0), m_NumAttempts (0) + { + if (i2p::context.IsFloodfill ()) + m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill + } + + RequestedDestination::~RequestedDestination () + { + InvokeRequestComplete (nullptr); + } + std::shared_ptr RequestedDestination::CreateRequestMessage (std::shared_ptr router, std::shared_ptr replyTunnel) { @@ -28,7 +46,9 @@ namespace data msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); if(router) m_ExcludedPeers.insert (router->GetIdentHash ()); - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); + m_NumAttempts++; + m_IsSentDirectly = false; return msg; } @@ -37,80 +57,155 @@ namespace data auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + m_NumAttempts++; + m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); + m_IsSentDirectly = true; return msg; } + bool RequestedDestination::IsExcluded (const IdentHash& ident) const + { + return m_ExcludedPeers.count (ident); + } + void RequestedDestination::ClearExcludedPeers () { m_ExcludedPeers.clear (); } + void RequestedDestination::InvokeRequestComplete (std::shared_ptr r) + { + if (!m_RequestComplete.empty ()) + { + for (auto it: m_RequestComplete) + if (it != nullptr) it (r); + m_RequestComplete.clear (); + } + } + void RequestedDestination::Success (std::shared_ptr r) { - if (m_RequestComplete) - { - m_RequestComplete (r); - m_RequestComplete = nullptr; - } + if (m_IsActive) + { + m_IsActive = false; + InvokeRequestComplete (r); + } } void RequestedDestination::Fail () { - if (m_RequestComplete) - { - m_RequestComplete (nullptr); - m_RequestComplete = nullptr; - } + if (m_IsActive) + { + m_IsActive = false; + InvokeRequestComplete (nullptr); + } } + NetDbRequests::NetDbRequests (): + RunnableServiceWithWork ("NetDbReq"), + m_ManageRequestsTimer (GetIOService ()), m_ExploratoryTimer (GetIOService ()), + m_CleanupTimer (GetIOService ()), m_DiscoveredRoutersTimer (GetIOService ()), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) + { + } + + NetDbRequests::~NetDbRequests () + { + Stop (); + } + void NetDbRequests::Start () { + if (!IsRunning ()) + { + StartIOService (); + ScheduleManageRequests (); + ScheduleCleanup (); + if (!i2p::context.IsHidden ()) + ScheduleExploratory (EXPLORATORY_REQUEST_INTERVAL); + } } void NetDbRequests::Stop () { - m_RequestedDestinations.clear (); + if (IsRunning ()) + { + m_ManageRequestsTimer.cancel (); + m_ExploratoryTimer.cancel (); + m_CleanupTimer.cancel (); + StopIOService (); + + m_RequestedDestinations.clear (); + m_RequestedDestinationsPool.CleanUpMt (); + } } - - std::shared_ptr NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete) + void NetDbRequests::ScheduleCleanup () + { + m_CleanupTimer.expires_from_now (boost::posix_time::seconds(REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL)); + m_CleanupTimer.async_wait (std::bind (&NetDbRequests::HandleCleanupTimer, + this, std::placeholders::_1)); + } + + void NetDbRequests::HandleCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + m_RequestedDestinationsPool.CleanUpMt (); + ScheduleCleanup (); + } + } + + std::shared_ptr NetDbRequests::CreateRequest (const IdentHash& destination, + bool isExploratory, bool direct, RequestedDestination::RequestComplete requestComplete) { // request RouterInfo directly - auto dest = std::make_shared (destination, isExploratory); - dest->SetRequestComplete (requestComplete); - { - std::unique_lock l(m_RequestedDestinationsMutex); - if (!m_RequestedDestinations.insert (std::make_pair (destination, dest)).second) // not inserted - return nullptr; - } + auto dest = m_RequestedDestinationsPool.AcquireSharedMt (destination, isExploratory, direct); + if (requestComplete) + dest->AddRequestComplete (requestComplete); + + auto ret = m_RequestedDestinations.emplace (destination, dest); + if (!ret.second) // not inserted + { + dest->ResetRequestComplete (); // don't call requestComplete in destructor + dest = ret.first->second; // existing one + if (requestComplete) + { + if (dest->IsActive ()) + dest->AddRequestComplete (requestComplete); + else + requestComplete (nullptr); + } + return nullptr; + } return dest; } void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr r) { - std::shared_ptr request; - { - std::unique_lock l(m_RequestedDestinationsMutex); - auto it = m_RequestedDestinations.find (ident); - if (it != m_RequestedDestinations.end ()) - { - request = it->second; - m_RequestedDestinations.erase (it); - } - } - if (request) - { - if (r) - request->Success (r); - else - request->Fail (); - } + boost::asio::post (GetIOService (), [this, ident, r]() + { + std::shared_ptr request; + auto it = m_RequestedDestinations.find (ident); + if (it != m_RequestedDestinations.end ()) + { + request = it->second; + if (request->IsExploratory ()) + m_RequestedDestinations.erase (it); + // otherwise cache for a while + } + if (request) + { + if (r) + request->Success (r); + else + request->Fail (); + } + }); } std::shared_ptr NetDbRequests::FindRequest (const IdentHash& ident) const { - std::unique_lock l(m_RequestedDestinationsMutex); auto it = m_RequestedDestinations.find (ident); if (it != m_RequestedDestinations.end ()) return it->second; @@ -119,50 +214,349 @@ namespace data void NetDbRequests::ManageRequests () { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(m_RequestedDestinationsMutex); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) { auto& dest = it->second; - bool done = false; - if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute - { - if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds - { - auto count = dest->GetExcludedPeers ().size (); - if (!dest->IsExploratory () && count < 7) + if (dest->IsActive () || ts < dest->GetCreationTime () + REQUEST_CACHE_TIME) + { + if (!dest->IsExploratory ()) + { + // regular request + bool done = false; + if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME) { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool->GetNextOutboundTunnel (); - auto inbound = pool->GetNextInboundTunnel (); - auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); - if (nextFloodfill && outbound && inbound) - outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (nextFloodfill, inbound)); - else - { - done = true; - if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels"); - if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels"); - if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills"); - } + if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) + // try next floodfill if no response after min interval + done = !SendNextRequest (dest); } - else - { - if (!dest->IsExploratory ()) - LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); + else // request is expired done = true; - } - } - } - else // delete obsolete request - done = true; - - if (done) - it = m_RequestedDestinations.erase (it); + if (done) + dest->Fail (); + it++; + } + else + { + // exploratory + if (ts >= dest->GetCreationTime () + MAX_EXPLORATORY_REQUEST_TIME) + { + dest->Fail (); + it = m_RequestedDestinations.erase (it); // delete expired exploratory request right a way + } + else + it++; + } + } else - ++it; + it = m_RequestedDestinations.erase (it); } } + + bool NetDbRequests::SendNextRequest (std::shared_ptr dest) + { + if (!dest || !dest->IsActive ()) return false; + bool ret = true; + auto count = dest->GetNumAttempts (); + if (!dest->IsExploratory () && count < MAX_NUM_REQUEST_ATTEMPTS) + { + auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); + if (nextFloodfill) + { + bool direct = dest->IsDirect (); + if (direct && !nextFloodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && + !i2p::transport::transports.IsConnected (nextFloodfill->GetIdentHash ())) + direct = false; // floodfill can't be reached directly + auto s = shared_from_this (); + auto onDrop = [s, dest]() + { + if (dest->IsActive ()) + { + boost::asio::post (s->GetIOService (), [s, dest]() + { + if (dest->IsActive ()) s->SendNextRequest (dest); + }); + } + }; + if (direct) + { + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " directly"); + auto msg = dest->CreateRequestMessage (nextFloodfill->GetIdentHash ()); + msg->onDrop = onDrop; + i2p::transport::transports.SendMessage (nextFloodfill->GetIdentHash (), msg); + } + else + { + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + if (pool) + { + auto outbound = pool->GetNextOutboundTunnel (); + auto inbound = pool->GetNextInboundTunnel (); + if (nextFloodfill && outbound && inbound) + { + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " through tunnels"); + auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); + msg->onDrop = onDrop; + outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0, + i2p::garlic::WrapECIESX25519MessageForRouter (msg, nextFloodfill->GetIdentity ()->GetEncryptionPublicKey ())); + } + else + { + ret = false; + if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels"); + if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels"); + } + } + else + { + ret = false; + LogPrint (eLogWarning, "NetDbReq: Exploratory pool is not ready"); + } + } + } + else + { + ret = false; + LogPrint (eLogWarning, "NetDbReq: No more floodfills for ", dest->GetDestination ().ToBase64 (), " after ", count, "attempts"); + } + } + else + { + if (!dest->IsExploratory ()) + LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after ", MAX_NUM_REQUEST_ATTEMPTS," attempts"); + ret = false; + } + return ret; + } + + void NetDbRequests::ScheduleManageRequests () + { + m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL + + m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE)); + m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer, + this, std::placeholders::_1)); + } + + void NetDbRequests::HandleManageRequestsTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (i2p::tunnel::tunnels.GetExploratoryPool ()) // expolratory pool is ready? + ManageRequests (); + ScheduleManageRequests (); + } + } + + void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr msg) + { + boost::asio::post (GetIOService (), [this, msg]() + { + HandleDatabaseSearchReplyMsg (msg); + }); + } + + void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) + { + const uint8_t * buf = msg->GetPayload (); + std::string key; + size_t num = buf[32]; // num + if (CheckLogLevel (eLogInfo)) + key = i2p::data::ByteStreamToBase64 (buf, 32); + LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num); + + IdentHash ident (buf); + bool isExploratory = false; + auto dest = FindRequest (ident); + if (dest && dest->IsActive ()) + { + isExploratory = dest->IsExploratory (); + if (!isExploratory && (num > 0 || dest->GetNumAttempts () < 3)) // before 3-rd attempt might be just bad luck + { + // try to send next requests + if (!SendNextRequest (dest)) + RequestComplete (ident, nullptr); + } + else + // no more requests for destination possible. delete it + RequestComplete (ident, nullptr); + } + else /*if (!m_FloodfillBootstrap)*/ + { + LogPrint (eLogInfo, "NetDbReq: Unsolicited or late database search reply for ", key); + return; + } + + // try responses + if (num > NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES) + { + LogPrint (eLogWarning, "NetDbReq: Too many peer hashes ", num, " in database search reply, Reduced to ", NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES); + num = NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES; + } + if (isExploratory && !m_DiscoveredRouterHashes.empty ()) + { + // request outstanding routers + for (auto it: m_DiscoveredRouterHashes) + RequestRouter (it); + m_DiscoveredRouterHashes.clear (); + m_DiscoveredRoutersTimer.cancel (); + } + for (size_t i = 0; i < num; i++) + { + IdentHash router (buf + 33 + i*32); + if (CheckLogLevel (eLogDebug)) + LogPrint (eLogDebug, "NetDbReq: ", i, ": ", router.ToBase64 ()); + + if (isExploratory) + // postpone request + m_DiscoveredRouterHashes.push_back (router); + else + // send request right a way + RequestRouter (router); + } + if (isExploratory && !m_DiscoveredRouterHashes.empty ()) + ScheduleDiscoveredRoutersRequest (); + } + + void NetDbRequests::RequestRouter (const IdentHash& router) + { + auto r = netdb.FindRouter (router); + if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) + { + // router with ident not found or too old (1 hour) + LogPrint (eLogDebug, "NetDbReq: Found new/outdated router. Requesting RouterInfo..."); + if (!IsRouterBanned (router)) + RequestDestination (router, nullptr, true); + else + LogPrint (eLogDebug, "NetDbReq: Router ", router.ToBase64 (), " is banned. Skipped"); + } + else + LogPrint (eLogDebug, "NetDbReq: [:|||:]"); + } + + void NetDbRequests::PostRequestDestination (const IdentHash& destination, + const RequestedDestination::RequestComplete& requestComplete, bool direct) + { + boost::asio::post (GetIOService (), [this, destination, requestComplete, direct]() + { + RequestDestination (destination, requestComplete, direct); + }); + } + + void NetDbRequests::RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct) + { + auto dest = CreateRequest (destination, false, direct, requestComplete); // non-exploratory + if (dest) + { + if (!SendNextRequest (dest)) + RequestComplete (destination, nullptr); + } + else + LogPrint (eLogWarning, "NetDbReq: Destination ", destination.ToBase64(), " is requested already or cached"); + } + + void NetDbRequests::Explore (int numDestinations) + { + // new requests + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; + bool throughTunnels = outbound && inbound; + + uint8_t randomHash[32]; + std::vector msgs; + LogPrint (eLogInfo, "NetDbReq: Exploring new ", numDestinations, " routers ..."); + for (int i = 0; i < numDestinations; i++) + { + RAND_bytes (randomHash, 32); + auto dest = CreateRequest (randomHash, true, !throughTunnels); // exploratory + if (!dest) + { + LogPrint (eLogWarning, "NetDbReq: Exploratory destination is requested already"); + return; + } + auto floodfill = netdb.GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); + if (floodfill) + { + if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) + throughTunnels = false; + if (throughTunnels) + { + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeRouter, + floodfill->GetIdentHash (), 0, + CreateDatabaseStoreMsg () // tell floodfill about us + }); + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeRouter, + floodfill->GetIdentHash (), 0, + dest->CreateRequestMessage (floodfill, inbound) // explore + }); + } + else + i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); + } + else + RequestComplete (randomHash, nullptr); + } + if (throughTunnels && msgs.size () > 0) + outbound->SendTunnelDataMsgs (msgs); + } + + void NetDbRequests::ScheduleExploratory (uint64_t interval) + { + m_ExploratoryTimer.expires_from_now (boost::posix_time::seconds(interval)); + m_ExploratoryTimer.async_wait (std::bind (&NetDbRequests::HandleExploratoryTimer, + this, std::placeholders::_1)); + } + + void NetDbRequests::HandleExploratoryTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto numRouters = netdb.GetNumRouters (); + auto nextExploratoryInterval = numRouters < 2500 ? (EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL)/2 : + EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL_VARIANCE; + if (numRouters) + { + if (i2p::transport::transports.IsOnline () && i2p::transport::transports.IsRunning ()) + { + // explore only if online + numRouters = 800/numRouters; + if (numRouters < 1) numRouters = 1; + if (numRouters > 9) numRouters = 9; + Explore (numRouters); + } + } + else + LogPrint (eLogError, "NetDbReq: No known routers, reseed seems to be totally failed"); + ScheduleExploratory (nextExploratoryInterval); + } + } + + void NetDbRequests::ScheduleDiscoveredRoutersRequest () + { + m_DiscoveredRoutersTimer.expires_from_now (boost::posix_time::milliseconds( + DISCOVERED_REQUEST_INTERVAL + m_Rng () % DISCOVERED_REQUEST_INTERVAL_VARIANCE)); + m_DiscoveredRoutersTimer.async_wait (std::bind (&NetDbRequests::HandleDiscoveredRoutersTimer, + this, std::placeholders::_1)); + } + + void NetDbRequests::HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (!m_DiscoveredRouterHashes.empty ()) + { + RequestRouter (m_DiscoveredRouterHashes.front ()); + m_DiscoveredRouterHashes.pop_front (); + if (!m_DiscoveredRouterHashes.empty ()) // more hashes to request + ScheduleDiscoveredRoutersRequest (); + } + } + } } } diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h index cf2f0915..53af2c6a 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,66 +9,121 @@ #ifndef NETDB_REQUESTS_H__ #define NETDB_REQUESTS_H__ +#include #include -#include -#include +#include +#include +#include +#include #include "Identity.h" #include "RouterInfo.h" +#include "util.h" namespace i2p { namespace data { + const int MAX_NUM_REQUEST_ATTEMPTS = 5; + const uint64_t MANAGE_REQUESTS_INTERVAL = 400; // in milliseconds + const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds + const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds + const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE); + const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds + const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds + const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds + const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds + const uint64_t DISCOVERED_REQUEST_INTERVAL_VARIANCE = 540; // in milliseconds + const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30000; // in milliseconds + const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40000; // in milliseconds + const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds + class RequestedDestination { public: typedef std::function)> RequestComplete; - RequestedDestination (const IdentHash& destination, bool isExploratory = false): - m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {}; - ~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); }; + RequestedDestination (const IdentHash& destination, bool isExploratory = false, bool direct = true); + ~RequestedDestination (); const IdentHash& GetDestination () const { return m_Destination; }; - int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; - const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; + const std::unordered_set& GetExcludedPeers () const { return m_ExcludedPeers; }; + int GetNumAttempts () const { return m_NumAttempts; }; void ClearExcludedPeers (); bool IsExploratory () const { return m_IsExploratory; }; - bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; + bool IsDirect () const { return m_IsDirect; }; + bool IsActive () const { return m_IsActive; }; + bool IsSentDirectly () const { return m_IsSentDirectly; }; + bool IsExcluded (const IdentHash& ident) const; uint64_t GetCreationTime () const { return m_CreationTime; }; + uint64_t GetLastRequestTime () const { return m_LastRequestTime; }; std::shared_ptr CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); std::shared_ptr CreateRequestMessage (const IdentHash& floodfill); - void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; - bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; + void AddRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete.push_back (requestComplete); }; + void ResetRequestComplete () { m_RequestComplete.clear (); }; void Success (std::shared_ptr r); void Fail (); + private: + + void InvokeRequestComplete (std::shared_ptr r); + private: IdentHash m_Destination; - bool m_IsExploratory; - std::set m_ExcludedPeers; - uint64_t m_CreationTime; - RequestComplete m_RequestComplete; + bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly; + std::unordered_set m_ExcludedPeers; + uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds + std::list m_RequestComplete; + int m_NumAttempts; }; - class NetDbRequests + class NetDbRequests: public std::enable_shared_from_this, + private i2p::util::RunnableServiceWithWork { public: + NetDbRequests (); + ~NetDbRequests (); + void Start (); void Stop (); - std::shared_ptr CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr); void RequestComplete (const IdentHash& ident, std::shared_ptr r); - std::shared_ptr FindRequest (const IdentHash& ident) const; - void ManageRequests (); + void PostDatabaseSearchReplyMsg (std::shared_ptr msg); + void PostRequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct); + + private: + std::shared_ptr CreateRequest (const IdentHash& destination, bool isExploratory, + bool direct = false, RequestedDestination::RequestComplete requestComplete = nullptr); + std::shared_ptr FindRequest (const IdentHash& ident) const; + bool SendNextRequest (std::shared_ptr dest); + + void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); + void RequestRouter (const IdentHash& router); + void RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct); + void Explore (int numDestinations); + void ManageRequests (); + // timer + void ScheduleManageRequests (); + void HandleManageRequestsTimer (const boost::system::error_code& ecode); + void ScheduleExploratory (uint64_t interval); + void HandleExploratoryTimer (const boost::system::error_code& ecode); + void ScheduleCleanup (); + void HandleCleanupTimer (const boost::system::error_code& ecode); + void ScheduleDiscoveredRoutersRequest (); + void HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode); + private: - mutable std::mutex m_RequestedDestinationsMutex; - std::map > m_RequestedDestinations; + i2p::util::MemoryPoolMt m_RequestedDestinationsPool; + std::unordered_map > m_RequestedDestinations; + std::list m_DiscoveredRouterHashes; + boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer, + m_CleanupTimer, m_DiscoveredRoutersTimer; + std::mt19937 m_Rng; }; } } diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp deleted file mode 100644 index 20b3ab2a..00000000 --- a/libi2pd/Poly1305.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * This code is licensed under the MCGSI Public License - * Copyright 2018 Jeff Becker - * - *Kovri go write your own code - * - */ - -#include "Poly1305.h" - -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -namespace i2p -{ -namespace crypto -{ - void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz) - { - Poly1305 p(key); - p.Update(buf, sz); - p.Finish(out); - } -} -} -#endif - diff --git a/libi2pd/Poly1305.h b/libi2pd/Poly1305.h deleted file mode 100644 index db659b84..00000000 --- a/libi2pd/Poly1305.h +++ /dev/null @@ -1,261 +0,0 @@ -/** - * This code is licensed under the MCGSI Public License - * Copyright 2018 Jeff Becker - * - * Kovri go write your own code - * - */ - -#ifndef LIBI2PD_POLY1305_H -#define LIBI2PD_POLY1305_H -#include -#include -#include "Crypto.h" - -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -namespace i2p -{ -namespace crypto -{ - const std::size_t POLY1305_DIGEST_BYTES = 16; - const std::size_t POLY1305_DIGEST_DWORDS = 4; - const std::size_t POLY1305_KEY_BYTES = 32; - const std::size_t POLY1305_KEY_DWORDS = 8; - const std::size_t POLY1305_BLOCK_BYTES = 16; - - namespace poly1305 - { - struct LongBlock - { - unsigned long data[17]; - operator unsigned long * () - { - return data; - } - }; - - struct Block - { - unsigned char data[17]; - - void Zero() - { - memset(data, 0, sizeof(data)); - } - - operator uint8_t * () - { - return data; - } - - Block & operator += (const Block & other) - { - unsigned short u; - unsigned int i; - for(u = 0, i = 0; i < 17; i++) - { - u += (unsigned short) data[i] + (unsigned short) other.data[i]; - data[i] = (unsigned char) u & 0xff; - u >>= 8; - } - return *this; - } - - Block & operator %=(const LongBlock & other) - { - unsigned long u; - unsigned int i; - u = 0; - for (i = 0; i < 16; i++) { - u += other.data[i]; - data[i] = (unsigned char)u & 0xff; - u >>= 8; - } - u += other.data[16]; - data[16] = (unsigned char)u & 0x03; - u >>= 2; - u += (u << 2); - for (i = 0; i < 16; i++) { - u += data[i]; - data[i] = (unsigned char)u & 0xff; - u >>= 8; - } - data[16] += (unsigned char)u; - return *this; - } - - Block & operator = (const Block & other) - { - memcpy(data, other.data, sizeof(data)); - return *this; - } - - Block & operator ~ () - { - static const Block minusp = { - 0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xfc - }; - Block orig; - unsigned char neg; - unsigned int i; - orig = *this; - *this += minusp; - neg = -(data[16] >> 7); - for(i = 0; i < 17; i++) - data[i] ^= neg & (orig.data[i] ^ data[i]); - - return *this; - } - - void PutKey(const uint64_t * key_l) - { - const uint8_t * key = (const uint8_t*) key_l; - data[0] = key[0] & 0xff; - data[1] = key[1] & 0xff; - data[2] = key[2] & 0xff; - data[3] = key[3] & 0x0f; - data[4] = key[4] & 0xfc; - data[5] = key[5] & 0xff; - data[6] = key[6] & 0xff; - data[7] = key[7] & 0x0f; - data[8] = key[8] & 0xfc; - data[9] = key[9] & 0xff; - data[10] = key[10] & 0xff; - data[11] = key[11] & 0x0f; - data[12] = key[12] & 0xfc; - data[13] = key[13] & 0xff; - data[14] = key[14] & 0xff; - data[15] = key[15] & 0x0f; - data[16] = 0; - } - - template - void Put(const Int_t * d, uint8_t last=0) - { - memcpy(data, d, 16); - data[16] = last; - } - }; - - struct Buffer - { - uint8_t data[POLY1305_BLOCK_BYTES]; - - operator uint8_t * () - { - return data; - } - }; - } - - struct Poly1305 - { - Poly1305(const uint64_t * key) - { - m_Leftover = 0; - m_H.Zero(); - m_Final = 0; - m_R.PutKey(key); - m_Pad.Put(key + 2); - } - - void Update(const uint8_t * buf, size_t sz) - { - // process leftover - if(m_Leftover) - { - size_t want = POLY1305_BLOCK_BYTES - m_Leftover; - if(want > sz) want = sz; - memcpy(m_Buffer + m_Leftover, buf, want); - sz -= want; - buf += want; - m_Leftover += want; - if(m_Leftover < POLY1305_BLOCK_BYTES) return; - Blocks(m_Buffer, POLY1305_BLOCK_BYTES); - m_Leftover = 0; - } - // process blocks - if(sz >= POLY1305_BLOCK_BYTES) - { - size_t want = (sz & ~(POLY1305_BLOCK_BYTES - 1)); - Blocks(buf, want); - buf += want; - sz -= want; - } - // leftover - if(sz) - { - memcpy(m_Buffer+m_Leftover, buf, sz); - m_Leftover += sz; - } - } - - void Blocks(const uint8_t * buf, size_t sz) - { - const unsigned char hi = m_Final ^ 1; - while (sz >= POLY1305_BLOCK_BYTES) { - unsigned long u; - unsigned int i, j; - m_Msg.Put(buf, hi); - /* h += m */ - m_H += m_Msg; - - /* h *= r */ - for (i = 0; i < 17; i++) { - u = 0; - for (j = 0; j <= i ; j++) { - u += (unsigned short)m_H.data[j] * m_R.data[i - j]; - } - for (j = i + 1; j < 17; j++) { - unsigned long v = (unsigned short)m_H.data[j] * m_R.data[i + 17 - j]; - v = ((v << 8) + (v << 6)); /* v *= (5 << 6); */ - u += v; - } - m_HR[i] = u; - } - /* (partial) h %= p */ - m_H %= m_HR; - buf += POLY1305_BLOCK_BYTES; - sz -= POLY1305_BLOCK_BYTES; - } - } - - void Finish(uint64_t * out) - { - // process leftovers - if(m_Leftover) - { - size_t idx = m_Leftover; - m_Buffer[idx++] = 1; - for(; idx < POLY1305_BLOCK_BYTES; idx++) - m_Buffer[idx] = 0; - m_Final = 1; - Blocks(m_Buffer, POLY1305_BLOCK_BYTES); - } - - // freeze H - ~m_H; - // add pad - m_H += m_Pad; - // copy digest - memcpy(out, m_H, 16); - } - - size_t m_Leftover; - poly1305::Buffer m_Buffer; - poly1305::Block m_H; - poly1305::Block m_R; - poly1305::Block m_Pad; - poly1305::Block m_Msg; - poly1305::LongBlock m_HR; - uint8_t m_Final; - }; - - void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz); -} -} -#endif - -#endif diff --git a/libi2pd/PostQuantum.cpp b/libi2pd/PostQuantum.cpp new file mode 100644 index 00000000..fa268828 --- /dev/null +++ b/libi2pd/PostQuantum.cpp @@ -0,0 +1,160 @@ +/* +* Copyright (c) 2025, 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 "Log.h" +#include "PostQuantum.h" + +#if OPENSSL_PQ + +#include +#include + +namespace i2p +{ +namespace crypto +{ + MLKEMKeys::MLKEMKeys (MLKEMTypes type): + m_Name (std::get<0>(MLKEMS[type])), m_KeyLen (std::get<1>(MLKEMS[type])), + m_CTLen (std::get<2>(MLKEMS[type])), m_Pkey (nullptr) + { + } + + MLKEMKeys::~MLKEMKeys () + { + if (m_Pkey) EVP_PKEY_free (m_Pkey); + } + + void MLKEMKeys::GenerateKeys () + { + if (m_Pkey) EVP_PKEY_free (m_Pkey); + m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ()); + } + + void MLKEMKeys::GetPublicKey (uint8_t * pub) const + { + if (m_Pkey) + { + size_t len = m_KeyLen; + EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len); + } + } + + void MLKEMKeys::SetPublicKey (const uint8_t * pub) + { + if (m_Pkey) + { + EVP_PKEY_free (m_Pkey); + m_Pkey = nullptr; + } + OSSL_PARAM params[] = + { + OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen), + OSSL_PARAM_END + }; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL); + if (ctx) + { + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLKEM can't create PKEY context"); + } + + void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared) + { + if (!m_Pkey) return; + auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (ctx) + { + EVP_PKEY_encapsulate_init (ctx, NULL); + size_t len = m_CTLen, sharedLen = 32; + EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLKEM can't create PKEY context"); + } + + void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared) + { + if (!m_Pkey) return; + auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (ctx) + { + EVP_PKEY_decapsulate_init (ctx, NULL); + size_t sharedLen = 32; + EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLKEM can't create PKEY context"); + } + + std::unique_ptr CreateMLKEMKeys (i2p::data::CryptoKeyType type) + { + if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || + type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return nullptr; + return std::make_unique((MLKEMTypes)(type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1)); + } + + static constexpr std::array, std::array >, 3> NoiseIKInitMLKEMKeys = + { + std::make_pair + ( + std::array + { + 0xb0, 0x8f, 0xb1, 0x73, 0x92, 0x66, 0xc9, 0x90, 0x45, 0x7f, 0xdd, 0xc6, 0x4e, 0x55, 0x40, 0xd8, + 0x0a, 0x37, 0x99, 0x06, 0x92, 0x2a, 0x78, 0xc4, 0xb1, 0xef, 0x86, 0x06, 0xd0, 0x15, 0x9f, 0x4d + }, // SHA256("Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256") + std::array + { + 0x95, 0x8d, 0xf6, 0x6c, 0x95, 0xce, 0xa9, 0xf7, 0x42, 0xfc, 0xfa, 0x62, 0x71, 0x36, 0x1e, 0xa7, + 0xdc, 0x7a, 0xc0, 0x75, 0x01, 0xcf, 0xf9, 0xfc, 0x9f, 0xdb, 0x4c, 0x68, 0x3a, 0x53, 0x49, 0xeb + } // SHA256 (first) + ), + std::make_pair + ( + std::array + { + 0x36, 0x03, 0x90, 0x2d, 0xf9, 0xa2, 0x2a, 0x5e, 0xc9, 0x3d, 0xdb, 0x8f, 0xa8, 0x1b, 0xdb, 0x4b, + 0xae, 0x9d, 0x93, 0x9c, 0xdf, 0xaf, 0xde, 0x55, 0x49, 0x13, 0xfe, 0x98, 0xf8, 0x4a, 0xd4, 0xbd + }, // SHA256("Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256") + std::array + { + 0x15, 0x44, 0x89, 0xbf, 0x30, 0xf0, 0xc9, 0x77, 0x66, 0x10, 0xcb, 0xb1, 0x57, 0x3f, 0xab, 0x68, + 0x79, 0x57, 0x39, 0x57, 0x0a, 0xe7, 0xc0, 0x31, 0x8a, 0xa2, 0x96, 0xef, 0xbf, 0xa9, 0x6a, 0xbb + } // SHA256 (first) + ), + std::make_pair + ( + std::array + { + 0x86, 0xa5, 0x36, 0x44, 0xc6, 0x12, 0xd5, 0x71, 0xa1, 0x2d, 0xd8, 0xb6, 0x0a, 0x00, 0x9f, 0x2c, + 0x1a, 0xa8, 0x7d, 0x22, 0xa4, 0xff, 0x2b, 0xcd, 0x61, 0x34, 0x97, 0x6d, 0xa1, 0x49, 0xeb, 0x4a + }, // SHA256("Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256") + std::array + { + 0x42, 0x0d, 0xc2, 0x1c, 0x7b, 0x18, 0x61, 0xb7, 0x4a, 0x04, 0x3d, 0xae, 0x0f, 0xdc, 0xf2, 0x71, + 0xb9, 0xba, 0x19, 0xbb, 0xbd, 0x5f, 0xd4, 0x9c, 0x3f, 0x4b, 0x01, 0xed, 0x6d, 0x13, 0x1d, 0xa2 + } // SHA256 (first) + ) + }; + + void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub) + { + if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || + type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)NoiseIKInitMLKEMKeys.size ()) return; + auto ind = type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1; + state.Init (NoiseIKInitMLKEMKeys[ind].first.data(), NoiseIKInitMLKEMKeys[ind].second.data(), pub); + } +} +} + +#endif \ No newline at end of file diff --git a/libi2pd/PostQuantum.h b/libi2pd/PostQuantum.h new file mode 100644 index 00000000..f426d661 --- /dev/null +++ b/libi2pd/PostQuantum.h @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2025, 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 POST_QUANTUM_H__ +#define POST_QUANTUM_H__ + +#include +#include +#include +#include +#include "Crypto.h" +#include "Identity.h" + +#if OPENSSL_PQ + +namespace i2p +{ +namespace crypto +{ + enum MLKEMTypes + { + eMLKEM512 = 0, + eMLKEM768, + eMLKEM1024 + }; + + constexpr size_t MLKEM512_KEY_LENGTH = 800; + constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768; + constexpr size_t MLKEM768_KEY_LENGTH = 1184; + constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088; + constexpr size_t MLKEM1024_KEY_LENGTH = 1568; + constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568; + + constexpr std::array, 3> MLKEMS = + { + std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH), + std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH), + std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH) + }; + + constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type) + { + if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || + type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0; + return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]); + } + + constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type) + { + if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || + type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0; + return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]); + } + + class MLKEMKeys + { + public: + + MLKEMKeys (MLKEMTypes type); + ~MLKEMKeys (); + + void GenerateKeys (); + void GetPublicKey (uint8_t * pub) const; + void SetPublicKey (const uint8_t * pub); + void Encaps (uint8_t * ciphertext, uint8_t * shared); + void Decaps (const uint8_t * ciphertext, uint8_t * shared); + + private: + + const std::string m_Name; + const size_t m_KeyLen, m_CTLen; + EVP_PKEY * m_Pkey; + }; + + std::unique_ptr CreateMLKEMKeys (i2p::data::CryptoKeyType type); + + void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub); // Noise_IK (ratchets) PQ ML-KEM5 +} +} + +#endif + +#endif diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 55b95831..fe7f9905 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,34 +7,42 @@ */ #include +#include +#include +#include +#include #include #include #include "Base.h" #include "FS.h" #include "Log.h" +#include "Timestamp.h" +#include "NetDb.hpp" #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 std::list)> > > g_PostponedUpdates; + static std::mutex g_PostponedUpdatesMutex; + RouterProfile::RouterProfile (): - m_LastUpdateTime (boost::posix_time::second_clock::local_time()), - m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), - m_NumTimesTaken (0), m_NumTimesRejected (0) + m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0), + m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), m_LastAccessTime (0), + m_LastPersistTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), + m_NumTunnelsNonReplied (0),m_NumTimesTaken (0), m_NumTimesRejected (0), + m_HasConnected (false), m_IsDuplicated (false) { } - boost::posix_time::ptime RouterProfile::GetTime () const - { - return boost::posix_time::second_clock::local_time(); - } - void RouterProfile::UpdateTime () { - m_LastUpdateTime = GetTime (); + m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); + m_IsUpdated = true; } void RouterProfile::Save (const IdentHash& identHash) @@ -47,15 +55,20 @@ namespace data boost::property_tree::ptree usage; usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken); usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected); + usage.put (PEER_PROFILE_USAGE_CONNECTED, m_HasConnected); + if (m_IsDuplicated) + usage.put (PEER_PROFILE_USAGE_DUPLICATED, true); // fill property tree boost::property_tree::ptree pt; - pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); + pt.put (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, 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); @@ -67,8 +80,9 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { + m_IsUpdated = false; 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)) @@ -89,11 +103,24 @@ namespace data try { - auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); - if (t.length () > 0) - m_LastUpdateTime = boost::posix_time::time_from_string (t); - if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) + auto ts = pt.get (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, 0); + if (ts) + m_LastUpdateTime = ts; + else + { + // try old lastupdatetime + auto ut = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); + if (ut.length () > 0) + { + std::istringstream ss (ut); std::tm t; + ss >> std::get_time(&t, "%Y-%b-%d %H:%M:%S"); + if (!ss.fail()) + m_LastUpdateTime = mktime (&t); // t is local time + } + } + if (i2p::util::GetSecondsSinceEpoch () - m_LastUpdateTime < PEER_PROFILE_EXPIRATION_TIMEOUT) { + m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0); try { // read participations @@ -112,6 +139,8 @@ namespace data auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); + m_HasConnected = usage.get (PEER_PROFILE_USAGE_CONNECTED, false); + m_IsDuplicated = usage.get (PEER_PROFILE_USAGE_DUPLICATED, false); } catch (boost::property_tree::ptree_bad_path& ex) { @@ -131,17 +160,44 @@ namespace data { UpdateTime (); if (ret > 0) + { m_NumTunnelsDeclined++; + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } else - m_NumTunnelsAgreed++; + { + m_NumTunnelsAgreed++; + m_LastDeclineTime = 0; + } } void RouterProfile::TunnelNonReplied () { - m_NumTunnelsNonReplied++; + m_NumTunnelsNonReplied++; + UpdateTime (); + if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3) + { + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } + } + + void RouterProfile::Unreachable (bool unreachable) + { + m_LastUnreachableTime = unreachable ? i2p::util::GetSecondsSinceEpoch () : 0; + UpdateTime (); + } + + void RouterProfile::Connected () + { + m_HasConnected = true; UpdateTime (); } + void RouterProfile::Duplicated () + { + m_IsDuplicated = true; + } + bool RouterProfile::IsLowPartcipationRate () const { return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate @@ -153,8 +209,21 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } + bool RouterProfile::IsDeclinedRecently (uint64_t ts) + { + if (!m_LastDeclineTime) return false; + 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 (IsUnreachable () || m_IsDuplicated) return true; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > PEER_PROFILE_MAX_DECLINED_INTERVAL + m_LastDeclineTime) return false; + if (IsDeclinedRecently (ts)) return true; auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { @@ -168,36 +237,186 @@ 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; + } + + bool RouterProfile::IsUseful() const + { + return IsReal () || m_NumTunnelsNonReplied >= PEER_PROFILE_USEFUL_THRESHOLD; + } + std::shared_ptr GetRouterProfile (const IdentHash& identHash) { - auto profile = std::make_shared (); + { + std::unique_lock l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + { + it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ()); + return it->second; + } + } + auto profile = netdb.NewRouterProfile (); profile->Load (identHash); // if possible + std::lock_guard l(g_ProfilesMutex); + g_Profiles.emplace (identHash, profile); return profile; } + bool IsRouterBanned (const IdentHash& identHash) + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second->IsUnreachable (); + return false; + } + + bool IsRouterDuplicated (const IdentHash& identHash) + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second->IsDuplicated (); + return false; + } + void InitProfilesStorage () { - 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); + } + + static void SaveProfilesToDisk (std::list > >&& profiles) + { + for (auto& it: profiles) + if (it.second) it.second->Save (it.first); + } + + std::future PersistProfiles () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + std::list > > tmp; + { + std::lock_guard l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL) + { + tmp.push_back (*it); + it->second->SetLastPersistTime (ts); + it->second->SetUpdated (false); + } + if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL) + it = g_Profiles.erase (it); + else + it++; + } + } + if (!tmp.empty ()) + return std::async (std::launch::async, SaveProfilesToDisk, std::move (tmp)); + return std::future(); } - void DeleteObsoleteProfiles () + void SaveProfiles () { + std::unordered_map > tmp; + { + std::lock_guard l(g_ProfilesMutex); + std::swap (tmp, g_Profiles); + } + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: tmp) + if (it.second->IsUseful() && (it.second->IsUpdated () || ts - it.second->GetLastUpdateTime () < PEER_PROFILE_EXPIRATION_TIMEOUT)) + it.second->Save (it.first); + } + + static void DeleteFilesFromDisk () + { + std::vector files; + g_ProfilesStorage.Traverse(files); + struct stat st; std::time_t now = std::time(nullptr); - - std::vector files; - m_ProfilesStorage.Traverse(files); - for (const auto& path: files) { - if (stat(path.c_str(), &st) != 0) { + 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) + { LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); i2p::fs::Remove(path); } } + } + + std::future DeleteObsoleteProfiles () + { + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + std::lock_guard l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) + it = g_Profiles.erase (it); + else + it++; + } + } + + return std::async (std::launch::async, DeleteFilesFromDisk); } + + bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update) + { + if (!update) return true; + std::shared_ptr profile; + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + profile = it->second; + } + if (profile) + { + update (profile); + return true; + } + // postpone + std::lock_guard l(g_PostponedUpdatesMutex); + g_PostponedUpdates.emplace_back (identHash, update); + return false; + } + + static void ApplyPostponedUpdates (std::list)> > >&& updates) + { + for (const auto& [ident, update] : updates) + { + auto profile = GetRouterProfile (ident); + update (profile); + } + } + + std::future FlushPostponedRouterProfileUpdates () + { + if (g_PostponedUpdates.empty ()) return std::future(); + + std::list)> > > updates; + { + std::lock_guard l(g_PostponedUpdatesMutex); + g_PostponedUpdates.swap (updates); + } + return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates)); + } } } diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 49e362ca..59995b3f 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,9 @@ #define PROFILING_H__ #include -#include +#include +#include +#include #include "Identity.h" namespace i2p @@ -21,44 +23,81 @@ namespace data const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation"; const char PEER_PROFILE_SECTION_USAGE[] = "usage"; // params - const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; + const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; // deprecated + const char PEER_PROFILE_LAST_UPDATE_TIMESTAMP[] = "lastupdatetimestamp"; + 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 char PEER_PROFILE_USAGE_CONNECTED[] = "connected"; + const char PEER_PROFILE_USAGE_DUPLICATED[] = "duplicated"; + + const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36*60*60; // in seconds (1.5 days) + const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 1500; // in seconds (25 minutes) + const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 900; // in seconds (15 minutes) + const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT = 5400; // in seconds (1.5 hours) + const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes) + const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes) + const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours) + const int PEER_PROFILE_PERSIST_INTERVAL = 1320; // in seconds (22 minutes) + const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) + const int PEER_PROFILE_USEFUL_THRESHOLD = 3; + const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined + const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds + const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds + class RouterProfile { public: RouterProfile (); - RouterProfile& operator= (const RouterProfile& ) = default; void Save (const IdentHash& identHash); void Load (const IdentHash& identHash); bool IsBad (); + bool IsUnreachable (); + bool IsReal () const { return m_HasConnected || m_NumTunnelsAgreed > 0 || m_NumTunnelsDeclined > 0; } void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); + void Unreachable (bool unreachable); + void Connected (); + void Duplicated (); + + uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; + bool IsUpdated () const { return m_IsUpdated; }; + void SetUpdated (bool updated) { m_IsUpdated = updated; } + uint64_t GetLastAccessTime () const { return m_LastAccessTime; }; + void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; }; + uint64_t GetLastPersistTime () const { return m_LastPersistTime; }; + void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; }; + + bool IsUseful() const; + bool IsDuplicated () const { return m_IsDuplicated; }; + + const boost::asio::ip::udp::endpoint& GetLastEndpoint () const { return m_LastEndpoint; } + void SetLastEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_LastEndpoint = ep; } + bool HasLastEndpoint (bool v4) const { return !m_LastEndpoint.address ().is_unspecified () && m_LastEndpoint.port () && + ((v4 && m_LastEndpoint.address ().is_v4 ()) || (!v4 && m_LastEndpoint.address ().is_v6 ())); } + 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 (uint64_t ts); private: - boost::posix_time::ptime m_LastUpdateTime; + bool m_IsUpdated; + uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, + m_LastAccessTime, m_LastPersistTime; // in seconds // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -66,11 +105,21 @@ namespace data // usage uint32_t m_NumTimesTaken; uint32_t m_NumTimesRejected; + bool m_HasConnected; // successful trusted(incoming or NTCP2) connection + bool m_IsDuplicated; + // connectivity + boost::asio::ip::udp::endpoint m_LastEndpoint; // SSU2 for non-published addresses }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); + bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles + bool IsRouterDuplicated (const IdentHash& identHash); // check only existing profiles void InitProfilesStorage (); - void DeleteObsoleteProfiles (); + std::future DeleteObsoleteProfiles (); + void SaveProfiles (); + std::future PersistProfiles (); + bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update); // return true if updated immediately, and false if postponed + std::future FlushPostponedRouterProfileUpdates (); } } diff --git a/libi2pd/Queue.h b/libi2pd/Queue.h index 441f8c3a..0e3e4fde 100644 --- a/libi2pd/Queue.h +++ b/libi2pd/Queue.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,8 +9,7 @@ #ifndef QUEUE_H__ #define QUEUE_H__ -#include -#include +#include #include #include #include @@ -29,22 +28,20 @@ namespace util void Put (Element e) { std::unique_lock l(m_QueueMutex); - m_Queue.push (std::move(e)); + m_Queue.push_back (std::move(e)); m_NonEmpty.notify_one (); } - templateclass Container, typename... R> - void Put (const Container& vec) + void Put (std::list& list) { - if (!vec.empty ()) + if (!list.empty ()) { std::unique_lock l(m_QueueMutex); - for (const auto& it: vec) - m_Queue.push (std::move(it)); + m_Queue.splice (m_Queue.end (), list); m_NonEmpty.notify_one (); - } - } - + } + } + Element GetNext () { std::unique_lock l(m_QueueMutex); @@ -87,7 +84,7 @@ namespace util return m_Queue.empty (); } - int GetSize () + int GetSize () const { std::unique_lock l(m_QueueMutex); return m_Queue.size (); @@ -107,15 +104,28 @@ namespace util return GetNonThreadSafe (true); } - private: + void GetWholeQueue (std::list& queue) + { + if (!queue.empty ()) + { + std::list newQueue; + queue.swap (newQueue); + } + { + std::unique_lock l(m_QueueMutex); + m_Queue.swap (queue); + } + } + private: + Element GetNonThreadSafe (bool peek = false) { if (!m_Queue.empty ()) { auto el = m_Queue.front (); if (!peek) - m_Queue.pop (); + m_Queue.pop_front (); return el; } return nullptr; @@ -123,8 +133,8 @@ namespace util private: - std::queue m_Queue; - std::mutex m_QueueMutex; + std::list m_Queue; + mutable std::mutex m_QueueMutex; std::condition_variable m_NonEmpty; }; } diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 4c23b4cc..e58e898b 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -26,6 +26,7 @@ #include "HTTP.h" #include "util.h" #include "Config.h" +#include "Socks5.h" namespace i2p { @@ -153,7 +154,7 @@ namespace data return ProcessSU3Stream (s); else { - LogPrint (eLogError, "Reseed: Can't open file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open file ", filename); return 0; } } @@ -170,7 +171,7 @@ namespace data } else { - LogPrint (eLogError, "Reseed: Can't open file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open file ", filename); return 0; } } @@ -278,7 +279,7 @@ namespace data if (verify) // not verified { - LogPrint (eLogError, "Reseed: SU3 verification failed"); + LogPrint (eLogCritical, "Reseed: SU3 verification failed"); return 0; } @@ -320,7 +321,7 @@ namespace data uint16_t fileNameLength, extraFieldLength; s.read ((char *)&fileNameLength, 2); fileNameLength = le16toh (fileNameLength); - if ( fileNameLength > 255 ) { + if ( fileNameLength >= 255 ) { // too big LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); return numFiles; @@ -492,7 +493,7 @@ namespace data SSL_free (ssl); } else - LogPrint (eLogError, "Reseed: Can't open certificate file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open certificate file ", filename); SSL_CTX_free (ctx); } @@ -534,24 +535,24 @@ namespace data } // check for valid proxy url schema if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { - LogPrint(eLogError, "Reseed: Bad proxy url: ", proxy); + LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); return ""; } } else { - LogPrint(eLogError, "Reseed: Bad proxy url: ", proxy); + LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); return ""; } } i2p::http::URL url; if (!url.parse(address)) { - LogPrint(eLogError, "Reseed: Failed to parse url: ", address); + LogPrint(eLogCritical, "Reseed: Failed to parse url: ", address); return ""; } url.schema = "https"; if (!url.port) url.port = 443; - boost::asio::io_service service; + boost::asio::io_context service; boost::system::error_code ecode; boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); @@ -561,11 +562,10 @@ namespace data if(proxyUrl.schema.size()) { // proxy connection - auto it = boost::asio::ip::tcp::resolver(service).resolve ( - boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode); + auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode); if(!ecode) { - s.lowest_layer().connect(*it, ecode); + s.lowest_layer().connect(*it.begin (), ecode); if(!ecode) { auto & sock = s.next_layer(); @@ -598,7 +598,7 @@ namespace data LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message()); return ""; } - if(proxyRes.parse(boost::asio::buffer_cast(readbuf.data()), readbuf.size()) <= 0) + if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0) { sock.close(); LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply"); @@ -615,62 +615,21 @@ namespace data { // assume socks if not http, is checked before this for other types // TODO: support username/password auth etc - uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00}; - uint8_t hs_readbuf[2]; - boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(), ecode); - if(ecode) + bool success = false; + i2p::transport::Socks5Handshake (sock, std::make_pair(url.host, url.port), + [&success](const boost::system::error_code& ec) + { + if (!ec) + success = true; + else + LogPrint (eLogError, "Reseed: SOCKS handshake failed: ", ec.message()); + }); + service.run (); // execute all async operations + if (!success) { sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message()); return ""; - } - boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode); - if(ecode) - { - sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message()); - return ""; - } - size_t sz = 0; - uint8_t buf[256]; - - buf[0] = 0x05; - buf[1] = 0x01; - buf[2] = 0x00; - buf[3] = 0x03; - sz += 4; - size_t hostsz = url.host.size(); - if(1 + 2 + hostsz + sz > sizeof(buf)) - { - sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host); - return ""; - } - buf[4] = (uint8_t) hostsz; - memcpy(buf+5, url.host.c_str(), hostsz); - sz += hostsz + 1; - htobe16buf(buf+sz, url.port); - sz += 2; - boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode); - if(ecode) - { - sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message()); - return ""; - } - boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode); - if(ecode) - { - sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message()); - return ""; - } - if(buf[1] != 0x00) - { - sock.close(); - LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1])); - return ""; - } + } } } } @@ -678,26 +637,32 @@ namespace data else { // direct connection - auto it = boost::asio::ip::tcp::resolver(service).resolve ( - boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); + auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); if (!ecode) { bool connected = false; - boost::asio::ip::tcp::resolver::iterator end; - while (it != end) + for (const auto& it: endpoints) { - boost::asio::ip::tcp::endpoint ep = *it; - if ((ep.address ().is_v4 () && i2p::context.SupportsV4 ()) || - (ep.address ().is_v6 () && i2p::context.SupportsV6 ())) + boost::asio::ip::tcp::endpoint ep = it; + bool supported = false; + if (!ep.address ().is_unspecified ()) + { + if (ep.address ().is_v4 ()) + supported = i2p::context.SupportsV4 (); + else if (ep.address ().is_v6 ()) + supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ? + i2p::context.SupportsMesh () : i2p::context.SupportsV6 (); + } + if (supported) { s.lowest_layer().connect (ep, ecode); if (!ecode) { + LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); connected = true; break; } } - it++; } if (!connected) { @@ -777,20 +742,44 @@ namespace data if (!url.port) url.port = 80; boost::system::error_code ecode; - boost::asio::io_service service; + boost::asio::io_context service; boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); - if (url.host.length () < 2) return ""; // assume [] - auto host = url.host.substr (1, url.host.length () - 2); - LogPrint (eLogDebug, "Reseed: Connecting to Yggdrasil ", url.host, ":", url.port); - s.connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::from_string (host), url.port), ecode); + auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); if (!ecode) { - LogPrint (eLogDebug, "Reseed: Connected to Yggdrasil ", url.host, ":", url.port); + bool connected = false; + for (const auto& it: endpoints) + { + boost::asio::ip::tcp::endpoint ep = it; + if ( + i2p::util::net::IsYggdrasilAddress (ep.address ()) && + i2p::context.SupportsMesh () + ) + { + LogPrint (eLogDebug, "Reseed: Yggdrasil: Resolved to ", ep.address ()); + s.connect (ep, ecode); + if (!ecode) + { + connected = true; + break; + } + } + } + if (!connected) + { + LogPrint(eLogError, "Reseed: Yggdrasil: Failed to connect to ", url.host); + return ""; + } + } + + if (!ecode) + { + LogPrint (eLogDebug, "Reseed: Yggdrasil: Connected to ", url.host, ":", url.port); return ReseedRequest (s, url.to_string()); } else - LogPrint (eLogError, "Reseed: Couldn't connect to Yggdrasil ", url.host, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); return ""; } diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 77d3763d..33fb5487 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,6 +20,9 @@ #include "Log.h" #include "Family.h" #include "ECIESX25519AEADRatchetSession.h" +#include "Transports.h" +#include "Tunnel.h" +#include "CryptoKey.h" #include "RouterContext.h" namespace i2p @@ -29,14 +32,17 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), - m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), m_NetID (I2PD_NET_ID) + m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), + m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID), + m_PublishReplyToken (0), m_IsHiddenMode (false), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL), m_IsSaving (false) { } void RouterContext::Init () { - srand (i2p::util::GetMillisecondsSinceEpoch () % 1000); - m_StartupTime = std::chrono::steady_clock::now(); + srand (m_Rng () % 1000); + m_StartupTime = i2p::util::GetMonotonicSeconds (); if (!Load ()) CreateNewRouter (); @@ -47,6 +53,40 @@ namespace i2p m_ECIESSession = std::make_shared(m_InitialNoiseState); } + void RouterContext::Start () + { + if (!m_Service) + { + m_Service.reset (new RouterService); + m_Service->Start (); + m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ())); + ScheduleInitialPublish (); + m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ())); + ScheduleCongestionUpdate (); + m_CleanupTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ())); + ScheduleCleanupTimer (); + } + } + + void RouterContext::Stop () + { + if (m_Service) + { + if (m_PublishTimer) + m_PublishTimer->cancel (); + if (m_CongestionUpdateTimer) + m_CongestionUpdateTimer->cancel (); + m_Service->Stop (); + CleanUp (); // GarlicDestination + } + } + + std::shared_ptr RouterContext::CopyRouterInfoBuffer () const + { + std::lock_guard l(m_RouterInfoMutex); + return m_RouterInfo.CopyBuffer (); + } + void RouterContext::CreateNewRouter () { m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, @@ -63,7 +103,6 @@ namespace i2p if (!port) port = SelectRandomPort (); bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ssu; i2p::config::GetOption("ssu", ssu); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); @@ -86,96 +125,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::make_address (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 (ssu) - { - routerInfo.AddSSUAddress (host.c_str(), port, nullptr); - caps |= i2p::data::RouterInfo::eReachable; // R } 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 = ssu ? (port + 1) : 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::make_address (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::make_address (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 (ssu) - { - routerInfo.AddSSUAddress (host.c_str(), port, nullptr); - caps |= i2p::data::RouterInfo::eReachable; // R - } 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 = ssu ? (port + 1) : 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::make_address (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); } } } @@ -186,27 +232,59 @@ 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); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + m_RouterInfo.SetUnreachable (false); } 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; - } - + } + void RouterContext::UpdateRouterInfo () { - m_RouterInfo.CreateBuffer (m_Keys); - m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO)); + std::shared_ptr buffer; + { + std::lock_guard l(m_RouterInfoMutex); + m_RouterInfo.CreateBuffer (m_Keys); + buffer = m_RouterInfo.CopyBuffer (); + } + { + // update save buffer to latest + std::lock_guard l(m_SaveBufferMutex); + m_SaveBuffer = buffer; + } + bool isSaving = false; + if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved + { + auto savingRouterInfo = std::async (std::launch::async, [this]() + { + std::shared_ptr buffer; + while (m_SaveBuffer) + { + { + std::lock_guard l(m_SaveBufferMutex); + buffer = m_SaveBuffer; + m_SaveBuffer = nullptr; + } + if (buffer) + i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer); + } + m_IsSaving = false; + }); + } m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } @@ -236,19 +314,34 @@ namespace i2p fk.write ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); } - bool RouterContext::IsSSU2Only () const + void RouterContext::SetTesting (bool testing) { - auto transports = m_RouterInfo.GetCompatibleTransports (false); - return (transports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && - !(transports & (i2p::data::RouterInfo::eSSUV4 | i2p::data::RouterInfo::eSSUV6)); + if (testing != m_Testing) + { + m_Testing = testing; + if (m_Testing) + m_Error = eRouterErrorNone; + } + } + + void RouterContext::SetTestingV6 (bool testing) + { + if (testing != m_TestingV6) + { + m_TestingV6 = testing; + if (m_TestingV6) + m_ErrorV6 = eRouterErrorNone; + } } void RouterContext::SetStatus (RouterStatus status) { + SetTesting (false); if (status != m_Status) { + LogPrint(eLogInfo, "Router: network status v4 changed ", + ROUTER_STATUS_NAMES[m_Status], " -> ", ROUTER_STATUS_NAMES[status]); m_Status = status; - m_Error = eRouterErrorNone; switch (m_Status) { case eRouterStatusOK: @@ -257,24 +350,26 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; + case eRouterStatusMesh: + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable); + break; + case eRouterStatusProxy: + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable); + break; default: ; } } } - void RouterContext::SetStatusSSU2 (RouterStatus status) - { - if (IsSSU2Only ()) - SetStatus (status); - } - void RouterContext::SetStatusV6 (RouterStatus status) { + SetTestingV6 (false); if (status != m_StatusV6) { + LogPrint(eLogInfo, "Router: network status v6 changed ", + ROUTER_STATUS_NAMES[m_StatusV6], " -> ", ROUTER_STATUS_NAMES[status]); m_StatusV6 = status; - m_ErrorV6 = eRouterErrorNone; switch (m_StatusV6) { case eRouterStatusOK: @@ -289,18 +384,14 @@ namespace i2p } } - void RouterContext::SetStatusV6SSU2 (RouterStatus status) - { - if (IsSSU2Only ()) - SetStatusV6 (status); - } - void RouterContext::UpdatePort (int port) { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + for (auto& address : *addresses) { - if (address->port != port && (address->transportStyle == i2p::data::RouterInfo::eTransportSSU || IsSSU2Only ())) + if (address && address->port != port) { address->port = port; updated = true; @@ -310,85 +401,89 @@ 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 : m_RouterInfo.GetAddresses ()) + 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 () { - auto& addresses = m_RouterInfo.GetAddresses (); - bool found = false, updated = false; - for (auto it = addresses.begin (); it != addresses.end ();) + if (!m_NTCP2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + 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) { if (!m_SSU2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; int newPort = 0; if (!port) { - for (const auto& address : m_RouterInfo.GetAddresses ()) - if (address->port) + for (const auto& address : *addresses) + if (address && address->port) { newPort = address->port; break; - } + } if (!newPort) newPort = SelectRandomPort (); - } + } bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + 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; @@ -405,106 +500,80 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::UpdateSSU2Address (bool enable) + void RouterContext::UpdateSSU2Keys () { - auto& addresses = m_RouterInfo.GetAddresses (); - bool found = false, updated = false; - for (auto it = addresses.begin (); it != addresses.end ();) + if (!m_SSU2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + 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) { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + 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)) + 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"); - } - if (mtu && !address->IsSSU2 ()) // SSU1 - mtu = (mtu >> 4) << 4; // round to multiple of 16 - 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 (); } - bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer) - { - bool ret = m_RouterInfo.AddIntroducer (introducer); - if (ret) - UpdateRouterInfo (); - return ret; - } - - void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) - { - if (m_RouterInfo.RemoveIntroducer (e)) - UpdateRouterInfo (); - } - bool RouterContext::AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4) { - if (!IsSSU2Only ()) return false; bool ret = m_RouterInfo.AddSSU2Introducer (introducer, v4); if (ret) UpdateRouterInfo (); @@ -513,34 +582,34 @@ namespace i2p void RouterContext::RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4) { - if (!IsSSU2Only ()) return; if (m_RouterInfo.RemoveSSU2Introducer (h, v4)) UpdateRouterInfo (); } - void RouterContext::ClearSSU2Introducers (bool v4) + void RouterContext::UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) { - bool updated = false; - auto& addresses = m_RouterInfo.GetAddresses (); - 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) + if (m_RouterInfo.UpdateSSU2Introducer (h, v4, iTag, iExp)) UpdateRouterInfo (); } + void RouterContext::ClearSSU2Introducers (bool v4) + { + auto addr = m_RouterInfo.GetSSU2Address (v4); + if (addr && !addr->ssu->introducers.empty ()) + { + addr->ssu->introducers.clear (); + UpdateRouterInfo (); + } + } + void RouterContext::SetFloodfill (bool floodfill) { m_IsFloodfill = floodfill; if (floodfill) - m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateFloodfillProperty (true); else { - m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateFloodfillProperty (false); // we don't publish number of routers and leaseset for non-floodfill m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS); m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS); @@ -577,15 +646,15 @@ namespace i2p /* detect parameters */ switch (L) { - case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break; - case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break; - case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break; - case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break; - case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break; - case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break; + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break; + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low; break; // 48 + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH3 : limit = 64; type = low; break; + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH4 : limit = 128; type = low; break; + case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high; break; // 256 + case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = i2p::data::EXTRA_BANDWIDTH_LIMIT; type = extra; break; // 2048 case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s default: - limit = 48; type = low; + limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low; // 48 } /* update caps & flags in RI */ auto caps = m_RouterInfo.GetCaps (); @@ -596,9 +665,7 @@ namespace i2p case low : /* not set */; break; case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P' case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif // no break here, extra + high means 'X' case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } @@ -609,12 +676,12 @@ namespace i2p void RouterContext::SetBandwidth (int limit) { - if (limit > 2000) { SetBandwidth('X'); } - else if (limit > 256) { SetBandwidth('P'); } - else if (limit > 128) { SetBandwidth('O'); } - else if (limit > 64) { SetBandwidth('N'); } - else if (limit > 48) { SetBandwidth('M'); } - else if (limit > 12) { SetBandwidth('L'); } + if (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); } + else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); } + else if (limit > 128) { SetBandwidth('O'); } + else if (limit > 64) { SetBandwidth('N'); } + else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); } + else if (limit > 12) { SetBandwidth('L'); } else { SetBandwidth('K'); } m_BandwidthLimit = limit; // set precise limit } @@ -631,50 +698,6 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } - void RouterContext::RemoveNTCPAddress (bool v4only) - { - bool updated = false; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto it = addresses.begin (); it != addresses.end ();) - { - if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () && - (!v4only || (*it)->host.is_v4 ())) - { - it = addresses.erase (it); - updated = true; - if (v4only) break; // otherwise might be more than one address - } - else - ++it; - } - if (updated) - m_RouterInfo.UpdateSupportedTransports (); - } - - void RouterContext::RemoveSSUAddress () - { - bool updated = false; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto it = addresses.begin (); it != addresses.end ();) - { - if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportSSU) - { - it = addresses.erase (it); - updated = true; - } - else - ++it; - } - if (updated) - m_RouterInfo.UpdateSupportedTransports (); - } - - void RouterContext::SetUnreachableSSU2 (bool v4, bool v6) - { - if (IsSSU2Only ()) - SetUnreachable (v4, v6); - } - void RouterContext::SetUnreachable (bool v4, bool v6) { if (v4 || (v6 && !SupportsV4 ())) @@ -689,16 +712,18 @@ namespace i2p } uint16_t port = 0; // delete previous introducers - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr : addresses) - if (addr->ssu && (!addr->IsSSU2 () || IsSSU2Only ()) && - ((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; - } + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) + { + for (auto& addr : *addresses) + 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) @@ -722,20 +747,19 @@ namespace i2p } uint16_t port = 0; // delete previous introducers - bool isSSU2Published = IsSSU2Only (); // TODO - if (isSSU2Published) - i2p::config::GetOption ("ssu2.published", isSSU2Published); - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr : addresses) - if (addr->ssu && (!addr->IsSSU2 () || isSSU2Published) && - ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) - { - addr->published = true; - addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; - addr->ssu->introducers.clear (); - if (addr->port && (!addr->IsSSU2 () || IsSSU2Only ())) - port = addr->port; - } + bool isSSU2Published; i2p::config::GetOption ("ssu2.published", isSSU2Published); + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) + { + for (auto& addr : *addresses) + 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) @@ -758,81 +782,98 @@ namespace i2p if (supportsV6) { // insert v6 addresses if necessary - bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false; + bool foundNTCP2 = false, foundSSU2 = false; uint16_t port = 0; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) { - if (addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) + for (auto& addr: *addresses) { - switch (addr->transportStyle) + if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) { - case i2p::data::RouterInfo::eTransportSSU: - foundSSU = true; - break; - case i2p::data::RouterInfo::eTransportNTCP: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; + switch (addr->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; + } } + if (addr) port = addr->port; } - port = addr->port; } - if (!port) - { + if (!port) + { i2p::config::GetOption("port", port); if (!port) port = SelectRandomPort (); - } - // SSU - bool ssu; i2p::config::GetOption("ssu", ssu); - if (!foundSSU && ssu) - { - std::string host = "::1"; // TODO: read host - m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); } // 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::make_address (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 = ssu ? (port + 1) : 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::make_address (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 (); @@ -841,81 +882,97 @@ namespace i2p void RouterContext::SetSupportsV4 (bool supportsV4) { - // check if updates - if (supportsV4 && SupportsV4 ()) return; - if (!supportsV4 && !SupportsV4 ()) return; - // update if (supportsV4) { - bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false; - std::string host = "127.0.0.1"; + bool foundNTCP2 = false, foundSSU2 = false; uint16_t port = 0; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) { - if (addr->IsV4 ()) + for (auto& addr: *addresses) { - switch (addr->transportStyle) + if (addr && addr->IsV4 ()) { - case i2p::data::RouterInfo::eTransportSSU: - foundSSU = true; - break; - case i2p::data::RouterInfo::eTransportNTCP: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; + switch (addr->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; + } } + if (addr && addr->port) port = addr->port; } - if (addr->port) port = addr->port; } - if (!port) - { + if (!port) + { i2p::config::GetOption("port", port); if (!port) port = SelectRandomPort (); - } - // SSU - bool ssu; i2p::config::GetOption("ssu", ssu); - if (!foundSSU && ssu) - m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); - + } // 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::make_address (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 = ssu ? (port + 1) : 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::make_address (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 (); @@ -926,23 +983,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 (); - for (auto& addr: addresses) + if (!port) { - if (!port) port = addr->port; - if (i2p::util::net::IsYggdrasilAddress (addr->host)) + for (auto& addr: *addresses) { - foundMesh = true; - break; + if (addr && addr->port) + { + 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 (); @@ -952,64 +1012,32 @@ namespace i2p void RouterContext::SetMTU (int mtu, bool v4) { if (mtu < 1280 || mtu > 1500) return; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + auto addresses = m_RouterInfo.GetAddresses (); + 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 ()))) { - if (!addr->IsSSU2 ()) // SSU1 - { - // round to multiple of 16 - if (v4) - { - if (mtu > 1484) mtu = 1484; - else - { - mtu -= 12; - mtu = (mtu >> 4) << 4; - mtu += 12; - } - } - else - { - if (mtu > 1488) mtu = 1488; - else - mtu = (mtu >> 4) << 4; - } - } - if (mtu) - { - addr->ssu->mtu = mtu; - LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); - } + addr->ssu->mtu = mtu; + LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); } } } void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { - bool isYgg = i2p::util::net::IsYggdrasilAddress (host); - bool updated = false; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + 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 () @@ -1077,6 +1105,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)); @@ -1097,40 +1139,28 @@ 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 (m_RouterInfo.UpdateCongestion (i2p::data::RouterInfo::eLowCongestion)) + updated = true; + if (updated) + UpdateRouterInfo (); return true; } @@ -1151,23 +1181,45 @@ namespace i2p return i2p::tunnel::tunnels.GetExploratoryPool (); } + int RouterContext::GetCongestionLevel (bool longTerm) const + { + return std::max ( + i2p::tunnel::tunnels.GetCongestionLevel (), + i2p::transport::transports.GetCongestionLevel (longTerm) + ); + } + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } - bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) { + if (typeID == eI2NPTunnelTest) + { + // try tunnel test + auto pool = GetTunnelPool (); + if (pool && pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET))) + return true; + } 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); + if (m_Service) + boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostGarlicMessage, this, msg)); + else + LogPrint (eLogError, "Router: service is NULL"); + } + + void RouterContext::PostGarlicMessage (std::shared_ptr msg) + { uint8_t * buf = msg->GetPayload (); uint32_t len = bufbe32toh (buf); if (len > msg->GetLength ()) @@ -1184,28 +1236,52 @@ namespace i2p else LogPrint (eLogError, "Router: Session is not set for ECIES router"); } - } - + } + void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - if (i2p::data::netdb.GetPublishReplyToken () == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) - i2p::data::netdb.PostI2NPMsg (msg); + if (m_Service) + boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg)); else - { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); - } + LogPrint (eLogError, "Router: service is NULL"); } - void RouterContext::CleanupDestination () + void RouterContext::PostDeliveryStatusMessage (std::shared_ptr msg) { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::CleanupExpiredTags (); + if (m_PublishReplyToken == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) + { + LogPrint (eLogInfo, "Router: Publishing confirmed. reply token=", m_PublishReplyToken); + m_PublishExcluded.clear (); + m_PublishReplyToken = 0; + SchedulePublish (); + } + else + i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); } + void RouterContext::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) + { + if (m_Service) + { + struct + { + uint8_t k[32]; + uint64_t t; + } data; + memcpy (data.k, key, 32); + data.t = tag; + boost::asio::post (m_Service->GetService (), [this,data](void) + { + AddECIESx25519Key (data.k, data.t); + }); + } + else + LogPrint (eLogError, "Router: service is NULL"); + } + uint32_t RouterContext::GetUptime () const { - return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); + return i2p::util::GetMonotonicSeconds () - m_StartupTime; } bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const @@ -1275,4 +1351,208 @@ namespace i2p } return *m_SSU2StaticKeys; } + + void RouterContext::ScheduleInitialPublish () + { + if (m_PublishTimer) + { + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_INITIAL_PUBLISH_INTERVAL)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandleInitialPublishTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandleInitialPublishTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_RouterInfo.IsReachableBy (i2p::data::RouterInfo::eAllTransports)) + { + UpdateCongestion (); + HandlePublishTimer (ecode); + } + else + { + UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + ScheduleInitialPublish (); + } + } + } + + void RouterContext::SchedulePublish () + { + if (m_PublishTimer) + { + m_PublishTimer->cancel (); + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL + + m_Rng () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandlePublishTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + if (!m_IsHiddenMode) + { + m_PublishExcluded.clear (); + m_PublishReplyToken = 0; + if (IsFloodfill ()) + { + UpdateStats (); // for floodfill + m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves + } + Publish (); + SchedulePublishResend (); + } + else + SchedulePublish (); + } + } + + void RouterContext::Publish () + { + if (!i2p::transport::transports.IsOnline ()) return; + if (m_PublishExcluded.size () > ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS) + { + LogPrint (eLogError, "Router: Couldn't publish our RouterInfo to ", ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS, " closest routers. Try again"); + m_PublishExcluded.clear (); + UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + } + + auto floodfill = i2p::data::netdb.GetClosestFloodfill (i2p::context.GetIdentHash (), m_PublishExcluded); + if (floodfill) + { + uint32_t replyToken; + RAND_bytes ((uint8_t *)&replyToken, 4); + LogPrint (eLogInfo, "Router: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); + auto onDrop = [this]() + { + if (m_Service) + boost::asio::post (m_Service->GetService (), [this]() { HandlePublishResendTimer (boost::system::error_code ()); }); + }; + if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()) || // already connected + (floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && // are we able to connect + !i2p::transport::transports.RoutesRestricted ())) // and routes not restricted + { + // send directly + auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken); + msg->onDrop = onDrop; + i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), msg); + } + else + { + // otherwise through exploratory + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; + if (inbound && outbound) + { + // encrypt for floodfill + auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound); + msg->onDrop = onDrop; + outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, + i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ())); + } + else + LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnels. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " milliseconds"); + } + m_PublishExcluded.insert (floodfill->GetIdentHash ()); + m_PublishReplyToken = replyToken; + } + else + LogPrint (eLogInfo, "Router: Can't find floodfill to publish our RouterInfo"); + } + + void RouterContext::SchedulePublishResend () + { + if (m_PublishTimer) + { + m_PublishTimer->cancel (); + m_PublishTimer->expires_from_now (boost::posix_time::milliseconds(ROUTER_INFO_CONFIRMATION_TIMEOUT)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandlePublishResendTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + i2p::context.UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + Publish (); + SchedulePublishResend (); + } + } + + void RouterContext::ScheduleCongestionUpdate () + { + if (m_CongestionUpdateTimer) + { + m_CongestionUpdateTimer->cancel (); + m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds( + ROUTER_INFO_CONGESTION_UPDATE_INTERVAL + m_Rng () % ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE)); + m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Congestion update timer is NULL"); + } + + void RouterContext::HandleCongestionUpdateTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + UpdateCongestion (); + ScheduleCongestionUpdate (); + } + } + + void RouterContext::UpdateCongestion () + { + auto c = i2p::data::RouterInfo::eLowCongestion; + if (!AcceptsTunnels () || !m_ShareRatio) + c = i2p::data::RouterInfo::eRejectAll; + else + { + int congestionLevel = GetCongestionLevel (true); + if (congestionLevel > CONGESTION_LEVEL_HIGH) + c = i2p::data::RouterInfo::eHighCongestion; + else if (congestionLevel > CONGESTION_LEVEL_MEDIUM) + c = i2p::data::RouterInfo::eMediumCongestion; + } + if (m_RouterInfo.UpdateCongestion (c)) + UpdateRouterInfo (); + } + + void RouterContext::ScheduleCleanupTimer () + { + if (m_CleanupTimer) + { + m_CleanupTimer->cancel (); + m_CleanupTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CLEANUP_INTERVAL)); + m_CleanupTimer->async_wait (std::bind (&RouterContext::HandleCleanupTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Cleanup timer is NULL"); + } + + void RouterContext::HandleCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + CleanupExpiredTags (); + ScheduleCleanupTimer (); + } + } } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index b6d3e7f0..754c49da 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,12 +12,13 @@ #include #include #include -#include -#include +#include +#include #include #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" +#include "util.h" namespace i2p { @@ -30,17 +31,32 @@ namespace garlic const char ROUTER_KEYS[] = "router.keys"; const char NTCP2_KEYS[] = "ntcp2.keys"; const char SSU2_KEYS[] = "ssu2.keys"; - const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes + const int ROUTER_INFO_UPDATE_INTERVAL = 30*60; // 30 minutes + const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds + const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds + const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds + const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 1600; // in milliseconds + const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; + const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 11*60; // in seconds + const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE = 130; // in seconds + const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds enum RouterStatus { eRouterStatusOK = 0, - eRouterStatusTesting = 1, - eRouterStatusFirewalled = 2, - eRouterStatusError = 3, - eRouterStatusUnknown = 4, - eRouterStatusProxy = 5, - eRouterStatusMesh = 6 + eRouterStatusFirewalled = 1, + eRouterStatusUnknown = 2, + eRouterStatusProxy = 3, + eRouterStatusMesh = 4 + }; + + const char* const ROUTER_STATUS_NAMES[] = + { + "OK", // 0 + "Firewalled", // 1 + "Unknown", // 2 + "Proxy", // 3 + "Mesh" // 4 }; enum RouterError @@ -48,7 +64,9 @@ namespace garlic eRouterErrorNone = 0, eRouterErrorClockSkew = 1, eRouterErrorOffline = 2, - eRouterErrorSymmetricNAT = 3 + eRouterErrorSymmetricNAT = 3, + eRouterErrorFullConeNAT = 4, + eRouterErrorNoDescriptors = 5 }; class RouterContext: public i2p::garlic::GarlicDestination @@ -69,11 +87,23 @@ namespace garlic uint8_t intro[32]; }; + class RouterService: public i2p::util::RunnableServiceWithWork + { + public: + + RouterService (): RunnableServiceWithWork ("Router") {}; + auto& GetService () { return GetIOService (); }; + void Start () { StartIOService (); }; + void Stop () { StopIOService (); }; + }; + public: RouterContext (); void Init (); - + void Start (); + void Stop (); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; i2p::data::LocalRouterInfo& GetRouterInfo () { return m_RouterInfo; }; std::shared_ptr GetSharedRouterInfo () @@ -86,7 +116,8 @@ namespace garlic return std::shared_ptr (this, [](i2p::garlic::GarlicDestination *) {}); } - + std::shared_ptr CopyRouterInfoBuffer () const; + const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; }; const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; }; const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; }; @@ -101,37 +132,33 @@ namespace garlic uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; }; uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; }; + bool GetTesting () const { return m_Testing; }; + void SetTesting (bool testing); RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); - void SetStatusSSU2 (RouterStatus status); RouterError GetError () const { return m_Error; }; - void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; + void SetError (RouterError error) { m_Error = error; }; + bool GetTestingV6 () const { return m_TestingV6; }; + void SetTestingV6 (bool testing); RouterStatus GetStatusV6 () const { return m_StatusV6; }; void SetStatusV6 (RouterStatus status); - void SetStatusV6SSU2 (RouterStatus status); RouterError GetErrorV6 () const { return m_ErrorV6; }; - void SetErrorV6 (RouterError error) { m_StatusV6 = eRouterStatusError; m_ErrorV6 = error; }; + void SetErrorV6 (RouterError error) { m_ErrorV6 = error; }; int GetNetID () const { return m_NetID; }; void SetNetID (int netID) { m_NetID = netID; }; bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); 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); - void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later - void RemoveSSUAddress (); // delete SSU address for older routers - bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); - void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4); void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4); + void UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); void ClearSSU2Introducers (bool v4); bool IsUnreachable () const; void SetUnreachable (bool v4, bool v6); - void SetUnreachableSSU2 (bool v4, bool v6); void SetReachable (bool v4, bool v6); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); @@ -142,6 +169,7 @@ namespace garlic void SetShareRatio (int percents); // 0 - 100 bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; + int GetCongestionLevel (bool longTerm) const; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; bool SupportsMesh () const { return m_RouterInfo.IsMesh (); }; @@ -149,32 +177,35 @@ namespace garlic void SetSupportsV4 (bool supportsV4); void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); void SetMTU (int mtu, bool v4); + void SetHidden(bool hide) { m_IsHiddenMode = hide; }; + bool IsHidden() const { return m_IsHiddenMode; }; + bool IsLimitedConnectivity () const { return m_Status == eRouterStatusProxy; }; // TODO: implement other cases i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing - void CleanupDestination (); // garlic destination // implements LocalDestination - std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; - void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; - void SetLeaseSetUpdated () {}; + std::shared_ptr GetIdentity () const override{ return m_Keys.GetPublic (); }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; + void SetLeaseSetUpdated (bool post) override {}; // implements GarlicDestination - std::shared_ptr GetLeaseSet () { return nullptr; }; - std::shared_ptr GetTunnelPool () const; + std::shared_ptr GetLeaseSet () override { return nullptr; }; + std::shared_ptr GetTunnelPool () const override; // override GarlicDestination - void ProcessGarlicMessage (std::shared_ptr msg); - void ProcessDeliveryStatusMessage (std::shared_ptr msg); + void ProcessGarlicMessage (std::shared_ptr msg) override; + void ProcessDeliveryStatusMessage (std::shared_ptr msg) override; + void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override; protected: // implements GarlicDestination - void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); + void HandleI2NPMessage (const uint8_t * buf, size_t len) override; + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, + size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override; private: @@ -183,13 +214,31 @@ namespace garlic void UpdateRouterInfo (); void NewNTCP2Keys (); void NewSSU2Keys (); - bool IsSSU2Only () const; // SSU2 and no SSU + void UpdateNTCP2Keys (); + void UpdateSSU2Keys (); bool Load (); void SaveKeys (); + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; 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); + void PostGarlicMessage (std::shared_ptr msg); + void PostDeliveryStatusMessage (std::shared_ptr msg); + void ScheduleInitialPublish (); + void HandleInitialPublishTimer (const boost::system::error_code& ecode); + void SchedulePublish (); + void HandlePublishTimer (const boost::system::error_code& ecode); + void Publish (); + void SchedulePublishResend (); + void HandlePublishResendTimer (const boost::system::error_code& ecode); + void ScheduleCongestionUpdate (); + void HandleCongestionUpdateTimer (const boost::system::error_code& ecode); + void UpdateCongestion (); + void ScheduleCleanupTimer (); + void HandleCleanupTimer (const boost::system::error_code& ecode); + private: i2p::data::LocalRouterInfo m_RouterInfo; @@ -198,18 +247,29 @@ namespace garlic std::shared_ptr m_ECIESSession; uint64_t m_LastUpdateTime; // in seconds bool m_AcceptsTunnels, m_IsFloodfill; - std::chrono::time_point m_StartupTime; + uint64_t m_StartupTime; // monotonic seconds uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; RouterStatus m_Status, m_StatusV6; RouterError m_Error, m_ErrorV6; + bool m_Testing, m_TestingV6; int m_NetID; - std::mutex m_GarlicMutex; std::unique_ptr m_NTCP2Keys; std::unique_ptr m_SSU2Keys; std::unique_ptr m_NTCP2StaticKeys, m_SSU2StaticKeys; // for ECIESx25519 i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; + // publish + std::unique_ptr m_Service; + std::unique_ptr m_PublishTimer, m_CongestionUpdateTimer, m_CleanupTimer; + std::unordered_set m_PublishExcluded; + uint32_t m_PublishReplyToken; + bool m_IsHiddenMode; // not publish + mutable std::mutex m_RouterInfoMutex; + std::mt19937 m_Rng; + std::shared_ptr m_SaveBuffer; + std::mutex m_SaveBufferMutex; // TODO: make m_SaveBuffer atomic + std::atomic m_IsSaving; }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 7368001d..4af32f57 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,9 +10,10 @@ #include #include "I2PEndian.h" #include -#include -#include -#if (BOOST_VERSION >= 105300) +#include +#include +#include // for boost::to_lower +#ifndef __cpp_lib_atomic_shared_ptr #include #endif #include "version.h" @@ -21,8 +22,10 @@ #include "Base.h" #include "Timestamp.h" #include "Log.h" +#include "Transports.h" #include "NetDb.hpp" #include "RouterContext.h" +#include "CryptoKey.h" #include "RouterInfo.h" namespace i2p @@ -33,33 +36,35 @@ namespace data { if (len > size ()) len = size (); memcpy (data (), buf, len); + m_BufferLen = len; } RouterInfo::RouterInfo (): m_Buffer (nullptr) { - m_Addresses = boost::make_shared(); // create empty list + m_Addresses = AddressesPtr(new Addresses ()); // create empty list } RouterInfo::RouterInfo (const std::string& fullPath): - m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0),m_ReachableTransports (0), - m_Caps (0), m_Version (0) + m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), m_IsFloodfill (false), + m_IsBufferScheduledToDelete (false), m_SupportedTransports (0), + m_ReachableTransports (0), m_PublishedTransports (0), m_Caps (0), m_Version (0), + m_Congestion (eLowCongestion) { - m_Addresses = boost::make_shared(); // create empty list - m_Buffer = NewBuffer (); // always RouterInfo's + m_Addresses = AddressesPtr(new Addresses ()); // create empty list + m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's ReadFromFile (fullPath); } RouterInfo::RouterInfo (std::shared_ptr&& buf, size_t len): - m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), - m_SupportedTransports (0), m_ReachableTransports (0), - m_Caps (0), m_Version (0) + m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), m_IsFloodfill (false), + m_IsBufferScheduledToDelete (false), m_SupportedTransports (0), m_ReachableTransports (0), m_PublishedTransports (0), + m_Caps (0), m_Version (0), m_Congestion (eLowCongestion) { if (len <= MAX_RI_BUFFER_SIZE) { - m_Addresses = boost::make_shared(); // create empty list + m_Addresses = AddressesPtr(new Addresses ()); // create empty list m_Buffer = buf; - m_BufferLen = len; + if (m_Buffer) m_Buffer->SetBufferLen (len); ReadFromBuffer (true); } else @@ -71,7 +76,7 @@ namespace data } RouterInfo::RouterInfo (const uint8_t * buf, size_t len): - RouterInfo (std::make_shared (buf, len), len) + RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len) { } @@ -79,13 +84,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 (); @@ -96,23 +100,24 @@ namespace data m_IsUnreachable = false; m_SupportedTransports = 0; m_ReachableTransports = 0; - m_Caps = 0; + m_PublishedTransports = 0; + m_Caps = 0; m_IsFloodfill = false; // 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)); - ReadFromStream (str); + ReadFromBuffer (buf + identityLen, len - identityLen); + 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) @@ -127,16 +132,17 @@ namespace data if (s.is_open ()) { s.seekg (0,std::ios::end); - m_BufferLen = s.tellg (); - if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE) + size_t bufferLen = s.tellg (); + if (bufferLen < 40 || bufferLen > MAX_RI_BUFFER_SIZE) { - LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed"); + LogPrint(eLogError, "RouterInfo: File ", fullPath, " is malformed"); return false; } s.seekg(0, std::ios::beg); if (!m_Buffer) m_Buffer = NewBuffer (); - s.read((char *)m_Buffer->data (), m_BufferLen); + s.read((char *)m_Buffer->data (), bufferLen); + m_Buffer->SetBufferLen (bufferLen); } else { @@ -161,11 +167,12 @@ namespace data m_IsUnreachable = true; return; } - m_RouterIdentity = std::make_shared(m_Buffer->data (), m_BufferLen); + size_t bufferLen = m_Buffer->GetBufferLen (); + m_RouterIdentity = NewIdentity (m_Buffer->data (), bufferLen); size_t identityLen = m_RouterIdentity->GetFullLen (); - if (identityLen >= m_BufferLen) + if (identityLen >= bufferLen) { - LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", bufferLen); m_IsUnreachable = true; return; } @@ -179,53 +186,45 @@ namespace data return; } // verify signature - int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); + int l = bufferLen - m_RouterIdentity->GetSignatureLen (); if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer->data (), l, (uint8_t *)m_Buffer->data () + l)) { LogPrint (eLogError, "RouterInfo: Signature verification failed"); m_IsUnreachable = true; return; } - m_RouterIdentity->DropVerifier (); } // parse RI - std::stringstream str; - str.write ((const char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen); - ReadFromStream (str); - if (!str) + if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen)) { LogPrint (eLogError, "RouterInfo: Malformed message"); m_IsUnreachable = true; - } + } } - void RouterInfo::ReadFromStream (std::istream& s) + bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len) { - if (!s) return; - m_Caps = 0; - s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); - m_Timestamp = be64toh (m_Timestamp); + if (len < 9) return false; + m_Caps = 0; m_Congestion = eLowCongestion; + m_Timestamp = bufbe64toh (buf); + size_t offset = 8; // timestamp // read addresses - auto addresses = boost::make_shared(); - uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); - addresses->reserve (numAddresses); + auto addresses = NewAddresses (); + uint8_t numAddresses = buf[offset]; offset++; for (int i = 0; i < numAddresses; i++) { + if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date uint8_t supportedTransports = 0; - auto address = std::make_shared

(); - uint8_t cost; // ignore - s.read ((char *)&cost, sizeof (cost)); - s.read ((char *)&address->date, sizeof (address->date)); - bool isHost = false, isIntroKey = false, isStaticKey = false, isV2 = false; - Tag<32> iV2; // for 'i' field in SSU, TODO: remove later - char transportStyle[6]; - ReadString (transportStyle, 6, s); - if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 - address->transportStyle = eTransportNTCP; - else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2 + auto address = NewAddress (); + offset++; // cost, ignore + address->date = bufbe64toh (buf + offset); offset += 8; // date + bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false; + auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1; + if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2 + address->transportStyle = eTransportNTCP2; + else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 { - address->transportStyle = (transportStyle[3] == '2') ? eTransportSSU2 : eTransportSSU; + address->transportStyle = eTransportSSU2; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } @@ -233,70 +232,88 @@ namespace data address->transportStyle = eTransportUnknown; address->caps = 0; address->port = 0; - uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); if (!s) return; - size = be16toh (size); + if (offset + 2 > len) return false; + uint16_t size = bufbe16toh (buf + offset); offset += 2; // size + if (offset + size >= len) return false; if (address->transportStyle == eTransportUnknown) { // skip unknown address - s.seekg (size, std::ios_base::cur); - if (s) continue; else return; + offset += size; + continue; } + size_t r = 0; while (r < size) { - char key[255], value[255]; - r += ReadString (key, 255, s); - s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, 255, s); - s.seekg (1, std::ios_base::cur); r++; // ; - if (!s) return; - if (!strcmp (key, "host")) + auto [key, value, sz] = ExtractParam (buf + offset, len - offset); + r += sz; offset += sz; + if (key.empty ()) continue; + if (key == "host") { boost::system::error_code ecode; - address->host = boost::asio::ip::address::from_string (value, ecode); - if (!ecode && !address->host.is_unspecified ()) isHost = true; + address->host = boost::asio::ip::make_address (value, ecode); + if (!ecode && !address->host.is_unspecified ()) + { + if (!i2p::transport::transports.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")) - address->port = boost::lexical_cast(value); - else if (!strcmp (key, "mtu")) + else if (key == "port") + { + auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); + } + else if (key == "mtu") { if (address->ssu) - address->ssu->mtu = boost::lexical_cast(value); + { + auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ()); + } else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP"); + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); } - else if (!strcmp (key, "key")) - { - if (address->ssu) - isIntroKey = (Base64ToByteStream (value, strlen (value), address->i, 32) == 32); - else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); - } - else if (!strcmp (key, "caps")) + else if (key == "caps") address->caps = ExtractAddressCaps (value); - else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key + else if (key == "s") // ntcp2 or ssu2 static key { - Base64ToByteStream (value, strlen (value), address->s, 32); - isStaticKey = true; + if (Base64ToByteStream (value, address->s, 32) == 32 && + !(address->s[31] & 0x80)) // check if x25519 public key + isStaticKey = true; + else + address->transportStyle = eTransportUnknown; // invalid address } - else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro + else if (key == "i") // ntcp2 iv or ssu2 intro { if (address->IsNTCP2 ()) { - Base64ToByteStream (value, strlen (value), address->i, 16); - address->published = true; // presence of "i" means "published" NTCP2 + if (Base64ToByteStream (value, address->i, 16) == 16) + address->published = true; // presence of "i" means "published" NTCP2 + else + address->transportStyle = eTransportUnknown; // invalid address } else if (address->IsSSU2 ()) - Base64ToByteStream (value, strlen (value), address->i, 32); - else - Base64ToByteStream (value, strlen (value), iV2, 32); + { + if (Base64ToByteStream (value, address->i, 32) == 32) + isIntroKey = true; + else + address->transportStyle = eTransportUnknown; // invalid address + } } - else if (!strcmp (key, "v")) + else if (key == "v") { - if (!strcmp (value, "2")) + if (value == "2") isV2 = true; else + { LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v"); + address->transportStyle = eTransportUnknown; // invalid address + } } else if (key[0] == 'i') { @@ -306,13 +323,11 @@ namespace data LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; } - size_t l = strlen(key); - unsigned char index = key[l-1] - '0'; // TODO: - key[l-1] = 0; + unsigned char index = key[key.length () - 1] - '0'; // TODO: if (index > 9) { LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); - if (s) continue; else return; + continue; } if (index >= address->ssu->introducers.size ()) { @@ -321,36 +336,38 @@ namespace data address->ssu->introducers.resize (index + 1); } Introducer& introducer = address->ssu->introducers.at (index); - if (!strcmp (key, "ihost")) + auto key1 = key.substr(0, key.length () - 1); + if (key1 == "itag") { - boost::system::error_code ecode; - introducer.iHost = boost::asio::ip::address::from_string (value, ecode); + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ()); + } + else if (key1 == "ih") + Base64ToByteStream (value, introducer.iH, 32); + else if (key1 == "iexp") + { + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); } - else if (!strcmp (key, "iport")) - introducer.iPort = boost::lexical_cast(value); - else if (!strcmp (key, "itag")) - introducer.iTag = boost::lexical_cast(value); - else if (!strcmp (key, "ikey") || !strcmp (key, "ih")) - Base64ToByteStream (value, strlen (value), introducer.iKey, 32); - else if (!strcmp (key, "iexp")) - introducer.iExp = boost::lexical_cast(value); } - if (!s) return; - } - if (address->transportStyle == eTransportNTCP) + } + if (address->transportStyle == eTransportNTCP2) { if (isStaticKey) { - if (isHost) + if (isHost && address->port) { if (address->host.is_v6 ()) supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); else supportedTransports |= eNTCP2V4; - m_ReachableTransports |= supportedTransports; + m_PublishedTransports |= supportedTransports; } - else if (!address->published) + else { + address->published = false; if (address->caps) { if (address->caps & AddressCaps::eV4) supportedTransports |= eNTCP2V4; @@ -361,205 +378,154 @@ namespace data } } } - else if (address->transportStyle == eTransportSSU) - { - if (isIntroKey) - { - if (isHost) - supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6; - else if (address->caps & AddressCaps::eV6) - { - supportedTransports |= eSSUV6; - if (address->caps & AddressCaps::eV4) supportedTransports |= eSSUV4; // in additional to v6 - } - else - supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4 - if (address->ssu && !address->ssu->introducers.empty ()) - { - // exclude invalid introducers - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - int numValid = 0; - for (auto& it: address->ssu->introducers) - { - if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; - if (ts <= it.iExp && it.iPort > 0 && - ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) - numValid++; - else - { - it.iPort = 0; - if (isV2) numValid++; - } - } - if (numValid) - m_ReachableTransports |= supportedTransports; - else - address->ssu->introducers.resize (0); - } - else if (isHost && address->port) - { - address->published = true; - m_ReachableTransports |= supportedTransports; - } - } - } - if (address->transportStyle == eTransportSSU2 || (isV2 && address->transportStyle == eTransportSSU)) + else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey) { 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; + if (address->host.is_v4 ()) m_PublishedTransports |= eSSU2V4; + if (address->host.is_v6 ()) m_PublishedTransports |= eSSU2V6; + address->published = true; } - if (address->transportStyle == eTransportSSU2) + else if (address->ssu && !address->ssu->introducers.empty ()) { - if (address->port) address->published = true; - if (address->ssu && !address->ssu->introducers.empty ()) - { - // exclude invalid introducers - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - int numValid = 0; - for (auto& it: address->ssu->introducers) - { - if (it.iTag && ts <= it.iExp) - numValid++; - else - it.iTag = 0; - } - if (numValid) - m_ReachableTransports |= supportedTransports; - else - address->ssu->introducers.resize (0); - } - } - else - { - // create additional SSU2 address. TODO: remove later - auto ssu2addr = std::make_shared
(); - ssu2addr->transportStyle = eTransportSSU2; - ssu2addr->host = address->host; ssu2addr->port = address->port; - ssu2addr->s = address->s; ssu2addr->i = iV2; - ssu2addr->date = address->date; ssu2addr->caps = address->caps; - ssu2addr->published = address->published; - ssu2addr->ssu.reset (new SSUExt ()); ssu2addr->ssu->mtu = address->ssu->mtu; + // exclude invalid introducers uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - if (!address->ssu->introducers.empty ()) - { - for (const auto& introducer: address->ssu->introducers) - if (!introducer.iPort && introducer.iHost.is_unspecified () && ts < introducer.iExp) // SSU2 - ssu2addr->ssu->introducers.push_back (introducer); - if (!ssu2addr->ssu->introducers.empty ()) - m_ReachableTransports |= supportedTransports; - } - addresses->push_back(ssu2addr); - } + UpdateIntroducers (address, ts); + if (!address->ssu->introducers.empty ()) // still has something + m_ReachableTransports |= supportedTransports; + } } 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; } } -#if (BOOST_VERSION >= 105300) + m_ReachableTransports |= m_PublishedTransports; + // update addresses +#ifdef __cpp_lib_atomic_shared_ptr + m_Addresses = addresses; +#else boost::atomic_store (&m_Addresses, addresses); -#else - m_Addresses = addresses; // race condition #endif // read peers - uint8_t numPeers; - s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; - s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers + if (offset + 1 > len) return false; + uint8_t numPeers = buf[offset]; offset++; // num peers + offset += numPeers*32; // TODO: read peers // read properties + if (offset + 2 > len) return false; m_Version = 0; bool isNetId = false; std::string family; - uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); if (!s) return; - size = be16toh (size); + uint16_t size = bufbe16toh (buf + offset); offset += 2; // size + if (offset + size > len) return false; + size_t r = 0; while (r < size) { - char key[255], value[255]; - r += ReadString (key, 255, s); - s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, 255, s); - s.seekg (1, std::ios_base::cur); r++; // ; - if (!s) return; + auto [key, value, sz] = ExtractParam (buf + offset, len - offset); + r += sz; offset += sz; + if (key.empty ()) continue; SetProperty (key, value); // extract caps - if (!strcmp (key, "caps")) + if (key == "caps") + { ExtractCaps (value); + m_IsFloodfill = IsDeclaredFloodfill (); + } // extract version - else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION)) + else if (key == ROUTER_INFO_PROPERTY_VERSION) { m_Version = 0; - char * ch = value; - while (*ch) + for (auto ch: value) { - if (*ch >= '0' && *ch <= '9') + if (ch >= '0' && ch <= '9') { m_Version *= 10; - m_Version += (*ch - '0'); + m_Version += (ch - '0'); } - ch++; } + if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6))) + { + auto addresses = GetAddresses (); + if (addresses) + { + if ((*addresses)[eSSU2V4Idx]) (*addresses)[eSSU2V4Idx]->caps &= ~eSSUTesting; + if ((*addresses)[eSSU2V6Idx]) (*addresses)[eSSU2V6Idx]->caps &= ~eSSUTesting; + } + } } // check netId - else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID)) + else if (key == ROUTER_INFO_PROPERTY_NETID) { isNetId = true; - if (atoi (value) != i2p::context.GetNetID ()) + int netID; + auto res = std::from_chars(value.data(), value.data() + value.size(), netID); + if (res.ec != std::errc() || netID != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } } // family - else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) + else if (key == ROUTER_INFO_PROPERTY_FAMILY) { family = value; boost::to_lower (family); } - else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) + else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG) { - if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) + if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); else + { LogPrint (eLogWarning, "RouterInfo: Family ", family, " signature verification failed"); + SetUnreachable (true); + } } - - if (!s) return; } if (!m_SupportedTransports || !isNetId || !m_Version) SetUnreachable (true); - } - + + return true; + } + bool RouterInfo::IsFamily (FamilyID famid) const { return m_FamilyID == famid; } - void RouterInfo::ExtractCaps (const char * value) + void RouterInfo::ExtractCaps (std::string_view value) { - const char * cap = value; - while (*cap) + for (auto cap: value) { - switch (*cap) + switch (cap) { case CAPS_FLAG_FLOODFILL: m_Caps |= Caps::eFloodfill; break; - case CAPS_FLAG_HIGH_BANDWIDTH1: - case CAPS_FLAG_HIGH_BANDWIDTH2: - case CAPS_FLAG_HIGH_BANDWIDTH3: + case CAPS_FLAG_LOW_BANDWIDTH1: + case CAPS_FLAG_LOW_BANDWIDTH2: + case CAPS_FLAG_LOW_BANDWIDTH3: + case CAPS_FLAG_LOW_BANDWIDTH4: + m_BandwidthCap = cap; + break; + case CAPS_FLAG_HIGH_BANDWIDTH: m_Caps |= Caps::eHighBandwidth; + m_BandwidthCap = cap; break; case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH2: m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth; + m_BandwidthCap = cap; break; case CAPS_FLAG_HIDDEN: m_Caps |= Caps::eHidden; @@ -570,19 +536,26 @@ namespace data case CAPS_FLAG_UNREACHABLE: m_Caps |= Caps::eUnreachable; break; + case CAPS_FLAG_MEDIUM_CONGESTION: + m_Congestion = eMediumCongestion; + break; + case CAPS_FLAG_HIGH_CONGESTION: + m_Congestion = eHighCongestion; + break; + case CAPS_FLAG_REJECT_ALL_CONGESTION: + m_Congestion = eRejectAll; + break; default: ; } - cap++; } - } - - uint8_t RouterInfo::ExtractAddressCaps (const char * value) const + } + + uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const { uint8_t caps = 0; - const char * cap = value; - while (*cap) + for (auto cap: value) { - switch (*cap) + switch (cap) { case CAPS_FLAG_V4: caps |= AddressCaps::eV4; @@ -590,19 +563,33 @@ namespace data case CAPS_FLAG_V6: caps |= AddressCaps::eV6; break; - case CAPS_FLAG_SSU_TESTING: + case CAPS_FLAG_SSU2_TESTING: caps |= AddressCaps::eSSUTesting; break; - case CAPS_FLAG_SSU_INTRODUCER: + case CAPS_FLAG_SSU2_INTRODUCER: caps |= AddressCaps::eSSUIntroducer; break; default: ; } - cap++; } return caps; - } - + } + + void RouterInfo::UpdateIntroducers (std::shared_ptr
address, uint64_t ts) + { + if (!address || !address->ssu) return; + int numValid = 0; + for (auto& it: address->ssu->introducers) + { + if (it.iTag && ts < it.iExp && !it.iH.IsZero ()) + numValid++; + else + it.iTag = 0; + } + if (!numValid) + address->ssu->introducers.resize (0); + } + bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { if (!m_RouterIdentity) return false; @@ -617,107 +604,174 @@ 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, std::shared_ptr buf) + { + if (!buf) return false; + std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) + { + LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath); + return false; + } + f.write ((char *)buf->data (), buf->GetBufferLen ()); + return true; + } + 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); - return false; - } - f.write ((char *)m_Buffer->data (), m_BufferLen); - return true; + return SaveToFile (fullPath, m_Buffer); } - size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const + std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const { - uint8_t l; - s.read ((char *)&l, 1); - if (l < len) - { - s.read (str, l); - if (!s) l = 0; // failed, return empty string - str[l] = 0; - } - else + uint8_t l = buf[0]; + if (l > len) { LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); - s.seekg (l, std::ios::cur); // skip - str[0] = 0; - } - return l+1; + l = len; + } + return { (const char *)(buf + 1), l }; } - - void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) + std::tuple RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const + { + auto key = ExtractString (buf, len); + size_t offset = key.length () + 1; + if (offset >= len) return { std::string_view(), std::string_view(), len }; + if (buf[offset] != '=') + { + LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key); + key = std::string_view(); + } + offset++; + if (offset >= len) return { key, std::string_view(), len }; + auto value = ExtractString (buf + offset, len - offset); + offset += value.length () + 1; + if (offset >= len) return { key, std::string_view(), len }; + if (buf[offset] != ';') + { + LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value); + value = std::string_view(); + } + offset++; + return { key, value, offset }; + } + + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) { auto addr = std::make_shared
(); - addr->host = boost::asio::ip::address::from_string (host); addr->port = port; - addr->transportStyle = eTransportSSU; - 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 = mtu; - if (key) - memcpy (addr->i, key, 32); - else - RAND_bytes (addr->i, 32); - for (const auto& it: *m_Addresses) // don't insert same address twice - if (*it == *addr) return; - m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; - m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; - m_Addresses->push_back(std::move(addr)); - } - - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port, uint8_t caps) - { - auto addr = std::make_shared
(); - addr->host = host; - addr->port = port; - addr->transportStyle = eTransportNTCP; + 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; + (*GetAddresses ())[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eNTCP2V6; - if (addr->published) m_ReachableTransports |= eNTCP2V6; + (*GetAddresses ())[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; + } + auto addresses = GetAddresses (); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eNTCP2V4; + m_ReachableTransports |= eNTCP2V4; + (*addresses)[eNTCP2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (addr->host)) + { + m_SupportedTransports |= eNTCP2V6Mesh; + m_ReachableTransports |= eNTCP2V6Mesh; + (*addresses)[eNTCP2V6MeshIdx] = addr; + } + else + { + m_SupportedTransports |= eNTCP2V6; + m_ReachableTransports |= eNTCP2V6; + (*addresses)[eNTCP2V6Idx] = addr; + } + } + } + + void RouterInfo::RemoveNTCP2Address (bool v4) + { + auto addresses = GetAddresses (); + if (v4) + { + if ((*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); + } + else + { + if ((*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*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)); + auto addresses = GetAddresses (); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eSSU2V4; + (*addresses)[eSSU2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eSSU2V6; + (*addresses)[eSSU2V6Idx] = addr; + } } void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, @@ -728,68 +782,50 @@ 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; + } + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; m_ReachableTransports |= eSSU2V4; + (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; m_ReachableTransports |= eSSU2V6; + (*addresses)[eSSU2V6Idx] = addr; } - m_Addresses->push_back(std::move(addr)); } - bool RouterInfo::AddIntroducer (const Introducer& introducer) + void RouterInfo::RemoveSSU2Address (bool v4) { - for (auto& addr : *m_Addresses) + auto addresses = GetAddresses (); + if (v4) { - if (addr->transportStyle == eTransportSSU && - ((addr->IsV4 () && introducer.iHost.is_v4 ()) || (addr->IsV6 () && introducer.iHost.is_v6 ()))) - { - for (auto& intro: addr->ssu->introducers) - if (intro.iTag == introducer.iTag) return false; // already presented - addr->ssu->introducers.push_back (introducer); - m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6); - return true; - } + if ((*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } - return false; - } - - bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) - { - for (auto& addr: *m_Addresses) - { - if (addr->transportStyle == eTransportSSU && - ((addr->IsV4 () && e.address ().is_v4 ()) || (addr->IsV6 () && e.address ().is_v6 ()))) - { - for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) - if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) - { - addr->ssu->introducers.erase (it); - if (addr->ssu->introducers.empty ()) - m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6); - return true; - } - } - } - return false; - } - - bool RouterInfo::IsSSU (bool v4only) const - { - if (v4only) - return m_SupportedTransports & eSSUV4; else - return m_SupportedTransports & (eSSUV4 | eSSUV6); + { + if ((*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); + } + UpdateSupportedTransports (); } bool RouterInfo::IsNTCP2 (bool v4only) const @@ -828,21 +864,18 @@ namespace data { if (IsV6 ()) { - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + auto addresses = GetAddresses (); + if ((*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 ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eNTCP2V6Idx].reset (); + } + if ((*addresses)[eSSU2V6Idx]) + { + if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -852,21 +885,18 @@ namespace data { if (IsV4 ()) { - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + auto addresses = GetAddresses (); + if ((*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 ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); + } + if ((*addresses)[eSSU2V4Idx]) + { + if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } UpdateSupportedTransports (); } @@ -887,57 +917,24 @@ 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; - } + (*GetAddresses ())[eNTCP2V6MeshIdx].reset (); } } - std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const - { - return GetAddress ( - [v4only](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && (!v4only || address->IsV4 ()); - }); - } - - std::shared_ptr RouterInfo::GetSSUV6Address () const - { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && address->IsV6(); - }); - } - 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 { if (v4) - { + { if (m_SupportedTransports & eSSU2V4) return GetSSU2V4Address (); } @@ -945,79 +942,72 @@ namespace data { if (m_SupportedTransports & eSSU2V6) return GetSSU2V6Address (); - } + } return nullptr; - } - + } + + RouterInfo::AddressesPtr RouterInfo::GetAddresses () const + { +#ifdef __cpp_lib_atomic_shared_ptr + return m_Addresses; +#else + return boost::atomic_load (&m_Addresses); +#endif + } + template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator -#if (BOOST_VERSION >= 105300) +#ifdef __cpp_lib_atomic_shared_ptr + AddressesPtr addresses = m_Addresses; +#else auto addresses = boost::atomic_load (&m_Addresses); -#else - auto addresses = m_Addresses; #endif for (const auto& address : *addresses) - if (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 @@ -1029,61 +1019,49 @@ namespace data bool RouterInfo::IsEligibleFloodfill () const { - // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA - return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && + // floodfill must have published ipv4 or reachable ipv4 and published ipv6 + // >= 0.9.59 and not DSA + return m_Version >= NETDB_MIN_FLOODFILL_VERSION && (IsPublished (true) || + (IsReachableBy (eNTCP2V4 | eSSU2V4) && IsPublished (false))) && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } - bool RouterInfo::IsPeerTesting (bool v4) const + bool RouterInfo::IsPublished (bool v4) const { - if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; - return (bool)GetAddress ( - [v4](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && address->IsPeerTesting () && - ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU (); - }); - } + if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addresses are not published + return IsPublishedOn (v4 ? (eNTCP2V4 | eSSU2V4) : (eNTCP2V6 | eSSU2V6)); + } + bool RouterInfo::IsPublishedOn (CompatibleTransports transports) const + { + return m_PublishedTransports & transports; + } + + bool RouterInfo::IsNAT2NATOnly (const RouterInfo& other) const + { + return !(m_PublishedTransports & other.m_SupportedTransports) && + !(other.m_PublishedTransports & m_SupportedTransports); + } + 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 (); - }); - } - - bool RouterInfo::IsIntroducer (bool v4) const - { - if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; - return (bool)GetAddress ( - [v4](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && address->IsIntroducer () && - ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); - }); + 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) + for (auto& addr: *GetAddresses ()) { - // TODO: implement SSU - if (!addr->published && (addr->transportStyle == eTransportNTCP || addr->transportStyle == eTransportSSU2)) + if (addr && !addr->published) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; @@ -1095,55 +1073,131 @@ namespace data { m_SupportedTransports = 0; m_ReachableTransports = 0; - for (const auto& addr: *m_Addresses) + for (const auto& addr: *GetAddresses ()) { + if (!addr) continue; uint8_t transports = 0; switch (addr->transportStyle) - { - case eTransportNTCP: + { + case eTransportNTCP2: if (addr->IsV4 ()) transports |= eNTCP2V4; if (addr->IsV6 ()) transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); if (addr->IsPublishedNTCP2 ()) m_ReachableTransports |= transports; break; - case eTransportSSU: - if (addr->IsV4 ()) transports |= eSSUV4; - if (addr->IsV6 ()) transports |= eSSUV6; - if (addr->IsReachableSSU ()) - m_ReachableTransports |= transports; - break; case eTransportSSU2: if (addr->IsV4 ()) transports |= eSSU2V4; if (addr->IsV6 ()) transports |= eSSU2V6; if (addr->IsReachableSSU ()) m_ReachableTransports |= transports; - break; - default: ; - } + break; + default: ; + } m_SupportedTransports |= transports; } } + void RouterInfo::UpdateIntroducers (uint64_t ts) + { + if (ts*1000 < m_Timestamp + INTRODUCER_UPDATE_INTERVAL) return; + if (m_ReachableTransports & eSSU2V4) + { + auto addr = (*GetAddresses ())[eSSU2V4Idx]; + if (addr && addr->UsesIntroducer ()) + { + UpdateIntroducers (addr, ts); + if (!addr->UsesIntroducer ()) // no more valid introducers + m_ReachableTransports &= ~eSSU2V4; + } + } + if (m_ReachableTransports & eSSU2V6) + { + auto addr = (*GetAddresses ())[eSSU2V6Idx]; + if (addr && addr->UsesIntroducer ()) + { + UpdateIntroducers (addr, ts); + if (!addr->UsesIntroducer ()) // no more valid introducers + m_ReachableTransports &= ~eSSU2V6; + } + } + } + void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len) { + m_IsBufferScheduledToDelete = false; if (!m_Buffer) m_Buffer = NewBuffer (); if (len > m_Buffer->size ()) len = m_Buffer->size (); memcpy (m_Buffer->data (), buf, len); - m_BufferLen = len; + m_Buffer->SetBufferLen (len); } + std::shared_ptr RouterInfo::CopyBuffer () const + { + if (!m_Buffer) return nullptr; + return netdb.NewRouterInfoBuffer (*m_Buffer); + } + std::shared_ptr RouterInfo::NewBuffer () const { return netdb.NewRouterInfoBuffer (); } + std::shared_ptr RouterInfo::NewAddress () const + { + return netdb.NewRouterInfoAddress (); + } + + RouterInfo::AddressesPtr RouterInfo::NewAddresses () const + { + return netdb.NewRouterInfoAddresses (); + } + + std::shared_ptr RouterInfo::NewIdentity (const uint8_t * buf, size_t len) const + { + return netdb.NewIdentity (buf, len); + } + void RouterInfo::RefreshTimestamp () { m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } + bool RouterInfo::IsHighCongestion (bool highBandwidth) const + { + switch (m_Congestion) + { + case eLowCongestion: + return false; + break; + case eMediumCongestion: + return highBandwidth; + break; + case eHighCongestion: + return i2p::util::GetMillisecondsSinceEpoch () < m_Timestamp + HIGH_CONGESTION_INTERVAL*1000LL; + break; + case eRejectAll: + return true; + break; + default: + return false; + } + } + + std::string RouterInfo::GetTransportName (SupportedTransports tr) + { + switch (tr) + { + case eNTCP2V4: return "NTCP2V4"; + case eNTCP2V6: return "NTCP2V6"; + case eSSU2V4: return "SSU2V4"; + case eSSU2V6: return "SSU2V6"; + case eNTCP2V6Mesh: return "Mesh"; + default: return ""; + } + } + void LocalRouterInfo::CreateBuffer (const PrivateKeys& privateKeys) { RefreshTimestamp (); @@ -1181,7 +1235,7 @@ namespace data CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else - caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else @@ -1189,113 +1243,107 @@ namespace data if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ else - caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth + caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (c & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable + switch (GetCongestion ()) + { + case eMediumCongestion: + caps += CAPS_FLAG_MEDIUM_CONGESTION; + break; + case eHighCongestion: + caps += CAPS_FLAG_HIGH_CONGESTION; + break; + case eRejectAll: + caps += CAPS_FLAG_REJECT_ALL_CONGESTION; + break; + default: ; + }; + SetProperty ("caps", caps); } + bool LocalRouterInfo::UpdateCongestion (Congestion c) + { + if (c != GetCongestion ()) + { + SetCongestion (c); + UpdateCapsProperty (); + return true; + } + return false; + } + void LocalRouterInfo::WriteToStream (std::ostream& s) const { + auto addresses = GetAddresses (); + if (!addresses) return; + uint64_t ts = htobe64 (GetTimestamp ()); s.write ((const char *)&ts, sizeof (ts)); - // addresses - const Addresses& addresses = GetAddresses (); - 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; - if (address.transportStyle == eTransportNTCP) + if (address.transportStyle == eTransportNTCP2) cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; - else if (address.transportStyle == eTransportSSU) - cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS; else if (address.transportStyle == eTransportSSU2) cost = address.published ? COST_SSU2_DIRECT : COST_SSU2_NON_PUBLISHED; + else + continue; // skip unknown address s.write ((const char *)&cost, sizeof (cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; - bool isPublished = false; - if (address.transportStyle == eTransportNTCP) + bool isPublished = address.published && !address.host.is_unspecified () && address.port; + if (address.transportStyle == eTransportNTCP2) { - if (address.IsNTCP2 ()) - { - WriteString ("NTCP2", s); - if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) - isPublished = true; - else - { - WriteString ("caps", properties); - properties << '='; - std::string caps; - if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 ()) caps += CAPS_FLAG_V6; - if (caps.empty ()) caps += CAPS_FLAG_V4; - WriteString (caps, properties); - properties << ';'; - } - } - else - continue; // don't write NTCP address - } - else if (address.transportStyle == eTransportSSU) - { - WriteString ("SSU", s); + WriteString ("NTCP2", s); // caps - WriteString ("caps", properties); - properties << '='; - std::string caps; - if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; - if (address.host.is_v4 ()) - { - if (address.published) - { - isPublished = true; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; - } - else - caps += CAPS_FLAG_V4; - } - else if (address.host.is_v6 ()) - { - if (address.published) - { - isPublished = true; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; - } - else - caps += CAPS_FLAG_V6; - } - else + if (!isPublished) { + WriteString ("caps", properties); + properties << '='; + std::string caps; if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (address.IsV6 () || address.host.is_v6 ()) caps += CAPS_FLAG_V6; // we set 6 for unspecified ipv6 if (caps.empty ()) caps += CAPS_FLAG_V4; + WriteString (caps, properties); + properties << ';'; } - WriteString (caps, properties); - properties << ';'; } else if (address.transportStyle == eTransportSSU2) { WriteString ("SSU2", s); // caps std::string caps; - if (address.published) + if (isPublished) { - isPublished = true; - if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU2_TESTING; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU2_INTRODUCER; } else { if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (address.IsV6 () || address.host.is_v6 ()) caps += CAPS_FLAG_V6; // we set 6 for unspecified ipv6 if (caps.empty ()) caps += CAPS_FLAG_V4; } if (!caps.empty ()) @@ -1323,7 +1371,7 @@ namespace data size_t len = address.IsSSU2 () ? 32 : 16; WriteString (address.i.ToBase64 (len), properties); properties << ';'; } - if (address.transportStyle == eTransportSSU || address.IsSSU2 ()) + if (address.transportStyle == eTransportSSU2) { // write introducers if any if (address.ssu && !address.ssu->introducers.empty()) @@ -1331,92 +1379,56 @@ namespace data int i = 0; for (const auto& introducer: address.ssu->introducers) { + if (!introducer.iTag) continue; if (introducer.iExp) // expiration is specified { - WriteString ("iexp" + boost::lexical_cast(i), properties); + WriteString ("iexp" + std::to_string(i), properties); properties << '='; - WriteString (boost::lexical_cast(introducer.iExp), properties); + WriteString (std::to_string(introducer.iExp), properties); properties << ';'; } i++; } - if (address.transportStyle == eTransportSSU) - { - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("ihost" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (introducer.iHost.to_string (), properties); - properties << ';'; - i++; - } - } i = 0; for (const auto& introducer: address.ssu->introducers) { - if (address.IsSSU2 ()) - WriteString ("ih" + boost::lexical_cast(i), properties); - else - WriteString ("ikey" + boost::lexical_cast(i), properties); + if (!introducer.iTag) continue; + WriteString ("ih" + std::to_string(i), properties); properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); - value[l] = 0; + auto value = ByteStreamToBase64 (introducer.iH, 32); WriteString (value, properties); properties << ';'; i++; } - if (address.transportStyle == eTransportSSU) - { - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("iport" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iPort), properties); - properties << ';'; - i++; - } - } i = 0; for (const auto& introducer: address.ssu->introducers) { - WriteString ("itag" + boost::lexical_cast(i), properties); + if (!introducer.iTag) continue; + WriteString ("itag" + std::to_string(i), properties); properties << '='; - WriteString (boost::lexical_cast(introducer.iTag), properties); + WriteString (std::to_string(introducer.iTag), properties); properties << ';'; i++; } } } - if (address.transportStyle == eTransportSSU) - { - // write intro key - WriteString ("key", properties); - properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (address.i, 32, value, 64); - value[l] = 0; - WriteString (value, properties); - properties << ';'; - } - if (address.transportStyle == eTransportSSU || address.IsSSU2 ()) + + if (address.transportStyle == eTransportSSU2) { // write mtu if (address.ssu && address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; - WriteString (boost::lexical_cast(address.ssu->mtu), properties); + WriteString (std::to_string(address.ssu->mtu), properties); properties << ';'; } } - if ((isPublished || (address.ssu && !address.IsSSU2 ())) && address.port) + if (isPublished && address.port) { WriteString ("port", properties); properties << '='; - WriteString (boost::lexical_cast(address.port), properties); + WriteString (std::to_string(address.port), properties); properties << ';'; } if (address.IsNTCP2 () || address.IsSSU2 ()) @@ -1451,9 +1463,11 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } - void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) + void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) { - m_Properties[key] = value; + auto [it, inserted] = m_Properties.emplace (key, value); + if (!inserted) + it->second = value; } void LocalRouterInfo::DeleteProperty (const std::string& key) @@ -1469,6 +1483,20 @@ namespace data return ""; } + void LocalRouterInfo::UpdateFloodfillProperty (bool floodfill) + { + if (floodfill) + { + UpdateCaps (GetCaps () | i2p::data::RouterInfo::eFloodfill); + SetFloodfill (); + } + else + { + UpdateCaps (GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + ResetFloodfill (); + } + } + void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const { uint8_t len = str.size (); @@ -1481,37 +1509,70 @@ namespace data return std::make_shared (); } + std::shared_ptr LocalRouterInfo::NewAddress () const + { + return std::make_shared
(); + } + + RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const + { + return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); + } + + std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const + { + return std::make_shared (buf, len); + } + bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) { - for (auto& addr : GetAddresses ()) + auto addresses = GetAddresses (); + if (!addresses) return false; + 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; - } + } bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) { - for (auto& addr: GetAddresses ()) + auto addresses = GetAddresses (); + if (!addresses) return false; + 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->iKey) - { - 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; + } + + bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + { + auto addresses = GetAddresses (); + if (!addresses) return false; + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) + { + for (auto& it: addr->ssu->introducers) + if (h == it.iH) + { + it.iTag = iTag; + it.iExp = iExp; + return true; + } } return false; } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 6bc66b35..cb3ae499 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,12 +11,17 @@ #include #include +#include +#include #include #include #include #include +#include #include +#ifndef __cpp_lib_atomic_shared_ptr #include +#endif #include "Identity.h" #include "Profiling.h" #include "Family.h" @@ -39,38 +44,57 @@ namespace data /* bandwidth flags */ const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */ const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */ - const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */ - const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */ - const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */ - const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ - const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ - + const char CAPS_FLAG_LOW_BANDWIDTH3 = 'M'; /* 48-64 KBps */ + const char CAPS_FLAG_LOW_BANDWIDTH4 = 'N'; /* 64-128 KBps */ + const char CAPS_FLAG_HIGH_BANDWIDTH = 'O'; /* 128-256 KBps */ + const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2048 KBps */ + const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2048 KBps */ + // bandwidth limits in kBps + const uint32_t LOW_BANDWIDTH_LIMIT = 48; + const uint32_t HIGH_BANDWIDTH_LIMIT = 256; + const uint32_t EXTRA_BANDWIDTH_LIMIT = 2048; + // congesion flags + const char CAPS_FLAG_MEDIUM_CONGESTION = 'D'; + const char CAPS_FLAG_HIGH_CONGESTION = 'E'; + const char CAPS_FLAG_REJECT_ALL_CONGESTION = 'G'; + const char CAPS_FLAG_V4 = '4'; const char CAPS_FLAG_V6 = '6'; - const char CAPS_FLAG_SSU_TESTING = 'B'; - const char CAPS_FLAG_SSU_INTRODUCER = 'C'; + const char CAPS_FLAG_SSU2_TESTING = 'B'; + const char CAPS_FLAG_SSU2_INTRODUCER = 'C'; const uint8_t COST_NTCP2_PUBLISHED = 3; const uint8_t COST_NTCP2_NON_PUBLISHED = 14; const uint8_t COST_SSU2_DIRECT = 8; - const uint8_t COST_SSU_DIRECT = 9; - const uint8_t COST_SSU_THROUGH_INTRODUCERS = 11; const uint8_t COST_SSU2_NON_PUBLISHED = 15; const size_t MAX_RI_BUFFER_SIZE = 3072; // if RouterInfo exceeds 3K we consider it as malformed, might extend later + const int HIGH_CONGESTION_INTERVAL = 15*60; // in seconds, 15 minutes + const int INTRODUCER_UPDATE_INTERVAL = 20*60*1000; // in milliseconds, 20 minutes + class RouterInfo: public RoutingDestination { 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, - eSSUV4 = 0x04, - eSSUV6 = 0x08, - eNTCP2V6Mesh = 0x10, - eSSU2V4 = 0x20, - eSSU2V6 = 0x40, + TransportBit(NTCP2V4), // 0x01 + TransportBit(NTCP2V6), // 0x02 + TransportBit(SSU2V4), // 0x04 + TransportBit(SSU2V6), // 0x08 + TransportBit(NTCP2V6Mesh), // 0x10 eAllTransports = 0xFF }; typedef uint8_t CompatibleTransports; @@ -85,6 +109,14 @@ namespace data eUnreachable = 0x20 }; + enum Congestion + { + eLowCongestion = 0, + eMediumCongestion, + eHighCongestion, + eRejectAll + }; + enum AddressCaps { eV4 = 0x01, @@ -96,18 +128,14 @@ namespace data enum TransportStyle { eTransportUnknown = 0, - eTransportNTCP, - eTransportSSU, + eTransportNTCP2, eTransportSSU2 }; - typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { - Introducer (): iPort (0), iExp (0) {}; - boost::asio::ip::address iHost; - int iPort; - IntroKey iKey; // or ih for SSU2 + Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; + IdentHash iH; uint32_t iTag; uint32_t iExp; }; @@ -146,7 +174,7 @@ namespace data return !(*this == other); } - bool IsNTCP2 () const { return transportStyle == eTransportNTCP; }; + bool IsNTCP2 () const { return transportStyle == eTransportNTCP2; }; bool IsSSU2 () const { return transportStyle == eTransportSSU2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; bool IsReachableSSU () const { return (bool)ssu && (published || UsesIntroducer ()); }; @@ -165,13 +193,25 @@ namespace data Buffer () = default; Buffer (const uint8_t * buf, size_t len); + Buffer (const Buffer& other): Buffer (other.data (), other.m_BufferLen) {}; + + size_t GetBufferLen () const { return m_BufferLen; }; + void SetBufferLen (size_t len) { m_BufferLen = len; }; + + private: + + size_t m_BufferLen = 0; }; - typedef std::vector > Addresses; - + typedef std::array, eNumTransports> Addresses; +#ifdef __cpp_lib_atomic_shared_ptr + typedef std::shared_ptr AddressesPtr; +#else + typedef boost::shared_ptr AddressesPtr; +#endif RouterInfo (const std::string& fullPath); - RouterInfo (const RouterInfo& ) = default; - RouterInfo& operator=(const RouterInfo& ) = default; + RouterInfo (const RouterInfo& ) = delete; + RouterInfo& operator=(const RouterInfo& ) = delete; RouterInfo (std::shared_ptr&& buf, size_t len); RouterInfo (const uint8_t * buf, size_t len); virtual ~RouterInfo (); @@ -181,41 +221,39 @@ namespace data std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; - virtual void SetProperty (const std::string& key, const std::string& value) {}; + virtual void SetProperty (std::string_view key, std::string_view value) {}; virtual void ClearProperties () {}; - Addresses& GetAddresses () { return *m_Addresses; }; // 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; + AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr + std::shared_ptr GetNTCP2V4Address () const; + std::shared_ptr GetNTCP2V6Address () const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; - std::shared_ptr GetSSUAddress (bool v4only = true) const; - std::shared_ptr GetSSUV6Address () const; std::shared_ptr GetYggdrasilAddress () const; std::shared_ptr GetSSU2V4Address () const; std::shared_ptr GetSSU2V6Address () const; std::shared_ptr GetSSU2Address (bool v4) const; - void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); + 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 - bool AddIntroducer (const Introducer& introducer); - bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); + void RemoveSSU2Address (bool v4); void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps void UpdateSupportedTransports (); - bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; - bool IsReachable () const { return m_Caps & Caps::eReachable; }; + void UpdateIntroducers (uint64_t ts); // ts in seconds + bool IsFloodfill () const { return m_IsFloodfill; }; + void SetFloodfill () { m_IsFloodfill = true; }; + void ResetFloodfill () { m_IsFloodfill = false; }; bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; - bool IsSSU (bool v4only = true) const; - bool IsSSUV6 () const { return m_SupportedTransports & eSSUV6; }; bool IsNTCP2 (bool v4only = true) const; bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; }; bool IsSSU2V4 () const { return m_SupportedTransports & eSSU2V4; }; bool IsSSU2V6 () const { return m_SupportedTransports & eSSU2V6; }; - bool IsV6 () const { return m_SupportedTransports & (eSSUV6 | eNTCP2V6 | eSSU2V6); }; - bool IsV4 () const { return m_SupportedTransports & (eSSUV4 | eNTCP2V4 | eSSU2V4); }; + bool IsV6 () const { return m_SupportedTransports & (eNTCP2V6 | eSSU2V6); }; + bool IsV4 () const { return m_SupportedTransports & (eNTCP2V4 | eSSU2V4); }; bool IsMesh () const { return m_SupportedTransports & eNTCP2V6Mesh; }; void EnableV6 (); void DisableV6 (); @@ -227,35 +265,50 @@ namespace data bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; bool IsReachableBy (CompatibleTransports transports) const { return m_ReachableTransports & transports; }; CompatibleTransports GetCompatibleTransports (bool incoming) const { return incoming ? m_ReachableTransports : m_SupportedTransports; }; + CompatibleTransports GetPublishedTransports () const { return m_PublishedTransports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsEligibleFloodfill () const; - bool IsPeerTesting (bool v4) const; + bool IsDeclaredFloodfill () const { return m_Caps & RouterInfo::eFloodfill; }; + bool IsPublished (bool v4) const; + bool IsPublishedOn (CompatibleTransports transports) const; + bool IsNAT2NATOnly (const RouterInfo& other) const; // only NAT-to-NAT connection is possible bool IsSSU2PeerTesting (bool v4) const; - bool IsIntroducer (bool v4) const; bool IsSSU2Introducer (bool v4) const; + bool IsHighCongestion (bool highBandwidth) const; uint8_t GetCaps () const { return m_Caps; }; + char GetBandwidthCap() const { return m_BandwidthCap; }; void SetCaps (uint8_t caps) { m_Caps = caps; }; + Congestion GetCongestion () const { return m_Congestion; }; + void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; + void ExcludeReachableTransports (CompatibleTransports transports) { m_ReachableTransports &= ~transports; }; - 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; }; + size_t GetBufferLen () const { return m_Buffer ? m_Buffer->GetBufferLen () : 0; }; + void DeleteBuffer () { m_Buffer = nullptr; m_IsBufferScheduledToDelete = false; }; + std::shared_ptr GetSharedBuffer () const { return m_Buffer; }; + std::shared_ptr CopyBuffer () const; + void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; }; + void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; }; + bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; }; bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; bool SaveToFile (const std::string& fullPath); - + static bool SaveToFile (const std::string& fullPath, std::shared_ptr buf); + std::shared_ptr GetProfile () const; - void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; + void DropProfile () { m_Profile = nullptr; }; + bool HasProfile () const { return (bool)m_Profile; }; - void Update (const uint8_t * buf, size_t len); - void DeleteBuffer () { m_Buffer = nullptr; }; + bool Update (const uint8_t * buf, size_t len); bool IsNewer (const uint8_t * buf, size_t len) const; /** return true if we are in a router family and the signature is valid */ @@ -272,38 +325,52 @@ namespace data RouterInfo (); uint8_t * GetBufferPointer (size_t offset = 0 ) { return m_Buffer->data () + offset; }; void UpdateBuffer (const uint8_t * buf, size_t len); - void SetBufferLen (size_t len) { m_BufferLen = len; }; + void SetBufferLen (size_t len) { if (m_Buffer) m_Buffer->SetBufferLen (len); }; void RefreshTimestamp (); - const Addresses& GetAddresses () const { return *m_Addresses; }; CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; - void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; - + void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; + void SetCongestion (Congestion c) { m_Congestion = c; }; + private: bool LoadFile (const std::string& fullPath); void ReadFromFile (const std::string& fullPath); - void ReadFromStream (std::istream& s); + bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed void ReadFromBuffer (bool verifySignature); - size_t ReadString (char* str, size_t len, std::istream& s) const; - void ExtractCaps (const char * value); - uint8_t ExtractAddressCaps (const char * value) const; + std::string_view ExtractString (const uint8_t * buf, size_t len) const; + std::tuple ExtractParam (const uint8_t * buf, size_t len) const; + void ExtractCaps (std::string_view value); + uint8_t ExtractAddressCaps (std::string_view value) const; + void UpdateIntroducers (std::shared_ptr
address, uint64_t ts); template std::shared_ptr GetAddress (Filter filter) const; virtual std::shared_ptr NewBuffer () const; + virtual std::shared_ptr
NewAddress () const; + virtual AddressesPtr NewAddresses () const; + virtual std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const; private: FamilyID m_FamilyID; std::shared_ptr m_RouterIdentity; std::shared_ptr m_Buffer; - size_t m_BufferLen; - uint64_t m_Timestamp; - boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 - bool m_IsUpdated, m_IsUnreachable; - CompatibleTransports m_SupportedTransports, m_ReachableTransports; + uint64_t m_Timestamp; // in milliseconds +#ifdef __cpp_lib_atomic_shared_ptr + std::atomic m_Addresses; +#else + AddressesPtr m_Addresses; +#endif + bool m_IsUpdated, m_IsUnreachable, m_IsFloodfill, m_IsBufferScheduledToDelete; + CompatibleTransports m_SupportedTransports, m_ReachableTransports, m_PublishedTransports; uint8_t m_Caps; + char m_BandwidthCap; int m_Version; + Congestion m_Congestion; mutable std::shared_ptr m_Profile; + + public: + + static std::string GetTransportName (SupportedTransports tr); }; class LocalRouterInfo: public RouterInfo @@ -313,21 +380,27 @@ namespace data LocalRouterInfo () = default; void CreateBuffer (const PrivateKeys& privateKeys); void UpdateCaps (uint8_t caps); + bool UpdateCongestion (Congestion c); // returns true if updated - void SetProperty (const std::string& key, const std::string& value) override; + void SetProperty (std::string_view key, std::string_view value) override; void DeleteProperty (const std::string& key); std::string GetProperty (const std::string& key) const; void ClearProperties () override { m_Properties.clear (); }; - + void UpdateFloodfillProperty (bool floodfill); + bool AddSSU2Introducer (const Introducer& introducer, bool v4); bool RemoveSSU2Introducer (const IdentHash& h, bool v4); - + bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); + private: void WriteToStream (std::ostream& s) const; void UpdateCapsProperty (); void WriteString (const std::string& str, std::ostream& s) const; std::shared_ptr NewBuffer () const override; + std::shared_ptr
NewAddress () const override; + RouterInfo::AddressesPtr NewAddresses () const override; + std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp deleted file mode 100644 index eec55857..00000000 --- a/libi2pd/SSU.cpp +++ /dev/null @@ -1,996 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#include -#include "Log.h" -#include "Timestamp.h" -#include "RouterContext.h" -#include "NetDb.hpp" -#include "Config.h" -#include "util.h" -#include "SSU.h" - -#if defined(__linux__) && !defined(_NETINET_IN_H) - #include -#endif - -#ifdef _WIN32 -#include -#endif - -namespace i2p -{ -namespace transport -{ - SSUServer::SSUServer (int port): - m_IsRunning(false), m_Thread (nullptr), - m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), - m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), - m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), - m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), - m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service), - m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service), - m_IsSyncClockFromPeers (true) - { - } - - SSUServer::~SSUServer () - { - } - - void SSUServer::OpenSocket () - { - try - { - m_Socket.open (boost::asio::ip::udp::v4()); - m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_Socket.bind (m_Endpoint); - LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: Failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::OpenSocketV6 () - { - try - { - m_SocketV6.open (boost::asio::ip::udp::v6()); - m_SocketV6.set_option (boost::asio::ip::v6_only (true)); - m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); -#if defined(__linux__) && !defined(_NETINET_IN_H) - if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address - { - // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others -#if (BOOST_VERSION >= 105500) - typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; -#else - typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; -#endif - m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); - } -#endif - m_SocketV6.bind (m_EndpointV6); - LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: Failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::Start () - { - i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); - if (context.SupportsV4 ()) - { - OpenSocket (); - m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); - m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); - ScheduleTermination (); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers - } - if (context.SupportsV6 ()) - { - OpenSocketV6 (); - m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); - ScheduleTerminationV6 (); - ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers - } - SchedulePeerTestsCleanupTimer (); - } - - void SSUServer::Stop () - { - DeleteAllSessions (); - m_IsRunning = false; - m_TerminationTimer.cancel (); - m_TerminationTimerV6.cancel (); - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimerV6.cancel (); - m_Service.stop (); - m_Socket.close (); - m_SocketV6.close (); - m_ReceiversService.stop (); - m_ReceiversServiceV6.stop (); - if (m_ReceiversThread) - { - m_ReceiversThread->join (); - delete m_ReceiversThread; - m_ReceiversThread = nullptr; - } - if (m_ReceiversThreadV6) - { - m_ReceiversThreadV6->join (); - delete m_ReceiversThreadV6; - m_ReceiversThreadV6 = nullptr; - } - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - - void SSUServer::Run () - { - i2p::util::SetThreadName("SSU"); - - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: Server runtime exception: ", ex.what ()); - } - } - } - - void SSUServer::RunReceivers () - { - i2p::util::SetThreadName("SSUv4"); - - while (m_IsRunning) - { - try - { - m_ReceiversService.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: Receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - // restart socket - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - } - - void SSUServer::RunReceiversV6 () - { - i2p::util::SetThreadName("SSUv6"); - - while (m_IsRunning) - { - try - { - m_ReceiversServiceV6.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - } - - void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress) - { - if (localAddress.is_v6 ()) - m_EndpointV6.address (localAddress); - else if (localAddress.is_v4 ()) - m_Endpoint.address (localAddress); - } - - void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) - { - m_Relays.emplace (tag, relay); - } - - void SSUServer::RemoveRelay (uint32_t tag) - { - m_Relays.erase (tag); - } - - std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) - { - auto it = m_Relays.find (tag); - if (it != m_Relays.end ()) - { - if (it->second->GetState () == eSessionStateEstablished) - return it->second; - else - m_Relays.erase (it); - } - return nullptr; - } - - void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) - { - boost::system::error_code ec; - if (to.protocol () == boost::asio::ip::udp::v4()) - m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec); - else - m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec); - - if (ec) - { - LogPrint (eLogError, "SSU: Send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")"); - } - } - - void SSUServer::Receive () - { - SSUPacket * packet = m_PacketsPool.AcquireMt (); - m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, - std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::ReceiveV6 () - { - SSUPacket * packet = m_PacketsPool.AcquireMt (); - m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, - std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode - || ecode == boost::asio::error::connection_refused - || ecode == boost::asio::error::connection_reset - || ecode == boost::asio::error::network_unreachable - || ecode == boost::asio::error::host_unreachable -#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO - || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ - || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ - || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ -#endif - ) - // just try continue reading when received ICMP response otherwise socket can crash, - // but better to find out which host were sent it and mark that router as unreachable - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_Socket.available(ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = m_PacketsPool.AcquireMt (); - packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_Socket.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); - m_PacketsPool.ReleaseMt (packet); - break; - } - } - } - - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); - Receive (); - } - else - { - m_PacketsPool.ReleaseMt (packet); - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: Receive error: code ", ecode.value(), ": ", ecode.message ()); - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - - void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode - || ecode == boost::asio::error::connection_refused - || ecode == boost::asio::error::connection_reset - || ecode == boost::asio::error::network_unreachable - || ecode == boost::asio::error::host_unreachable -#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO - || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ - || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ - || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ -#endif - ) - // just try continue reading when received ICMP response otherwise socket can crash, - // but better to find out which host were sent it and mark that router as unreachable - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_SocketV6.available (ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = m_PacketsPool.AcquireMt (); - packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_SocketV6.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); - m_PacketsPool.ReleaseMt (packet);; - break; - } - } - } - - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); - ReceiveV6 (); - } - else - { - m_PacketsPool.ReleaseMt (packet); - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - - void SSUServer::HandleReceivedPackets (std::vector packets, - std::map > * sessions) - { - if (!m_IsRunning) return; - std::shared_ptr session; - for (auto& packet: packets) - { - try - { - if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous - { - if (session) - { - session->FlushData (); - session = nullptr; - } - auto it = sessions->find (packet->from); - if (it != sessions->end ()) - session = it->second; - if (!session && packet->len > 0) - { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); - (*sessions)[packet->from] = session; - LogPrint (eLogDebug, "SSU: New session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); - } - } - if (session) - session->ProcessNextMessage (packet->buf, packet->len, packet->from); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); - if (session) session->FlushData (); - session = nullptr; - } - } - m_PacketsPool.ReleaseMt (packets); - if (session) session->FlushData (); - } - - std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const - { - auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (e); - if (it != sessions.end ()) - return it->second; - else - return nullptr; - } - - bool SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) - { - auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); - if (address) - return CreateSession (router, address, peerTest); - else - LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); - return false; - } - - bool SSUServer::CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest) - { - if (router && address) - { - if (address->UsesIntroducer ()) - m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread - else - { - if (address->host.is_unspecified () || !address->port) return false; - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); - } - } - else - return false; - return true; - } - - void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) - { - auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (remoteEndpoint); - if (it != sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - } - else - { - // otherwise create new session - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - sessions[remoteEndpoint] = session; - - // connect - LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); - session->Connect (); - } - } - - void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, - std::shared_ptr address, bool peerTest) - { - if (router && address && address->UsesIntroducer ()) - { - if (address->IsV4 () && !i2p::context.SupportsV4 ()) return; - if (address->IsV6 () && !i2p::context.SupportsV6 ()) return; - if (!address->host.is_unspecified () && address->port) - { - // we rarely come here - auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions; - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto it = sessions.find (remoteEndpoint); - // check if session is presented already - if (it != sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - return; - } - } - // create new session - int numIntroducers = address->ssu->introducers.size (); - if (numIntroducers > 0) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::shared_ptr introducerSession; - const i2p::data::RouterInfo::Introducer * introducer = nullptr; - // we might have a session to introducer already - auto offset = rand (); - for (int i = 0; i < numIntroducers; i++) - { - auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]); - if (!intr->iPort) continue; // skip invalid introducer - if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer - boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); - if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4 - { - if (!introducer) introducer = intr; - auto it = m_Sessions.find (ep); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } - } - if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6 - { - if (!introducer) introducer = intr; - auto it = m_SessionsV6.find (ep); - if (it != m_SessionsV6.end ()) - { - introducerSession = it->second; - break; - } - } - } - if (!introducer) - { - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented"); - return; - } - - if (introducerSession) // session found - LogPrint (eLogWarning, "SSU: Session to introducer already exists"); - else // create new - { - LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); - boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); - introducerSession = std::make_shared (*this, introducerEndpoint, router); - if (introducerEndpoint.address ().is_v4 ()) - m_Sessions[introducerEndpoint] = introducerSession; - else if (introducerEndpoint.address ().is_v6 ()) - m_SessionsV6[introducerEndpoint] = introducerSession; - } - if (!address->host.is_unspecified () && address->port) - { - // create session - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - if (address->host.is_v4 ()) - m_Sessions[remoteEndpoint] = session; - else if (address->host.is_v6 ()) - m_SessionsV6[remoteEndpoint] = session; - - // introduce - LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), - "] through introducer ", introducer->iHost, ":", introducer->iPort); - session->WaitForIntroduction (); - if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || - (address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) - { - uint8_t buf[1]; - Send (buf, 0, remoteEndpoint); // send HolePunch - } - } - introducerSession->Introduce (*introducer, router); - } - else - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); - } - } - - void SSUServer::DeleteSession (std::shared_ptr session) - { - if (session) - { - session->Close (); - auto& ep = session->GetRemoteEndpoint (); - if (ep.address ().is_v6 ()) - m_SessionsV6.erase (ep); - else - m_Sessions.erase (ep); - } - } - - void SSUServer::DeleteAllSessions () - { - for (auto& it: m_Sessions) - it.second->Close (); - m_Sessions.clear (); - - for (auto& it: m_SessionsV6) - it.second->Close (); - m_SessionsV6.clear (); - } - - template - std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only - { - std::vector > filteredSessions; - for (const auto& s :m_Sessions) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only - { - return GetRandomV4Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - template - std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only - { - std::vector > filteredSessions; - for (const auto& s :m_SessionsV6) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only - { - return GetRandomV6Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - std::list > SSUServer::FindIntroducers (int maxNumIntroducers, - bool v4, std::set& excluded) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::list > ret; - const auto& sessions = v4 ? m_Sessions : m_SessionsV6; - for (const auto& s : sessions) - { - if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished && - ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) - ret.push_back (s.second); - else if (s.second->GetRemoteIdentity ()) - excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ()); - } - if ((int)ret.size () > maxNumIntroducers) - { - // shink ret randomly - int sz = ret.size () - maxNumIntroducers; - for (int i = 0; i < sz; i++) - { - auto ind = rand () % ret.size (); - auto it = ret.begin (); - std::advance (it, ind); - ret.erase (it); - } - } - return ret; - } - - void SSUServer::RescheduleIntroducersUpdateTimer () - { - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - - void SSUServer::ScheduleIntroducersUpdateTimer () - { - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - - void SSUServer::RescheduleIntroducersUpdateTimerV6 () - { - m_IntroducersUpdateTimerV6.cancel (); - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - - void SSUServer::ScheduleIntroducersUpdateTimerV6 () - { - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - - void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) - { - if (ecode != boost::asio::error::operation_aborted) - { - // timeout expired - if (v4) - { - if (i2p::context.GetStatus () == eRouterStatusTesting) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimer (); - return; - } - if (i2p::context.GetStatus () != eRouterStatusFirewalled) - { - // we don't need introducers - m_Introducers.clear (); - return; - } - // we are firewalled - if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4 - } - else - { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimerV6 (); - return; - } - if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) - { - // we don't need introducers - m_IntroducersV6.clear (); - return; - } - // we are firewalled - auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address (); - if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachable (false, true); // v6 - } - - std::list newList; - size_t numIntroducers = 0; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::set excluded; - auto& introducers = v4 ? m_Introducers : m_IntroducersV6; - for (const auto& it : introducers) - { - auto session = FindSession (it); - if (session) - { - if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) - session->SendKeepAlive (); - if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) - { - newList.push_back (it); - numIntroducers++; - if (session->GetRemoteIdentity ()) - excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ()); - } - else - session = nullptr; - } - if (!session) - i2p::context.RemoveIntroducer (it); - } - if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) - { - // create new - auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates - if (sessions.empty () && !introducers.empty ()) - { - // bump creation time for previous introducers if no new sessions found - LogPrint (eLogDebug, "SSU: No new introducers found. Trying to reuse existing"); - for (const auto& it : introducers) - { - auto session = FindSession (it); - if (session) - session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION); - } - // try again - excluded.clear (); - sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); - } - for (const auto& it1: sessions) - { - const auto& ep = it1->GetRemoteEndpoint (); - i2p::data::RouterInfo::Introducer introducer; - introducer.iHost = ep.address (); - introducer.iPort = ep.port (); - introducer.iTag = it1->GetRelayTag (); - introducer.iKey = it1->GetIntroKey (); - introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION; - if (i2p::context.AddIntroducer (introducer)) - { - newList.push_back (ep); - if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; - } - if (it1->GetRemoteIdentity ()) - excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ()); - } - } - introducers = newList; - if (introducers.size () < SSU_MAX_NUM_INTRODUCERS) - { - for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) - { - auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded); - if (introducer) - { - auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address (); - if (address && !address->host.is_unspecified () && address->port) - { - boost::asio::ip::udp::endpoint ep (address->host, address->port); - if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet - { - CreateDirectSession (introducer, ep, false); - excluded.insert (introducer->GetIdentHash ()); - } - } - } - else - { - LogPrint (eLogDebug, "SSU: Can't find more introducers"); - break; - } - } - } - if (v4) - ScheduleIntroducersUpdateTimer (); - else - ScheduleIntroducersUpdateTimerV6 (); - } - } - - void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) - { - m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; - } - - PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.role; - else - return ePeerTestParticipantUnknown; - } - - std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.session; - else - return nullptr; - } - - void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - it->second.role = role; - } - - void SSUServer::RemovePeerTest (uint32_t nonce) - { - m_PeerTests.erase (nonce); - } - - void SSUServer::SchedulePeerTestsCleanupTimer () - { - m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); - m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - int numDeleted = 0; - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) - { - if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) - { - numDeleted++; - it = m_PeerTests.erase (it); - } - else - ++it; - } - if (numDeleted > 0) - LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); - // some cleaups. TODO: use separate timer - m_FragmentsPool.CleanUp (); - m_IncompleteMessagesPool.CleanUp (); - m_SentMessagesPool.CleanUp (); - - SchedulePeerTestsCleanupTimer (); - } - } - - void SSUServer::ScheduleTermination () - { - uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout)); - m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_Sessions) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted"); - m_Service.post ([session] - { - LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - else - it.second->CleanUp (ts); - ScheduleTermination (); - } - } - - void SSUServer::ScheduleTerminationV6 () - { - uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; - m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout)); - m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_SessionsV6) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); - m_Service.post ([session] - { - LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - else - it.second->CleanUp (ts); - ScheduleTerminationV6 (); - } - } -} -} diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h deleted file mode 100644 index 25ce4d40..00000000 --- a/libi2pd/SSU.h +++ /dev/null @@ -1,159 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#ifndef SSU_H__ -#define SSU_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Crypto.h" -#include "util.h" -#include "I2PEndian.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "I2NPProtocol.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds - const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds - const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour - const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes - const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds - const size_t SSU_MAX_NUM_INTRODUCERS = 3; - const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K - const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K - - struct SSUPacket - { - i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size - boost::asio::ip::udp::endpoint from; - size_t len; - }; - - class SSUServer - { - public: - - SSUServer (int port); - ~SSUServer (); - void Start (); - void Stop (); - bool CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); - bool CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest = false); - void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); - std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; - std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); - std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); - void DeleteSession (std::shared_ptr session); - void DeleteAllSessions (); - - boost::asio::io_service& GetService () { return m_Service; }; - i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; - i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; - i2p::util::MemoryPool& GetSentMessagesPool () { return m_SentMessagesPool; }; - - uint16_t GetPort () const { return m_Endpoint.port (); }; - bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; - void SetLocalAddress (const boost::asio::ip::address& localAddress); - - void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); - void AddRelay (uint32_t tag, std::shared_ptr relay); - void RemoveRelay (uint32_t tag); - std::shared_ptr FindRelaySession (uint32_t tag); - void RescheduleIntroducersUpdateTimer (); - void RescheduleIntroducersUpdateTimerV6 (); - - void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); - PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); - std::shared_ptr GetPeerTestSession (uint32_t nonce); - void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); - void RemovePeerTest (uint32_t nonce); - - private: - - void OpenSocket (); - void OpenSocketV6 (); - void Run (); - void RunReceivers (); - void RunReceiversV6 (); - void Receive (); - void ReceiveV6 (); - void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedPackets (std::vector packets, - std::map >* sessions); - - void CreateSessionThroughIntroducer (std::shared_ptr router, - std::shared_ptr address, bool peerTest = false); - template - std::shared_ptr GetRandomV4Session (Filter filter); - template - std::shared_ptr GetRandomV6Session (Filter filter); - - std::list > FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded); - void ScheduleIntroducersUpdateTimer (); - void ScheduleIntroducersUpdateTimerV6 (); - void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); - - void SchedulePeerTestsCleanupTimer (); - void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleTerminationV6 (); - void HandleTerminationTimerV6 (const boost::system::error_code& ecode); - - private: - - struct PeerTest - { - uint64_t creationTime; - PeerTestParticipant role; - std::shared_ptr session; // for Bob to Alice - }; - - volatile bool m_IsRunning; - std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; - boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; - boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; - boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; - boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6, - m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; - bool m_IsSyncClockFromPeers; - std::list m_Introducers, m_IntroducersV6; // introducers we are connected to - std::map > m_Sessions, m_SessionsV6; - std::map > m_Relays; // we are introducer - std::map m_PeerTests; // nonce -> creation time in milliseconds - - i2p::util::MemoryPool m_FragmentsPool; - i2p::util::MemoryPool m_IncompleteMessagesPool; - i2p::util::MemoryPool m_SentMessagesPool; - i2p::util::MemoryPoolMt m_PacketsPool; - - public: - // for HTTP only - const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; - const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; - }; -} -} - -#endif diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 019fffca..fc2355a5 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -17,14 +17,15 @@ namespace i2p { namespace transport -{ +{ SSU2Server::SSU2Server (): 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_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0), + m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL), m_IsThroughProxy (false) { } @@ -36,12 +37,32 @@ namespace transport i2p::config::GetOption ("ssu2.published", m_IsPublished); i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); bool found = false; - auto& addresses = i2p::context.GetRouterInfo ().GetAddresses (); - for (const auto& address: addresses) + auto addresses = i2p::context.GetRouterInfo ().GetAddresses (); + if (!addresses) return; + for (const auto& address: *addresses) { if (!address) continue; if (address->transportStyle == i2p::data::RouterInfo::eTransportSSU2) { + if (m_IsThroughProxy) + { + found = true; + if (address->IsV6 ()) + { + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); + if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE) + mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; + i2p::context.SetMTU (mtu, false); + } + else + { + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); + if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE) + mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; + i2p::context.SetMTU (mtu, true); + } + continue; // we don't need port for proxy + } auto port = address->port; if (!port) { @@ -49,9 +70,8 @@ namespace transport if (ssu2Port) port = ssu2Port; else { - bool ssu; i2p::config::GetOption("ssu", ssu); uint16_t p; i2p::config::GetOption ("port", p); - if (p) port = ssu ? (p + 1) : p; + if (p) port = p; } } if (port) @@ -59,8 +79,9 @@ namespace transport if (address->IsV4 ()) { found = true; + LogPrint (eLogDebug, "SSU2: Opening IPv4 socket at Start"); OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV4, port)); - m_ReceiveService.GetService ().post( + boost::asio::post (m_ReceiveService.GetService (), [this]() { Receive (m_SocketV4); @@ -70,9 +91,10 @@ namespace transport if (address->IsV6 ()) { found = true; + LogPrint (eLogDebug, "SSU2: Opening IPv6 socket at Start"); OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV6, port)); - m_ReceiveService.GetService ().post( - [this]() + boost::asio::post (m_ReceiveService.GetService (), + [this]() { Receive (m_SocketV6); }); @@ -80,90 +102,179 @@ namespace transport } } else - LogPrint (eLogError, "SSU2: Can't start server because port not specified"); + LogPrint (eLogCritical, "SSU2: Can't start server because port not specified"); } } if (found) + { + if (m_IsThroughProxy) + ConnectToProxy (); m_ReceiveService.Start (); + } ScheduleTermination (); + ScheduleCleanup (); + ScheduleResend (false); } } void SSU2Server::Stop () { if (IsRunning ()) - { + { m_TerminationTimer.cancel (); + m_CleanupTimer.cancel (); m_ResendTimer.cancel (); m_IntroducersUpdateTimer.cancel (); m_IntroducersUpdateTimerV6.cancel (); - } - + } + auto sessions = m_Sessions; for (auto& it: sessions) - { + { it.second->RequestTermination (eSSU2TerminationReasonRouterShutdown); it.second->Done (); - } - + } + if (context.SupportsV4 () || context.SupportsV6 ()) m_ReceiveService.Stop (); m_SocketV4.close (); m_SocketV6.close (); - + + if (m_UDPAssociateSocket) + { + m_UDPAssociateSocket->close (); + m_UDPAssociateSocket.reset (nullptr); + } + StopIOService (); m_Sessions.clear (); m_SessionsByRouterHash.clear (); m_PendingOutgoingSessions.clear (); m_Relays.clear (); + m_PeerTests.clear (); m_Introducers.clear (); m_IntroducersV6.clear (); + m_ConnectedRecently.clear (); + m_RequestedPeerTests.clear (); + + m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue); + m_ReceivedPacketsQueue.clear (); } void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) { if (localAddress.is_unspecified ()) return; if (localAddress.is_v4 ()) - { + { m_AddressV4 = localAddress; - int mtu = i2p::util::net::GetMTU (localAddress); + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); + if (!mtu) mtu = i2p::util::net::GetMTU (localAddress); if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; i2p::context.SetMTU (mtu, true); - } + } else if (localAddress.is_v6 ()) - { + { m_AddressV6 = localAddress; - int maxMTU = i2p::util::net::GetMaxMTU (localAddress.to_v6 ()); - int mtu = i2p::util::net::GetMTU (localAddress); - if (mtu > maxMTU) mtu = maxMTU; + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); + if (!mtu) + { + int maxMTU = i2p::util::net::GetMaxMTU (localAddress.to_v6 ()); + mtu = i2p::util::net::GetMTU (localAddress); + if (mtu > maxMTU) mtu = maxMTU; + } + 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); - } - } + } + } bool SSU2Server::IsSupported (const boost::asio::ip::address& addr) const { + if (m_IsThroughProxy) + return m_SocketV4.is_open (); if (addr.is_v4 ()) - { - if (m_SocketV4.is_open ()) + { + if (m_SocketV4.is_open ()) return true; - } + } else if (addr.is_v6 ()) - { - if (m_SocketV6.is_open ()) + { + if (m_SocketV6.is_open ()) return true; } return false; - } + } uint16_t SSU2Server::GetPort (bool v4) const { boost::system::error_code ec; - boost::asio::ip::udp::endpoint ep = v4 ? m_SocketV4.local_endpoint (ec) : m_SocketV6.local_endpoint (ec); + boost::asio::ip::udp::endpoint ep = (v4 || m_IsThroughProxy) ? m_SocketV4.local_endpoint (ec) : m_SocketV6.local_endpoint (ec); if (ec) return 0; return ep.port (); + } + + bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max) + { + if (!ep.port () || ep.address ().is_unspecified ()) return false; + std::lock_guard l(m_ConnectedRecentlyMutex); + auto it = m_ConnectedRecently.find (ep); + if (it != m_ConnectedRecently.end ()) + { + if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION)) + return true; + else if (max) + m_ConnectedRecently.erase (it); + } + return false; + } + + void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts) + { + if (!ep.port () || ep.address ().is_unspecified () || + i2p::util::GetSecondsSinceEpoch () > ts + SSU2_MAX_HOLE_PUNCH_EXPIRATION) return; + std::lock_guard l(m_ConnectedRecentlyMutex); + auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts); + if (!added && ts > it->second) + it->second = ts; // renew timestamp of existing endpoint + } + + void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr from) + { + if (offset) + { + if (m_PendingTimeOffset) // one more + { + if (m_PendingTimeOffsetFrom && from && + m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers + { + if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW) + { + offset = (m_PendingTimeOffset + offset)/2; // average + LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds"); + i2p::util::AdjustTimeOffset (offset); + } + else + LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted"); + m_PendingTimeOffset = 0; + m_PendingTimeOffsetFrom = nullptr; + } + else + LogPrint (eLogWarning, "SSU2: Time offsets from same router. Clock not adjusted"); + } + else + { + m_PendingTimeOffset = offset; // first + m_PendingTimeOffsetFrom = from; + } + } + else + { + m_PendingTimeOffset = 0; // reset + m_PendingTimeOffsetFrom = nullptr; + } } boost::asio::ip::udp::socket& SSU2Server::OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint) @@ -171,18 +282,54 @@ 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)); - socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU2_SOCKET_RECEIVE_BUFFER_SIZE)); - socket.set_option (boost::asio::socket_base::send_buffer_size (SSU2_SOCKET_SEND_BUFFER_SIZE)); + + uint64_t bufferSize = i2p::context.GetBandwidthLimit() * 1024 / 5; // max lag = 200ms + bufferSize = std::max(SSU2_SOCKET_MIN_BUFFER_SIZE, std::min(bufferSize, SSU2_SOCKET_MAX_BUFFER_SIZE)); + + boost::asio::socket_base::receive_buffer_size receiveBufferSizeSet (bufferSize); + boost::asio::socket_base::send_buffer_size sendBufferSizeSet (bufferSize); + socket.set_option (receiveBufferSizeSet); + socket.set_option (sendBufferSizeSet); + boost::asio::socket_base::receive_buffer_size receiveBufferSizeGet; + boost::asio::socket_base::send_buffer_size sendBufferSizeGet; + socket.get_option (receiveBufferSizeGet); + socket.get_option (sendBufferSizeGet); + if (receiveBufferSizeGet.value () != receiveBufferSizeSet.value () || + sendBufferSizeGet.value () != sendBufferSizeSet.value ()) + { + LogPrint (eLogWarning, "SSU2: Socket receive buffer size: requested = ", + receiveBufferSizeSet.value (), ", got = ", receiveBufferSizeGet.value ()); + LogPrint (eLogWarning, "SSU2: Socket send buffer size: requested = ", + sendBufferSizeSet.value (), ", got = ", sendBufferSizeGet.value ()); + } + else + { + LogPrint (eLogInfo, "SSU2: Socket receive buffer size: ", receiveBufferSizeGet.value ()); + LogPrint (eLogInfo, "SSU2: Socket send buffer size: ", sendBufferSizeGet.value ()); + } + + socket.non_blocking (true); + } + catch (std::exception& ex ) + { + LogPrint (eLogCritical, "SSU2: Failed to open socket on ", localEndpoint.address (), ": ", ex.what()); + ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint.address (), ": ", ex.what ()); + return socket; + } + try + { socket.bind (localEndpoint); LogPrint (eLogInfo, "SSU2: Start listening on ", localEndpoint); } catch (std::exception& ex ) { - LogPrint (eLogError, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what()); - ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint, ": ", ex.what ()); + LogPrint (eLogWarning, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what(), ". Actual endpoint is ", socket.local_endpoint ()); + // we can continue without binding being firewalled } return socket; } @@ -197,25 +344,49 @@ namespace transport void SSU2Server::HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred, Packet * packet, boost::asio::ip::udp::socket& socket) { - if (!ecode) + if (!ecode + || ecode == boost::asio::error::connection_refused + || ecode == boost::asio::error::connection_reset + || ecode == boost::asio::error::network_reset + || ecode == boost::asio::error::network_unreachable + || ecode == boost::asio::error::host_unreachable +#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO + || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ + || ecode.value() == boost::winapi::WSAENETRESET_ // 10052 + || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ + || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ +#endif + ) + // just try continue reading when received ICMP response otherwise socket can crash, + // but better to find out which host were sent it and mark that router as unreachable { i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); + if (bytes_transferred < SSU2_MIN_RECEIVED_PACKET_SIZE) + { + // drop too short packets + m_PacketsPool.ReleaseMt (packet); + Receive (socket); + return; + } packet->len = bytes_transferred; - + boost::system::error_code ec; size_t moreBytes = socket.available (ec); if (!ec && moreBytes) { - std::vector packets; + std::list packets; packets.push_back (packet); - while (moreBytes && packets.size () < 32) - { + while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH) + { packet = m_PacketsPool.AcquireMt (); packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); if (!ec) { i2p::transport::transports.UpdateReceivedBytes (packet->len); - packets.push_back (packet); + if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE) + packets.push_back (packet); + else // drop too short packets + m_PacketsPool.ReleaseMt (packet); moreBytes = socket.available(ec); if (ec) break; } @@ -226,10 +397,10 @@ namespace transport break; } } - GetService ().post (std::bind (&SSU2Server::HandleReceivedPackets, this, packets)); + InsertToReceivedPacketsQueue (packets); } else - GetService ().post (std::bind (&SSU2Server::HandleReceivedPacket, this, packet)); + InsertToReceivedPacketsQueue (packet); Receive (socket); } else @@ -238,41 +409,93 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU2: Receive error: code ", ecode.value(), ": ", ecode.message ()); - auto ep = socket.local_endpoint (); - socket.close (); - OpenSocket (ep); - Receive (socket); + if (m_IsThroughProxy) + { + m_UDPAssociateSocket.reset (nullptr); + m_ProxyRelayEndpoint.reset (nullptr); + m_SocketV4.close (); + ConnectToProxy (); + } + else + { + auto ep = socket.local_endpoint (); + LogPrint (eLogCritical, "SSU2: Reopening socket in HandleReceivedFrom: code ", ecode.value(), ": ", ecode.message ()); + OpenSocket (ep); + Receive (socket); + } } } } - void SSU2Server::HandleReceivedPacket (Packet * packet) + void SSU2Server::HandleReceivedPackets (std::list&& packets) { - if (packet) - { - ProcessNextPacket (packet->buf, packet->len, packet->from); - m_PacketsPool.ReleaseMt (packet); - if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) - m_LastSession->FlushData (); - } - } - - void SSU2Server::HandleReceivedPackets (std::vector packets) - { - for (auto& packet: packets) - ProcessNextPacket (packet->buf, packet->len, packet->from); + if (packets.empty ()) return; + if (m_IsThroughProxy) + for (auto it: packets) + ProcessNextPacketFromProxy (it->buf, it->len); + else + for (auto it: packets) + ProcessNextPacket (it->buf, it->len, it->from); m_PacketsPool.ReleaseMt (packets); - if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) + if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) m_LastSession->FlushData (); } - void SSU2Server::AddSession (std::shared_ptr session) + void SSU2Server::InsertToReceivedPacketsQueue (Packet * packet) + { + if (!packet) return; + bool empty = false; + { + std::lock_guard l(m_ReceivedPacketsQueueMutex); + empty = m_ReceivedPacketsQueue.empty (); + m_ReceivedPacketsQueue.push_back (packet); + } + if (empty) + boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); + } + + void SSU2Server::InsertToReceivedPacketsQueue (std::list& packets) + { + if (packets.empty ()) return; + size_t queueSize = 0; + { + std::lock_guard l(m_ReceivedPacketsQueueMutex); + queueSize = m_ReceivedPacketsQueue.size (); + if (queueSize < SSU2_MAX_RECEIVED_QUEUE_SIZE) + m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets); + else + { + LogPrint (eLogError, "SSU2: Received queue size ", queueSize, " exceeds max size", SSU2_MAX_RECEIVED_QUEUE_SIZE); + m_PacketsPool.ReleaseMt (packets); + queueSize = 0; // invoke processing just in case + } + } + if (!queueSize) + boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); + } + + void SSU2Server::HandleReceivedPacketsQueue () + { + std::list receivedPackets; + { + std::lock_guard l(m_ReceivedPacketsQueueMutex); + m_ReceivedPacketsQueue.swap (receivedPackets); + } + HandleReceivedPackets (std::move (receivedPackets)); + } + + bool SSU2Server::AddSession (std::shared_ptr session) { if (session) { - m_Sessions.emplace (session->GetConnID (), session); - AddSessionByRouterHash (session); + if (m_Sessions.emplace (session->GetConnID (), session).second) + { + if (session->GetState () != eSSU2SessionStatePeerTest) + AddSessionByRouterHash (session); + return true; + } } + return false; } void SSU2Server::RemoveSession (uint64_t connID) @@ -280,14 +503,27 @@ namespace transport auto it = m_Sessions.find (connID); if (it != m_Sessions.end ()) { - auto ident = it->second->GetRemoteIdentity (); - if (ident) - m_SessionsByRouterHash.erase (ident->GetIdentHash ()); + if (it->second->GetState () != eSSU2SessionStatePeerTest) + { + auto ident = it->second->GetRemoteIdentity (); + if (ident) + { + std::lock_guard l(m_SessionsByRouterHashMutex); + auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ()); + if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second.lock ()) + m_SessionsByRouterHash.erase (it1); + } + } if (m_LastSession == it->second) m_LastSession = nullptr; m_Sessions.erase (it); } } + + void SSU2Server::RequestRemoveSession (uint64_t connID) + { + boost::asio::post (GetService (), [connID, this]() { RemoveSession (connID); }); + } void SSU2Server::AddSessionByRouterHash (std::shared_ptr session) { @@ -296,16 +532,26 @@ namespace transport auto ident = session->GetRemoteIdentity (); if (ident) { - auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); - if (!ret.second) + std::shared_ptr oldSession; { + std::lock_guard l(m_SessionsByRouterHashMutex); + auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); + if (!ret.second) + { + oldSession = ret.first->second.lock (); + // update session + ret.first->second = session; + } + } + if (oldSession && oldSession != session) + { // session already exists LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); + // move unsent msgs to new session + oldSession->MoveSendQueue (session); // terminate existing - GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession)); - // update session - ret.first->second = session; - } + boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession)); + } } } } @@ -313,19 +559,30 @@ namespace transport bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) { if (!session) return false; + std::lock_guard l(m_PendingOutgoingSessionsMutex); return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; } - std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) const + std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) { + std::lock_guard l(m_SessionsByRouterHashMutex); auto it = m_SessionsByRouterHash.find (ident); if (it != m_SessionsByRouterHash.end ()) - return it->second; + { + if (!it->second.expired ()) + { + auto s = it->second.lock (); + if (s && s->GetState () != eSSU2SessionStateTerminated) + return s; + } + m_SessionsByRouterHash.erase (it); + } return nullptr; - } + } std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const - { + { + std::lock_guard l(m_PendingOutgoingSessionsMutex); auto it = m_PendingOutgoingSessions.find (ep); if (it != m_PendingOutgoingSessions.end ()) return it->second; @@ -334,37 +591,36 @@ namespace transport void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) { + std::lock_guard l(m_PendingOutgoingSessionsMutex); m_PendingOutgoingSessions.erase (ep); - } - - std::shared_ptr SSU2Server::GetRandomSession ( - i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const + } + + std::shared_ptr SSU2Server::GetRandomPeerTestSession ( + i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) { if (m_Sessions.empty ()) return nullptr; - uint16_t ind; - RAND_bytes ((uint8_t *)&ind, sizeof (ind)); - ind %= m_Sessions.size (); + int ind = m_Rng () % m_Sessions.size (); auto it = m_Sessions.begin (); std::advance (it, ind); while (it != m_Sessions.end ()) { - if ((it->second->GetRemoteTransports () & remoteTransports) && + if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) && it->second->GetRemoteIdentity ()->GetIdentHash () != excluded) return it->second; it++; } - // not found, try from begining + // not found, try from beginning it = m_Sessions.begin (); while (it != m_Sessions.end () && ind) { - if ((it->second->GetRemoteTransports () & remoteTransports) && + if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) && it->second->GetRemoteIdentity ()->GetIdentHash () != excluded) return it->second; it++; ind--; - } + } return nullptr; - } - + } + void SSU2Server::AddRelay (uint32_t tag, std::shared_ptr relay) { m_Relays.emplace (tag, relay); @@ -380,14 +636,51 @@ namespace transport auto it = m_Relays.find (tag); if (it != m_Relays.end ()) { - if (it->second->IsEstablished ()) - return it->second; - else - m_Relays.erase (it); + if (!it->second.expired ()) + { + auto s = it->second.lock (); + if (s && s->IsEstablished ()) + return s; + } + m_Relays.erase (it); } return nullptr; } + bool SSU2Server::AddPeerTest (uint32_t nonce, std::shared_ptr aliceSession, uint64_t ts) + { + return m_PeerTests.emplace (nonce, std::pair{ aliceSession, ts }).second; + } + + std::shared_ptr SSU2Server::GetPeerTest (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + { + auto s = it->second.first.lock (); + m_PeerTests.erase (it); + return s; + } + return nullptr; + } + + bool SSU2Server::AddRequestedPeerTest (uint32_t nonce, std::shared_ptr session, uint64_t ts) + { + return m_RequestedPeerTests.emplace (nonce, std::pair{ session, ts }).second; + } + + std::shared_ptr SSU2Server::GetRequestedPeerTest (uint32_t nonce) + { + auto it = m_RequestedPeerTests.find (nonce); + if (it != m_RequestedPeerTests.end ()) + { + auto s = it->second.first.lock (); + m_RequestedPeerTests.erase (it); + return s; + } + return nullptr; + } + void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { if (len < 24) return; @@ -408,41 +701,45 @@ namespace transport switch (m_LastSession->GetState ()) { case eSSU2SessionStateEstablished: - case eSSU2SessionStateSessionConfirmedSent: - m_LastSession->ProcessData (buf, len); + case eSSU2SessionStateSessionConfirmedSent: + m_LastSession->ProcessData (buf, len, senderEndpoint); break; case eSSU2SessionStateSessionCreatedSent: if (!m_LastSession->ProcessSessionConfirmed (buf, len)) { m_LastSession->Done (); m_LastSession = nullptr; - } + } break; case eSSU2SessionStateIntroduced: - if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) + if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) m_LastSession->SetRemoteEndpoint (senderEndpoint); - if (m_LastSession->GetRemoteEndpoint () == senderEndpoint) + if (m_LastSession->GetRemoteEndpoint ().address () == senderEndpoint.address ()) // port might be different m_LastSession->ProcessHolePunch (buf, len); else { - LogPrint (eLogWarning, "SSU2: HolePunch endpoint ", senderEndpoint, - " doesn't match RelayResponse ", m_LastSession->GetRemoteEndpoint ()); + LogPrint (eLogWarning, "SSU2: HolePunch address ", senderEndpoint.address (), + " doesn't match RelayResponse ", m_LastSession->GetRemoteEndpoint ().address ()); m_LastSession->Done (); - m_LastSession = nullptr; - } + m_LastSession = nullptr; + } break; case eSSU2SessionStatePeerTest: m_LastSession->SetRemoteEndpoint (senderEndpoint); m_LastSession->ProcessPeerTest (buf, len); break; - case eSSU2SessionStateClosing: - m_LastSession->ProcessData (buf, len); // we might receive termintaion block - if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) - m_LastSession->RequestTermination (eSSU2TerminationReasonIdleTimeout); // send termination again + case eSSU2SessionStateHolePunch: + m_LastSession->ProcessFirstIncomingMessage (connID, buf, len); // SessionRequest break; + case eSSU2SessionStateClosing: + m_LastSession->ProcessData (buf, len, senderEndpoint); // we might receive termintaion block + if (m_LastSession && m_LastSession->GetState () == eSSU2SessionStateClosing) + m_LastSession->RequestTermination (eSSU2TerminationReasonIdleTimeout); // send termination again + break; + case eSSU2SessionStateClosingConfirmed: case eSSU2SessionStateTerminated: m_LastSession = nullptr; - break; + break; default: LogPrint (eLogWarning, "SSU2: Invalid session state ", (int)m_LastSession->GetState ()); } @@ -455,110 +752,162 @@ namespace transport { if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent && it1->second->ProcessSessionCreated (buf, len)) - m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint + { + std::lock_guard l(m_PendingOutgoingSessionsMutex); + m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint + } else it1->second->ProcessRetry (buf, len); } - else + else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) { // assume new incoming session auto session = std::make_shared (*this); session->SetRemoteEndpoint (senderEndpoint); session->ProcessFirstIncomingMessage (connID, buf, len); } + else + LogPrint (eLogError, "SSU2: Incoming packet received from invalid endpoint ", senderEndpoint); } } void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { + if (m_IsThroughProxy) + { + SendThroughProxy (header, headerLen, nullptr, 0, payload, payloadLen, to); + return; + } + std::vector bufs { boost::asio::buffer (header, headerLen), boost::asio::buffer (payload, payloadLen) }; + boost::system::error_code ec; if (to.address ().is_v6 ()) + { + if (!m_SocketV6.is_open ()) return; m_SocketV6.send_to (bufs, to, 0, ec); + } else + { + if (!m_SocketV4.is_open ()) return; m_SocketV4.send_to (bufs, to, 0, ec); + } + if (!ec) i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen); else - LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); + { + LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError, + "SSU2: Send exception: ", ec.message (), " to ", to); + } } void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { + if (m_IsThroughProxy) + { + SendThroughProxy (header, headerLen, headerX, headerXLen, payload, payloadLen, to); + return; + } + std::vector bufs { boost::asio::buffer (header, headerLen), boost::asio::buffer (headerX, headerXLen), boost::asio::buffer (payload, payloadLen) }; + boost::system::error_code ec; if (to.address ().is_v6 ()) + { + if (!m_SocketV6.is_open ()) return; m_SocketV6.send_to (bufs, to, 0, ec); + } else + { + if (!m_SocketV4.is_open ()) return; m_SocketV4.send_to (bufs, to, 0, ec); + } if (!ec) i2p::transport::transports.UpdateSentBytes (headerLen + headerXLen + payloadLen); else - LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); + { + LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError, + "SSU2: Send exception: ", ec.message (), " to ", to); + } } + bool SSU2Server::CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest) + { + auto s = FindPendingOutgoingSession (ep); + if (s) + { + if (peerTest) + { + // if peer test requested add it to the list for pending session + auto onEstablished = s->GetOnEstablished (); + if (onEstablished) + s->SetOnEstablished ([s, onEstablished]() + { + onEstablished (); + s->SendPeerTest (); + }); + else + s->SetOnEstablished ([s]() { s->SendPeerTest (); }); + } + return true; + } + return false; + } + bool SSU2Server::CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest) { if (router && address) { // check if no session - auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); - if (it != m_SessionsByRouterHash.end ()) + auto existingSession = FindSession (router->GetIdentHash ()); + if (existingSession) { // session with router found, trying to send peer test if requested - if (peerTest && it->second->IsEstablished ()) - { - auto session = it->second; - GetService ().post ([session]() { session->SendPeerTest (); }); - } + if (peerTest && existingSession->IsEstablished ()) + boost::asio::post (GetService (), [existingSession]() { existingSession->SendPeerTest (); }); return false; - } + } // check is no pending session bool isValidEndpoint = !address->host.is_unspecified () && address->port; if (isValidEndpoint) - { - if (i2p::util::net::IsInReservedRange(address->host)) return false; - auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port)); - if (s) - { - if (peerTest) - { - // if peer test requested add it to the list for pending session - auto onEstablished = s->GetOnEstablished (); - if (onEstablished) - s->SetOnEstablished ([s, onEstablished]() - { - onEstablished (); - s->SendPeerTest (); - }); - else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); - } - return false; + { + if (i2p::transport::transports.IsInReservedRange(address->host)) return false; + if (CheckPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port), peerTest)) return false; + } + + auto session = std::make_shared (*this, router, address); + if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) + { + // router doesn't publish endpoint, but we connected before and hole punch might be alive + auto ep = router->GetProfile ()->GetLastEndpoint (); + if (IsConnectedRecently (ep, false)) + { + if (CheckPendingOutgoingSession (ep, peerTest)) return false; + session->SetRemoteEndpoint (ep); + isValidEndpoint = true; } } - - auto session = std::make_shared (*this, router, address); if (peerTest) session->SetOnEstablished ([session]() {session->SendPeerTest (); }); - if (address->UsesIntroducer ()) - GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session)); - else if (isValidEndpoint) // we can't connect without endpoint - GetService ().post ([session]() { session->Connect (); }); + if (isValidEndpoint) // we know endpoint + boost::asio::post (GetService (), [session]() { session->Connect (); }); + else if (address->UsesIntroducer ()) // we don't know endpoint yet + boost::asio::post (GetService (), std::bind (&SSU2Server::ConnectThroughIntroducer, this, session)); else return false; } @@ -573,82 +922,119 @@ namespace transport auto address = session->GetAddress (); if (!address) return; session->WaitForIntroduction (); + auto ts = i2p::util::GetSecondsSinceEpoch (); + std::vector indices; int i = 0; // try to find existing session first for (auto& it: address->ssu->introducers) { - auto it1 = m_SessionsByRouterHash.find (it.iKey); - if (it1 != m_SessionsByRouterHash.end ()) + if (it.iTag && ts < it.iExp) { - it1->second->Introduce (session, it.iTag); - return; - } + auto s = FindSession (it.iH); + if (s) + { + auto addr = s->GetAddress (); + if (addr && addr->IsIntroducer ()) + { + s->Introduce (session, it.iTag); + return; + } + } + else + indices.push_back(i); + } + i++; } // we have to start a new session to an introducer - auto ts = i2p::util::GetSecondsSinceEpoch (); + std::vector newRouters; std::shared_ptr r; + std::shared_ptr addr; uint32_t relayTag = 0; - if (!address->ssu->introducers.empty ()) - { - std::vector indicies; - for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indicies.push_back(i); - if (indicies.size () > 1) - std::shuffle (indicies.begin(), indicies.end(), std::mt19937(std::random_device()())); + if (!indices.empty ()) + { + if (indices.size () > 1) + std::shuffle (indices.begin(), indices.end(), m_Rng); - for (auto i: indicies) + for (auto ind: indices) { - const auto& introducer = address->ssu->introducers[indicies[i]]; - if (introducer.iTag && ts < introducer.iExp) + const auto& introducer = address->ssu->introducers[ind]; + // introducer is not expired, because in indices + r = i2p::data::netdb.FindRouter (introducer.iH); + if (r) { - r = i2p::data::netdb.FindRouter (introducer.iKey); - if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) + if (r->IsPublishedOn (i2p::context.GetRouterInfo ().GetCompatibleTransports (false) & // outgoing + (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))) { relayTag = introducer.iTag; - if (relayTag) break; - } - } - } - } - if (r) - { - if (relayTag) - { - // introducer and tag found connect to it through SSU2 - auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); - if (addr) - { - bool isValidEndpoint = !addr->host.is_unspecified () && addr->port && - !i2p::util::net::IsInReservedRange(addr->host); - if (isValidEndpoint) - { - auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); - if (!s) - { - s = std::make_shared (*this, r, addr); - s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); }); - s->Connect (); - } + addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port && + !i2p::transport::transports.IsInReservedRange(addr->host)) + break; else { - auto onEstablished = s->GetOnEstablished (); - if (onEstablished) - s->SetOnEstablished ([session, s, relayTag, onEstablished]() - { - onEstablished (); - s->Introduce (session, relayTag); - }); - else - s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); }); + // address is invalid or not intrudcer, try another SSU2 address if exists + if (address->IsV4 ()) + { + if (i2p::context.SupportsV6 ()) + addr = r->GetSSU2V6Address (); + } + else + { + if (i2p::context.SupportsV4 ()) + addr = r->GetSSU2V4Address (); + } + if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port && + !i2p::transport::transports.IsInReservedRange(addr->host)) + break; + else + { + // all addresses are invalid, try next introducer + relayTag = 0; + addr = nullptr; + r = nullptr; + } } - } + } + else + r = nullptr; + } + else if (!i2p::data::IsRouterBanned (introducer.iH)) + newRouters.push_back (introducer.iH); + } + } + if (r) + { + if (relayTag && addr) + { + // introducer and tag found connect to it through SSU2 + auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); + if (!s) + { + s = std::make_shared (*this, r, addr); + s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); }); + s->Connect (); + } + else + { + auto onEstablished = s->GetOnEstablished (); + if (onEstablished) + s->SetOnEstablished ([session, s, relayTag, onEstablished]() + { + onEstablished (); + s->Introduce (session, relayTag); + }); + else + s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); }); } } + else + session->Done (); } else { // introducers not found, try to request them - for (auto& it: address->ssu->introducers) - if (it.iTag && ts < it.iExp) - i2p::data::netdb.RequestDestination (it.iKey); + for (auto& it: newRouters) + i2p::data::netdb.RequestDestination (it); + session->Done (); // don't wait for connect timeout } } @@ -657,24 +1043,27 @@ namespace transport if (!router) return false; auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address (); if (!addr) return false; - auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); - if (it != m_SessionsByRouterHash.end ()) + auto session = FindSession (router->GetIdentHash ()); + if (session) { - auto s = it->second; - if (it->second->IsEstablished ()) - GetService ().post ([s]() { s->SendPeerTest (); }); - else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); - return true; + auto remoteAddr = session->GetAddress (); + if (!remoteAddr || !remoteAddr->IsPeerTesting () || + (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; + if (session->IsEstablished ()) + boost::asio::post (GetService (), [session]() { session->SendPeerTest (); }); + else + session->SetOnEstablished ([session]() { session->SendPeerTest (); }); + return true; } - else + else CreateSession (router, addr, true); return true; - } - + } + void SSU2Server::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU2_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds( + SSU2_TERMINATION_CHECK_TIMEOUT + m_Rng () % SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE)); m_TerminationTimer.async_wait (std::bind (&SSU2Server::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -684,16 +1073,20 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) + { - if (it->second->IsTerminationTimeoutExpired (ts)) + std::lock_guard l(m_PendingOutgoingSessionsMutex); + for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) { - //it->second->Terminate (); - it = m_PendingOutgoingSessions.erase (it); + if (it->second->IsTerminationTimeoutExpired (ts)) + { + //it->second->Terminate (); + it = m_PendingOutgoingSessions.erase (it); + } + else + it++; } - else - it++; - } + } for (auto it: m_Sessions) { @@ -711,21 +1104,40 @@ namespace transport it.second->CleanUp (ts); } - for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) - { - if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) - it = m_SessionsByRouterHash.erase (it); - else - it++; - } + ScheduleTermination (); + } + } + 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) + if (it->second.expired ()) it = m_Relays.erase (it); else it++; - } + } + + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT || it->second.first.expired ()) + { + LogPrint (eLogInfo, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds or session invalid. Deleted"); + it = m_PeerTests.erase (it); + } + else + it++; + } for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) { @@ -742,16 +1154,47 @@ namespace transport else it++; } + + for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); ) + { + if (ts > it->second + SSU2_MAX_HOLE_PUNCH_EXPIRATION) + it = m_ConnectedRecently.erase (it); + else + it++; + } + + for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) + it = m_RequestedPeerTests.erase (it); + else + it++; + } + + { + std::lock_guard l(m_SessionsByRouterHashMutex); + for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) + { + if (it->second.expired ()) + it = m_SessionsByRouterHash.erase (it); + else + it++; + } + } m_PacketsPool.CleanUpMt (); m_SentPacketsPool.CleanUp (); - ScheduleTermination (); + m_IncompleteMessagesPool.CleanUp (); + m_FragmentsPool.CleanUp (); + ScheduleCleanup (); } } - void SSU2Server::ScheduleResend () + void SSU2Server::ScheduleResend (bool more) { - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(SSU2_RESEND_CHECK_TIMEOUT)); + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? + (SSU2_RESEND_CHECK_MORE_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE): + (SSU2_RESEND_CHECK_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE))); m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer, this, std::placeholders::_1)); } @@ -760,12 +1203,17 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { + size_t resentPacketsNum = 0; auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_Sessions) - it.second->Resend (ts); + { + if (ts >= it.second->GetLastResendTime () + SSU2_RESEND_CHECK_TIMEOUT) + resentPacketsNum += it.second->Resend (ts); + if (resentPacketsNum > SSU2_MAX_RESEND_PACKETS) break; + } for (auto it: m_PendingOutgoingSessions) it.second->Resend (ts); - ScheduleResend (); + ScheduleResend (resentPacketsNum > SSU2_MAX_RESEND_PACKETS); } } @@ -774,140 +1222,178 @@ namespace transport m_OutgoingTokens[ep] = {token, exp}; } - uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const + uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) { auto it = m_OutgoingTokens.find (ep); if (it != m_OutgoingTokens.end ()) { if (i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_THRESHOLD > it->second.second) - return 0; // token expired + { + // token expired + m_OutgoingTokens.erase (it); + return 0; + } return it->second.first; - } + } return 0; } uint64_t SSU2Server::GetIncomingToken (const boost::asio::ip::udp::endpoint& ep) { + auto ts = i2p::util::GetSecondsSinceEpoch (); auto it = m_IncomingTokens.find (ep); if (it != m_IncomingTokens.end ()) - return it->second.first; + { + if (ts + SSU2_TOKEN_EXPIRATION_THRESHOLD <= it->second.second) + return it->second.first; + else // token expired + m_IncomingTokens.erase (it); + } uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - m_IncomingTokens.emplace (ep, std::make_pair (token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT)); + if (!token) token = 1; // token can't be zero + m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)); return token; } std::pair SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) { - m_IncomingTokens.erase (ep); // drop previous uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - auto ret = std::make_pair (token, i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT); - m_IncomingTokens.emplace (ep, ret); - return ret; - } + if (!token) token = 1; // token can't be zero + uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT; + auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires); + if (!inserted) + it->second = { token, expires }; // override + return it->second; + } - std::list > SSU2Server::FindIntroducers (int maxNumIntroducers, - bool v4, const std::set& excluded) const + std::vector > SSU2Server::FindIntroducers (int maxNumIntroducers, + bool v4, const std::unordered_set& excluded) { - std::list > ret; + std::vector > ret; + if (maxNumIntroducers <= 0 || m_Sessions.empty ()) return ret; + + std::vector > eligible; + eligible.reserve (m_Sessions.size ()/2); + auto ts = i2p::util::GetSecondsSinceEpoch (); for (const auto& s : m_Sessions) { - if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) && + if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) && + ts < s.second->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION/2 && !excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) && ((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) || - (!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6)))) - ret.push_back (s.second); - } - if ((int)ret.size () > maxNumIntroducers) - { - // shink ret randomly - int sz = ret.size () - maxNumIntroducers; - for (int i = 0; i < sz; i++) - { - auto ind = rand () % ret.size (); - auto it = ret.begin (); - std::advance (it, ind); - ret.erase (it); - } + (!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6)))) + eligible.push_back (s.second); } + + if (eligible.size () <= (size_t)maxNumIntroducers) + return eligible; + else + std::sample (eligible.begin(), eligible.end(), std::back_inserter(ret), maxNumIntroducers, m_Rng); return ret; - } + } void SSU2Server::UpdateIntroducers (bool v4) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::list newList; + std::list > newList, impliedList; auto& introducers = v4 ? m_Introducers : m_IntroducersV6; - std::set excluded; - for (const auto& it : introducers) + std::unordered_set excluded; + for (const auto& [ident, tag] : introducers) { - std::shared_ptr session; - auto it1 = m_SessionsByRouterHash.find (it); - if (it1 != m_SessionsByRouterHash.end ()) + std::shared_ptr session = FindSession (ident); + if (session) + excluded.insert (ident); + if (session) { - session = it1->second; - excluded.insert (it); - } - if (session && session->IsEstablished ()) - { - if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) + if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer? + ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) + { session->SendKeepAlive (); - if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) - newList.push_back (it); + if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) + { + newList.push_back ({ident, session->GetRelayTag ()}); + if (tag != session->GetRelayTag ()) + { + LogPrint (eLogDebug, "SSU2: Introducer session to ", session->GetIdentHashBase64() , " was replaced. iTag ", tag, "->", session->GetRelayTag ()); + i2p::context.UpdateSSU2Introducer (ident, v4, session->GetRelayTag (), + session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION); + } + } + else + { + impliedList.push_back ({ident, session->GetRelayTag ()}); // keep in introducers list, but not publish + session = nullptr; + } + } else session = nullptr; } + if (!session) - i2p::context.RemoveSSU2Introducer (it, v4); - } + i2p::context.RemoveSSU2Introducer (ident, v4); + } + int numOldSessions = 0; if (newList.size () < SSU2_MAX_NUM_INTRODUCERS) { auto sessions = FindIntroducers (SSU2_MAX_NUM_INTRODUCERS - newList.size (), v4, excluded); - if (sessions.empty () && !introducers.empty ()) + if (sessions.empty () && !impliedList.empty ()) { - // bump creation time for previous introducers if no new sessions found LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing"); - for (auto& it : introducers) - { - auto it1 = m_SessionsByRouterHash.find (it); - if (it1 != m_SessionsByRouterHash.end ()) + for (const auto& it : impliedList) + { + auto session = FindSession (it.first); + if (session) { - auto session = it1->second; - if (session->IsEstablished ()) + if (std::find_if (newList.begin (), newList.end (), + [&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ()) { - session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION); - if (std::find (newList.begin (), newList.end (), it) == newList.end ()) - { - newList.push_back (it); - sessions.push_back (session); - } + sessions.push_back (session); + numOldSessions++; } - } - } - } - + } + } + impliedList.clear (); + } + for (const auto& it : sessions) { + uint32_t tag = it->GetRelayTag (); + uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; + if (!tag && ts >= exp) + continue; // don't publish expired introducer i2p::data::RouterInfo::Introducer introducer; - introducer.iTag = it->GetRelayTag (); - introducer.iKey = it->GetRemoteIdentity ()->GetIdentHash (); - introducer.iExp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; + introducer.iTag = tag; + introducer.iH = it->GetRemoteIdentity ()->GetIdentHash (); + introducer.iExp = exp; excluded.insert (it->GetRemoteIdentity ()->GetIdentHash ()); if (i2p::context.AddSSU2Introducer (introducer, v4)) { - LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ", + LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ", i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ())); - newList.push_back (it->GetRemoteIdentity ()->GetIdentHash ()); + newList.push_back ({ it->GetRemoteIdentity ()->GetIdentHash (), tag }); + it->SendKeepAlive (); if (newList.size () >= SSU2_MAX_NUM_INTRODUCERS) break; - } - } - } + } + } + } introducers = newList; - if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS) + if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS || numOldSessions) { - for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS; i++) + // we need to create more sessions with relay tag + + // exclude all existing sessions + excluded.clear (); + { + std::lock_guard l(m_SessionsByRouterHashMutex); + for (const auto& [ident, s] : m_SessionsByRouterHash) + excluded.insert (ident); + } + + // session about to expire are not counted + for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS + numOldSessions; i++) { auto introducer = i2p::data::netdb.GetRandomSSU2Introducer (v4, excluded); if (introducer) @@ -926,17 +1412,19 @@ namespace transport } } } + introducers.splice (introducers.end (), impliedList); // insert non-published, but non-expired introducers back } void SSU2Server::ScheduleIntroducersUpdateTimer () { if (m_IsPublished) - { - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL)); + { + m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds( + SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, this, std::placeholders::_1, true)); - } - } + } + } void SSU2Server::RescheduleIntroducersUpdateTimer () { @@ -945,21 +1433,23 @@ namespace transport m_IntroducersUpdateTimer.cancel (); i2p::context.ClearSSU2Introducers (true); m_Introducers.clear (); - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL/2)); + m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds( + (SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, this, std::placeholders::_1, true)); - } + } } void SSU2Server::ScheduleIntroducersUpdateTimerV6 () { if (m_IsPublished) - { - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL)); + { + m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds( + SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)); m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, this, std::placeholders::_1, false)); - } - } + } + } void SSU2Server::RescheduleIntroducersUpdateTimerV6 () { @@ -968,12 +1458,13 @@ namespace transport m_IntroducersUpdateTimerV6.cancel (); i2p::context.ClearSSU2Introducers (false); m_IntroducersV6.clear (); - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL/2)); + m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds( + (SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2)); m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, this, std::placeholders::_1, false)); - } + } } - + void SSU2Server::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) { if (ecode != boost::asio::error::operation_aborted) @@ -981,7 +1472,7 @@ namespace transport // timeout expired if (v4) { - if (i2p::context.GetStatus () == eRouterStatusTesting) + if (i2p::context.GetTesting ()) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimer (); @@ -997,14 +1488,14 @@ namespace transport // we are firewalled auto addr = i2p::context.GetRouterInfo ().GetSSU2V4Address (); if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachableSSU2 (true, false); // v4 - + i2p::context.SetUnreachable (true, false); // v4 + UpdateIntroducers (true); ScheduleIntroducersUpdateTimer (); } else { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + if (i2p::context.GetTestingV6 ()) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimerV6 (); @@ -1020,12 +1511,292 @@ namespace transport // we are firewalled auto addr = i2p::context.GetRouterInfo ().GetSSU2V6Address (); if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachableSSU2 (false, true); // v6 - + i2p::context.SetUnreachable (false, true); // v6 + UpdateIntroducers (false); ScheduleIntroducersUpdateTimerV6 (); - } + } } + } + + bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + m_ChaCha20 (msg, msgLen, key, nonce, out); } + + void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, + const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) + { + if (!m_ProxyRelayEndpoint) return; + size_t requestHeaderSize = 0; + memset (m_UDPRequestHeader, 0, 3); + if (to.address ().is_v6 ()) + { + m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV6; + memcpy (m_UDPRequestHeader + 4, to.address ().to_v6().to_bytes().data(), 16); + requestHeaderSize = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; + } + else + { + m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4; + memcpy (m_UDPRequestHeader + 4, to.address ().to_v4().to_bytes().data(), 4); + requestHeaderSize = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; + } + htobe16buf (m_UDPRequestHeader + requestHeaderSize - 2, to.port ()); + + std::vector bufs; + bufs.push_back (boost::asio::buffer (m_UDPRequestHeader, requestHeaderSize)); + bufs.push_back (boost::asio::buffer (header, headerLen)); + if (headerX) bufs.push_back (boost::asio::buffer (headerX, headerXLen)); + bufs.push_back (boost::asio::buffer (payload, payloadLen)); + + boost::system::error_code ec; + m_SocketV4.send_to (bufs, *m_ProxyRelayEndpoint, 0, ec); // TODO: implement ipv6 proxy + if (!ec) + i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen); + else + LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); + } + + void SSU2Server::ProcessNextPacketFromProxy (uint8_t * buf, size_t len) + { + if (buf[2]) // FRAG + { + LogPrint (eLogWarning, "SSU2: Proxy packet fragmentation is not supported"); + return; + } + size_t offset = 0; + boost::asio::ip::udp::endpoint ep; + switch (buf[3]) // ATYP + { + case SOCKS5_ATYP_IPV4: + { + offset = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; + if (offset > len) return; + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), buf + 4, 4); + uint16_t port = bufbe16toh (buf + 8); + ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port); + break; + } + case SOCKS5_ATYP_IPV6: + { + offset = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; + if (offset > len) return; + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), buf + 4, 16); + uint16_t port = bufbe16toh (buf + 20); + ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6 (bytes), port); + break; + } + default: + { + LogPrint (eLogWarning, "SSU2: Unknown ATYP ", (int)buf[3], " from proxy relay"); + return; + } + } + ProcessNextPacket (buf + offset, len - offset, ep); + } + + void SSU2Server::ConnectToProxy () + { + if (!m_ProxyEndpoint) return; + m_UDPAssociateSocket.reset (new boost::asio::ip::tcp::socket (m_ReceiveService.GetService ())); + 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 ()); + m_UDPAssociateSocket.reset (nullptr); + ReconnectToProxy (); + } + else + HandshakeWithProxy (); + }); + } + + void SSU2Server::HandshakeWithProxy () + { + if (!m_UDPAssociateSocket) return; + 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(), + [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message()); + m_UDPAssociateSocket.reset (nullptr); + ReconnectToProxy (); + } + else + ReadHandshakeWithProxyReply (); + }); + } + + void SSU2Server::ReadHandshakeWithProxyReply () + { + if (!m_UDPAssociateSocket) return; + boost::asio::async_read (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, 2), boost::asio::transfer_all(), + [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message()); + m_UDPAssociateSocket.reset (nullptr); + ReconnectToProxy (); + } + else + { + if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1]) + SendUDPAssociateRequest (); + else + { + LogPrint(eLogError, "SSU2: Invalid proxy reply"); + m_UDPAssociateSocket.reset (nullptr); + } + } + }); + } + + void SSU2Server::SendUDPAssociateRequest () + { + if (!m_UDPAssociateSocket) return; + 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 + boost::asio::async_write (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), boost::asio::transfer_all(), + [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message()); + m_UDPAssociateSocket.reset (nullptr); + ReconnectToProxy (); + } + else + ReadUDPAssociateReply (); + }); + } + + void SSU2Server::ReadUDPAssociateReply () + { + if (!m_UDPAssociateSocket) return; + boost::asio::async_read (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), boost::asio::transfer_all(), + [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message()); + m_UDPAssociateSocket.reset (nullptr); + ReconnectToProxy (); + } + else + { + if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1]) + { + if (m_UDPRequestHeader[3] == SOCKS5_ATYP_IPV4) + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), m_UDPRequestHeader + 4, 4); + uint16_t port = bufbe16toh (m_UDPRequestHeader + 8); + m_ProxyRelayEndpoint.reset (new boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port)); + m_SocketV4.open (boost::asio::ip::udp::v4 ()); + Receive (m_SocketV4); + ReadUDPAssociateSocket (); + } + else + { + LogPrint(eLogError, "SSU2: Proxy UDP associate unsupported ATYP ", (int)m_UDPRequestHeader[3]); + m_UDPAssociateSocket.reset (nullptr); + } + } + else + { + LogPrint(eLogError, "SSU2: Proxy UDP associate error ", (int)m_UDPRequestHeader[1]); + m_UDPAssociateSocket.reset (nullptr); + } + } + }); + } + + void SSU2Server::ReadUDPAssociateSocket () + { + if (!m_UDPAssociateSocket) return; + m_UDPAssociateSocket->async_read_some (boost::asio::buffer (m_UDPRequestHeader, 1), + [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint(eLogWarning, "SSU2: Proxy UDP Associate socket error ", ecode.message()); + m_UDPAssociateSocket.reset (nullptr); + m_ProxyRelayEndpoint.reset (nullptr); + m_SocketV4.close (); + ConnectToProxy (); // try to reconnect immediately + } + else + ReadUDPAssociateSocket (); + }); + } + + void SSU2Server::ReconnectToProxy () + { + LogPrint(eLogInfo, "SSU2: Reconnect to proxy after ", SSU2_PROXY_CONNECT_RETRY_TIMEOUT, " seconds"); + if (m_ProxyConnectRetryTimer) + m_ProxyConnectRetryTimer->cancel (); + else + m_ProxyConnectRetryTimer.reset (new boost::asio::deadline_timer (m_ReceiveService.GetService ())); + m_ProxyConnectRetryTimer->expires_from_now (boost::posix_time::seconds (SSU2_PROXY_CONNECT_RETRY_TIMEOUT)); + m_ProxyConnectRetryTimer->async_wait ( + [this](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + m_UDPAssociateSocket.reset (nullptr); + m_ProxyRelayEndpoint.reset (nullptr); + LogPrint(eLogInfo, "SSU2: Reconnecting to proxy"); + ConnectToProxy (); + } + }); + } + + bool SSU2Server::SetProxy (const std::string& address, uint16_t port) + { + boost::system::error_code ecode; + auto addr = boost::asio::ip::make_address (address, ecode); + if (!ecode && !addr.is_unspecified () && port) + { + m_IsThroughProxy = true; + m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint (addr, port)); + } + else + { + if (ecode) + LogPrint (eLogError, "SSU2: Invalid proxy address ", address, " ", ecode.message()); + return false; + } + return true; + } } } diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 8ad64692..a8598ce3 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,22 +10,43 @@ #define SSU2_H__ #include +#include +#include +#include +#include +#include +#include #include "util.h" #include "SSU2Session.h" +#include "SSU2OutOfSession.h" +#include "Socks5.h" namespace i2p { namespace transport { - const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // in seconds - const int SSU2_RESEND_CHECK_TIMEOUT = 500; // in milliseconds - const size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K - const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K + const int SSU2_TERMINATION_CHECK_TIMEOUT = 23; // in seconds + const int SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // in seconds + const int SSU2_CLEANUP_INTERVAL = 72; // in seconds + const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds + const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds + const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 4; // in milliseconds + const int SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE = 9; // in milliseconds + const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time + const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024; + const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024; const size_t SSU2_MAX_NUM_INTRODUCERS = 3; + const size_t SSU2_MIN_RECEIVED_PACKET_SIZE = 40; // 16 byte short header + 8 byte minimum payload + 16 byte MAC + const size_t SSU2_MAX_RECEIVED_QUEUE_SIZE = 2500; // in packets const int SSU2_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes - const int SSU2_KEEP_ALIVE_INTERVAL = 30; // 30 seconds - + const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds + const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds + const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds + const int SSU2_MIN_HOLE_PUNCH_EXPIRATION = 30; // in seconds + const int SSU2_MAX_HOLE_PUNCH_EXPIRATION = 160; // in seconds + const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 64; + class SSU2Server: private i2p::util::RunnableServiceWithWork { struct Packet @@ -34,13 +55,13 @@ namespace transport size_t len; boost::asio::ip::udp::endpoint from; }; - + class ReceiveService: public i2p::util::RunnableService { public: ReceiveService (const std::string& name): RunnableService (name) {}; - boost::asio::io_service& GetService () { return GetIOService (); }; + auto& GetService () { return GetIOService (); }; void Start () { StartIOService (); }; void Stop () { StopIOService (); }; }; @@ -52,26 +73,45 @@ namespace transport void Start (); void Stop (); - boost::asio::io_service& GetService () { return GetIOService (); }; + auto& GetService () { return GetIOService (); }; void SetLocalAddress (const boost::asio::ip::address& localAddress); + bool SetProxy (const std::string& address, uint16_t port); + bool UsesProxy () const { return m_IsThroughProxy; }; bool IsSupported (const boost::asio::ip::address& addr) const; uint16_t GetPort (bool v4) const; + bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true); + void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts); + std::mt19937& GetRng () { return m_Rng; } + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; - - void AddSession (std::shared_ptr session); + void AdjustTimeOffset (int64_t offset, std::shared_ptr from); + + bool AddSession (std::shared_ptr session); void RemoveSession (uint64_t connID); + void RequestRemoveSession (uint64_t connID); void AddSessionByRouterHash (std::shared_ptr session); bool AddPendingOutgoingSession (std::shared_ptr session); void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep); - std::shared_ptr FindSession (const i2p::data::IdentHash& ident) const; + std::shared_ptr FindSession (const i2p::data::IdentHash& ident); std::shared_ptr FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const; - std::shared_ptr GetRandomSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports, - const i2p::data::IdentHash& excluded) const; - + std::shared_ptr GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports, + const i2p::data::IdentHash& excluded); + void AddRelay (uint32_t tag, std::shared_ptr relay); void RemoveRelay (uint32_t tag); std::shared_ptr FindRelaySession (uint32_t tag); + bool AddPeerTest (uint32_t nonce, std::shared_ptr aliceSession, uint64_t ts); + std::shared_ptr GetPeerTest (uint32_t nonce); + + bool AddRequestedPeerTest (uint32_t nonce, std::shared_ptr session, uint64_t ts); + std::shared_ptr GetRequestedPeerTest (uint32_t nonce); + void Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); void Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, @@ -80,60 +120,103 @@ namespace transport bool CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest = false); bool StartPeerTest (std::shared_ptr router, bool v4); - + void UpdateOutgoingToken (const boost::asio::ip::udp::endpoint& ep, uint64_t token, uint32_t exp); - uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const; + uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep); uint64_t GetIncomingToken (const boost::asio::ip::udp::endpoint& ep); std::pair NewIncomingToken (const boost::asio::ip::udp::endpoint& ep); - + void RescheduleIntroducersUpdateTimer (); void RescheduleIntroducersUpdateTimerV6 (); i2p::util::MemoryPool& GetSentPacketsPool () { return m_SentPacketsPool; }; - + i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; + i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; + private: boost::asio::ip::udp::socket& OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint); void Receive (boost::asio::ip::udp::socket& socket); void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred, Packet * packet, boost::asio::ip::udp::socket& socket); - void HandleReceivedPacket (Packet * packet); - void HandleReceivedPackets (std::vector packets); + void HandleReceivedPackets (std::list&& packets); void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - + void InsertToReceivedPacketsQueue (Packet * packet); + void InsertToReceivedPacketsQueue (std::list& packets); + void HandleReceivedPacketsQueue (); + void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleResend (); + void ScheduleCleanup (); + void HandleCleanupTimer (const boost::system::error_code& ecode); + + void ScheduleResend (bool more); void HandleResendTimer (const boost::system::error_code& ecode); + bool CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest); void ConnectThroughIntroducer (std::shared_ptr session); - std::list > FindIntroducers (int maxNumIntroducers, - bool v4, const std::set& excluded) const; + std::vector > FindIntroducers (int maxNumIntroducers, + bool v4, const std::unordered_set& excluded); void UpdateIntroducers (bool v4); void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); void ScheduleIntroducersUpdateTimerV6 (); - + + void SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, + const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); + void ProcessNextPacketFromProxy (uint8_t * buf, size_t len); + void ConnectToProxy (); + void ReconnectToProxy (); + void HandshakeWithProxy (); + void ReadHandshakeWithProxyReply (); + void SendUDPAssociateRequest (); + void ReadUDPAssociateReply (); + void ReadUDPAssociateSocket (); // handle if closed by peer + private: ReceiveService m_ReceiveService; boost::asio::ip::udp::socket m_SocketV4, m_SocketV6; boost::asio::ip::address m_AddressV4, m_AddressV6; std::unordered_map > m_Sessions; - std::unordered_map > m_SessionsByRouterHash; + std::unordered_map > m_SessionsByRouterHash; + mutable std::mutex m_SessionsByRouterHashMutex; std::map > m_PendingOutgoingSessions; + mutable std::mutex m_PendingOutgoingSessionsMutex; std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) - std::map > m_Relays; // we are introducer, relay tag -> session - std::list m_Introducers, m_IntroducersV6; // introducers we are connected to + std::unordered_map > m_Relays; // we are introducer, relay tag -> session + std::unordered_map, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob + std::list > m_Introducers, m_IntroducersV6; // introducers we are connected to i2p::util::MemoryPoolMt m_PacketsPool; i2p::util::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 bool m_IsSyncClockFromPeers; - + int64_t m_PendingTimeOffset; // during peer test + std::shared_ptr m_PendingTimeOffsetFrom; + std::mt19937 m_Rng; + std::map m_ConnectedRecently; // endpoint -> last activity time in seconds + mutable std::mutex m_ConnectedRecentlyMutex; + std::unordered_map, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) + std::list m_ReceivedPacketsQueue; + mutable std::mutex m_ReceivedPacketsQueueMutex; + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + i2p::crypto::ChaCha20Context m_ChaCha20; + + // proxy + bool m_IsThroughProxy; + uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE]; + std::unique_ptr m_ProxyEndpoint; + std::unique_ptr m_UDPAssociateSocket; + std::unique_ptr m_ProxyRelayEndpoint; + std::unique_ptr m_ProxyConnectRetryTimer; + public: // for HTTP/I2PControl diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp new file mode 100644 index 00000000..3760e329 --- /dev/null +++ b/libi2pd/SSU2OutOfSession.cpp @@ -0,0 +1,345 @@ +/* +* Copyright (c) 2024-2025, 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 "Log.h" +#include "SSU2.h" +#include "SSU2OutOfSession.h" + +namespace i2p +{ +namespace transport +{ + SSU2PeerTestSession::SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID): + SSU2Session (server, nullptr, nullptr, false), + m_MsgNumReceived (0), m_NumResends (0),m_IsConnectedRecently (false), m_IsStatusChanged (false), + m_PeerTestResendTimer (server.GetService ()) + { + if (!sourceConnID) sourceConnID = ~destConnID; + if (!destConnID) destConnID = ~sourceConnID; + SetSourceConnID (sourceConnID); + SetDestConnID (destConnID); + SetState (eSSU2SessionStatePeerTest); + SetTerminationTimeout (SSU2_PEER_TEST_EXPIRATION_TIMEOUT); + } + + bool SSU2PeerTestSession::ProcessPeerTest (uint8_t * buf, size_t len) + { + // we are Alice or Charlie, msgs 5,6,7 + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); + header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); + if (header.h.type != eSSU2PeerTest) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); + return false; + } + if (len < 48) + { + LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); + return false; + } + uint8_t nonce[12] = {0}; + uint64_t headerX[2]; // sourceConnID, token + GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + SetDestConnID (headerX[0]); + // decrypt and handle payload + uint8_t * payload = buf + 32; + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t h[32]; + memcpy (h, header.buf, 16); + memcpy (h + 16, &headerX, 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); + return false; + } + HandlePayload (payload, len - 48); + SetIsDataReceived (false); + return true; + } + + void SSU2PeerTestSession::HandleAddress (const uint8_t * buf, size_t len) + { + if (!ExtractEndpoint (buf, len, m_OurEndpoint)) + LogPrint (eLogWarning, "SSU2: Can't handle address block from peer test message"); + } + + void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len) + { + // msgs 5-7 + if (len < 8) return; + uint8_t msg = buf[0]; + if (msg <= m_MsgNumReceived) + { + LogPrint (eLogDebug, "SSU2: PeerTest msg num ", msg, " received after ", m_MsgNumReceived, ". Ignored"); + return; + } + size_t offset = 3; // points to signed data after msg + code + flag + uint32_t nonce = bufbe32toh (buf + offset + 1); // 1 - ver + switch (msg) // msg + { + case 5: // Alice from Charlie 1 + { + if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ()) + { + m_PeerTestResendTimer.cancel (); // cancel delayed msg 6 if any + m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ()); + if (GetAddress ()) + { + if (!m_IsConnectedRecently) + SetRouterStatus (eRouterStatusOK); + else if (m_IsStatusChanged && GetRouterStatus () == eRouterStatusFirewalled) + SetRouterStatus (eRouterStatusUnknown); + SendPeerTest (6, buf + offset, len - offset); + } + } + else + LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", GetSourceConnID ()); + break; + } + case 6: // Charlie from Alice + { + m_PeerTestResendTimer.cancel (); // no more msg 5 resends + if (GetAddress ()) + SendPeerTest (7, buf + offset, len - offset); + else + LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); + GetServer ().RequestRemoveSession (GetConnID ()); + break; + } + case 7: // Alice from Charlie 2 + { + m_PeerTestResendTimer.cancel (); // no more msg 6 resends + if (m_MsgNumReceived < 5 && m_OurEndpoint.port ()) // msg 5 was not received + { + if (m_OurEndpoint.address ().is_v4 ()) // ipv4 + { + if (i2p::context.GetStatus () == eRouterStatusFirewalled) + { + if (m_OurEndpoint.port () != GetServer ().GetPort (true)) + i2p::context.SetError (eRouterErrorSymmetricNAT); + else if (i2p::context.GetError () == eRouterErrorSymmetricNAT) + i2p::context.SetError (eRouterErrorNone); + } + } + else + { + if (i2p::context.GetStatusV6 () == eRouterStatusFirewalled) + { + if (m_OurEndpoint.port () != GetServer ().GetPort (false)) + i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT); + else if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT) + i2p::context.SetErrorV6 (eRouterErrorNone); + } + } + } + GetServer ().RequestRemoveSession (GetConnID ()); + break; + } + default: + LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", msg); + return; + } + m_MsgNumReceived = msg; + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg) + { + auto addr = GetAddress (); + if (!addr) return; + Header header; + uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; + // fill packet + header.h.connID = GetDestConnID (); // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2PeerTest; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (h, header.buf, 16); + htobuf64 (h + 16, GetSourceConnID ()); // source id + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + size_t payloadSize = 7; + if (msg == 6 || msg == 7) + payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, GetRemoteEndpoint ()); + payloadSize += CreatePeerTestBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, + msg, eSSU2PeerTestCodeAccept, nullptr, m_SignedData.data (), m_SignedData.size ()); + payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize); + // encrypt + uint8_t n[12]; + CreateNonce (be32toh (header.h.packetNum), n); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true); + payloadSize += 16; + header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); + header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); + memset (n, 0, 12); + GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); + // send + GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); + UpdateNumSentBytes (payloadSize + 32); + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed) + { + m_SignedData.assign (signedData, signedData + signedDataLen); + if (!delayed) + SendPeerTest (msg); + // schedule resend for msgs 5 or 6 + if (msg == 5 || msg == 6) + ScheduleResend (msg); + } + + void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, + std::shared_ptr addr, bool delayed) + { + if (!addr) return; + SetAddress (addr); + SendPeerTest (msg, signedData, signedDataLen, delayed); + } + + void SSU2PeerTestSession::Connect () + { + LogPrint (eLogError, "SSU2: Can't connect peer test session"); + } + + bool SSU2PeerTestSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) + { + LogPrint (eLogError, "SSU2: Can't handle incoming message in peer test session"); + return false; + } + + void SSU2PeerTestSession::ScheduleResend (uint8_t msg) + { + if (m_NumResends < SSU2_PEER_TEST_MAX_NUM_RESENDS) + { + m_PeerTestResendTimer.expires_from_now (boost::posix_time::milliseconds( + SSU2_PEER_TEST_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE)); + std::weak_ptr s(std::static_pointer_cast(shared_from_this ())); + m_PeerTestResendTimer.async_wait ([s, msg](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto s1 = s.lock (); + if (s1) + { + if (msg > s1->m_MsgNumReceived) + { + s1->SendPeerTest (msg); + s1->m_NumResends++; + s1->ScheduleResend (msg); + } + } + } + }); + } + } + + SSU2HolePunchSession::SSU2HolePunchSession (SSU2Server& server, uint32_t nonce, + const boost::asio::ip::udp::endpoint& remoteEndpoint, + std::shared_ptr addr): + SSU2Session (server), // we create full incoming session + m_NumResends (0), m_HolePunchResendTimer (server.GetService ()) + { + // we are Charlie + uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id + uint64_t sourceConnID = ~destConnID; + SetSourceConnID (sourceConnID); + SetDestConnID (destConnID); + SetState (eSSU2SessionStateHolePunch); + SetRemoteEndpoint (remoteEndpoint); + SetAddress (addr); + SetTerminationTimeout (SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT); + } + + void SSU2HolePunchSession::SendHolePunch () + { + auto addr = GetAddress (); + if (!addr) return; + auto& ep = GetRemoteEndpoint (); + LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep); + Header header; + uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; + // fill packet + header.h.connID = GetDestConnID (); // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2HolePunch; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (h, header.buf, 16); + htobuf64 (h + 16, GetSourceConnID ()); // source id + RAND_bytes (h + 24, 8); // header token, to be ignored by Alice + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + size_t payloadSize = 7; + payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, ep); + // relay response block + if (payloadSize + m_RelayResponseBlock.size () < GetMaxPayloadSize ()) + { + memcpy (payload + payloadSize, m_RelayResponseBlock.data (), m_RelayResponseBlock.size ()); + payloadSize += m_RelayResponseBlock.size (); + } + payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize); + // encrypt + uint8_t n[12]; + CreateNonce (be32toh (header.h.packetNum), n); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true); + payloadSize += 16; + header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); + header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); + memset (n, 0, 12); + GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); + // send + GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); + UpdateNumSentBytes (payloadSize + 32); + } + + void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen) + { + m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen); + SendHolePunch (); + ScheduleResend (); + } + + void SSU2HolePunchSession::ScheduleResend () + { + if (m_NumResends < SSU2_HOLE_PUNCH_MAX_NUM_RESENDS) + { + m_HolePunchResendTimer.expires_from_now (boost::posix_time::milliseconds( + SSU2_HOLE_PUNCH_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE)); + std::weak_ptr s(std::static_pointer_cast(shared_from_this ())); + m_HolePunchResendTimer.async_wait ([s](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto s1 = s.lock (); + if (s1 && s1->GetState () == eSSU2SessionStateHolePunch) + { + s1->SendHolePunch (); + s1->m_NumResends++; + s1->ScheduleResend (); + } + } + }); + } + } + + bool SSU2HolePunchSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) + { + m_HolePunchResendTimer.cancel (); + return SSU2Session::ProcessFirstIncomingMessage (connID, buf, len); + } +} +} diff --git a/libi2pd/SSU2OutOfSession.h b/libi2pd/SSU2OutOfSession.h new file mode 100644 index 00000000..e8c55c3c --- /dev/null +++ b/libi2pd/SSU2OutOfSession.h @@ -0,0 +1,86 @@ +/* +* Copyright (c) 2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef SSU2_OUT_OF_SESSION_H__ +#define SSU2_OUT_OF_SESSION_H__ + +#include +#include "SSU2Session.h" + +namespace i2p +{ +namespace transport +{ + const int SSU2_PEER_TEST_RESEND_INTERVAL = 3000; // in milliseconds + const int SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE = 2000; // in milliseconds + const int SSU2_PEER_TEST_MAX_NUM_RESENDS = 3; + + class SSU2PeerTestSession: public SSU2Session // for PeerTest msgs 5,6,7 + { + public: + + SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID); + + uint8_t GetMsgNumReceived () const { return m_MsgNumReceived; } + bool IsConnectedRecently () const { return m_IsConnectedRecently; } + void SetStatusChanged () { m_IsStatusChanged = true; } + + void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, + std::shared_ptr addr, bool delayed = false); + bool ProcessPeerTest (uint8_t * buf, size_t len) override; + void Connect () override; // outgoing + bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // incoming + + private: + + void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed = false); // PeerTest message + void SendPeerTest (uint8_t msg); // send or resend m_SignedData + void HandlePeerTest (const uint8_t * buf, size_t len) override; + void HandleAddress (const uint8_t * buf, size_t len) override; + + void ScheduleResend (uint8_t msg); + + private: + + uint8_t m_MsgNumReceived, m_NumResends; + bool m_IsConnectedRecently, m_IsStatusChanged; + std::vector m_SignedData; // for resends + boost::asio::deadline_timer m_PeerTestResendTimer; + boost::asio::ip::udp::endpoint m_OurEndpoint; // as seen by peer + }; + + const int SSU2_HOLE_PUNCH_RESEND_INTERVAL = 1000; // in milliseconds + const int SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE = 500; // in milliseconds + const int SSU2_HOLE_PUNCH_MAX_NUM_RESENDS = 3; + + class SSU2HolePunchSession: public SSU2Session // Charlie + { + public: + + SSU2HolePunchSession (SSU2Server& server, uint32_t nonce, const boost::asio::ip::udp::endpoint& remoteEndpoint, + std::shared_ptr addr); + + void SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen); + + bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // SessionRequest + + private: + + void SendHolePunch (); + void ScheduleResend (); + + private: + + int m_NumResends; + std::vector m_RelayResponseBlock; + boost::asio::deadline_timer m_HolePunchResendTimer; + }; +} +} + +#endif diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index da7a9434..cb10f848 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,6 +13,7 @@ #include "Gzip.h" #include "NetDb.hpp" #include "SSU2.h" +#include "SSU2Session.h" namespace i2p { @@ -23,7 +24,7 @@ namespace transport if (msg->len + fragmentSize > msg->maxLen) { LogPrint (eLogInfo, "SSU2: I2NP message size ", msg->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); + auto newMsg = NewI2NPMessage (msg->len + fragmentSize); *newMsg = *msg; msg = newMsg; } @@ -31,33 +32,88 @@ namespace transport LogPrint (eLogError, "SSU2: I2NP buffer overflow ", msg->maxLen); nextFragmentNum++; } - - - 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_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose), - m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32) // min size + + bool SSU2IncompleteMessage::ConcatOutOfSequenceFragments () { - m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); + 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, bool noise): + TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), + m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0), + m_RemoteVersion (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), + m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0), + m_IsDataReceived (false), m_RTT (SSU2_UNKNOWN_RTT), + m_MsgLocalExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX), + m_MsgLocalSemiExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX / 2), + m_WindowSize (SSU2_MIN_WINDOW_SIZE), + m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()), + m_TerminationReason (eSSU2TerminationReasonNormalClose), + m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size + m_LastResendTime (0), m_LastResendAttemptTime (0), m_NumRanges (0) + { + if (noise) + m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); if (in_RemoteRouter && m_Address) { // outgoing - InitNoiseXKState1 (*m_NoiseState, m_Address->s); + if (noise) + InitNoiseXKState1 (*m_NoiseState, m_Address->s); m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port); m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false); + m_RemoteVersion = in_RemoteRouter->GetVersion (); + if (in_RemoteRouter->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4; + if (in_RemoteRouter->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6; RAND_bytes ((uint8_t *)&m_DestConnID, 8); RAND_bytes ((uint8_t *)&m_SourceConnID, 8); } else { // incoming - InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); + if (noise) + InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); } } @@ -68,17 +124,19 @@ namespace transport void SSU2Session::Connect () { if (m_State == eSSU2SessionStateUnknown || m_State == eSSU2SessionStateTokenReceived) - { + { + LogPrint(eLogDebug, "SSU2: Connecting to ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ")"); ScheduleConnectTimer (); auto token = m_Server.FindOutgoingToken (m_RemoteEndpoint); if (token) SendSessionRequest (token); else - { - m_State = eSSU2SessionStateUnknown; + { + m_State = eSSU2SessionStateUnknown; SendTokenRequest (); - } - } + } + } } void SSU2Session::ScheduleConnectTimer () @@ -91,54 +149,64 @@ namespace transport void SSU2Session::HandleConnectTimer (const boost::system::error_code& ecode) { - if (!ecode) + if (!ecode && m_State != eSSU2SessionStateTerminated) { // timeout expired - LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds"); + if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer + LogPrint (eLogWarning, "SSU2: Session was not introduced after ", SSU2_CONNECT_TIMEOUT, " seconds"); + else + LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds"); Terminate (); } } - + bool SSU2Session::Introduce (std::shared_ptr session, uint32_t relayTag) { // we are Alice if (!session || !relayTag) return false; - // find local adddress to introduce + // find local address to introduce auto localAddress = session->FindLocalAddress (); - if (!localAddress) return false; + if (!localAddress || localAddress->host.is_unspecified () || !localAddress->port) + { + // can't introduce invalid endpoint + LogPrint (eLogWarning, "SSU2: Can't find local address to introduce"); + return false; + } // create nonce uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); // payload - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 0; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayRequest; payload[3] = 0; // flag htobe32buf (payload + 4, nonce); htobe32buf (payload + 8, relayTag); - htobe32buf (payload + 12, ts); + htobe32buf (payload + 12, ts/1000); payload[16] = 2; // ver size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); if (!asz) return false; payload[17] = asz; - payloadSize += asz + 18; - SignedData s; + packet->payloadSize = asz + 18; + SignedData<128> s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash s.Insert (payload + 4, 14 + asz); // nonce, relay tag, timestamp, ver, asz and Alice's endpoint - s.Sign (i2p::context.GetPrivateKeys (), payload + payloadSize); - payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); - htobe16buf (payload + 1, payloadSize - 3); // size - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + s.Sign (i2p::context.GetPrivateKeys (), payload + packet->payloadSize); + packet->payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); + htobe16buf (payload + 1, packet->payloadSize - 3); // size + packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); // send - m_RelaySessions.emplace (nonce, std::make_pair (session, ts)); + m_RelaySessions.emplace (nonce, std::make_pair (session, ts/1000)); session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); session->m_DestConnID = ~session->m_SourceConnID; m_Server.AddSession (session); - SendData (payload, payloadSize); - + int32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); + return true; } @@ -152,51 +220,66 @@ namespace transport { if (m_State == eSSU2SessionStateIntroduced) { - // create new connID - uint64_t oldConnID = GetConnID (); - RAND_bytes ((uint8_t *)&m_DestConnID, 8); - RAND_bytes ((uint8_t *)&m_SourceConnID, 8); - // connect + // we are Alice + // keep ConnIDs used for introduction, because Charlie waits for SessionRequest from us m_State = eSSU2SessionStateTokenReceived; - m_Server.AddPendingOutgoingSession (shared_from_this ()); - m_Server.RemoveSession (oldConnID); - Connect (); + // move session to pending outgoing + if (m_Server.AddPendingOutgoingSession (shared_from_this ())) + { + m_Server.RemoveSession (GetConnID ()); + // update endpoint in profile because we know it now + auto identity = GetRemoteIdentity (); + if (identity) + { + auto profile = i2p::data::GetRouterProfile (identity->GetIdentHash ()); + if (profile) profile->SetLastEndpoint (m_RemoteEndpoint); + } + // connect + LogPrint (eLogDebug, "SSU2: Connecting after introduction to ", GetIdentHashBase64()); + Connect (); + } + else + { + LogPrint (eLogError, "SSU2: Session ", GetConnID (), " is already pending"); + m_Server.RequestRemoveSession (GetConnID ()); + } } - } - + } + void SSU2Session::SendPeerTest () { // we are Alice uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); // session for message 5 - auto session = std::make_shared (m_Server); - session->SetState (eSSU2SessionStatePeerTest); - m_PeerTests.emplace (nonce, std::make_pair (session, ts)); - session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_DestConnID = ~session->m_SourceConnID; + auto session = std::make_shared (m_Server, + htobe64 (((uint64_t)nonce << 32) | nonce), 0); + m_Server.AddRequestedPeerTest (nonce, session, ts/1000); m_Server.AddSession (session); // peer test block - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, nonce); - if (payloadSize > 0) - { - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); - } - } + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, nonce); + if (packet->payloadSize > 0) + { + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); + LogPrint (eLogDebug, "SSU2: PeerTest msg=1 sent to ", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ())); + } + } void SSU2Session::SendKeepAlive () { if (IsEstablished ()) - { + { uint8_t payload[20]; - size_t payloadSize = CreatePaddingBlock (payload, 20, 5); - SendData (payload, payloadSize); - } - } - + size_t payloadSize = CreatePaddingBlock (payload, 20, 8); + SendData (payload, payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); + } + } + void SSU2Session::Terminate () { if (m_State != eSSU2SessionStateTerminated) @@ -206,15 +289,28 @@ namespace transport m_OnEstablished = nullptr; if (m_RelayTag) m_Server.RemoveRelay (m_RelayTag); + m_Server.AddConnectedRecently (m_RemoteEndpoint, GetLastActivityTimestamp ()); m_SentHandshakePacket.reset (nullptr); + m_SessionConfirmedFragment.reset (nullptr); + m_PathChallenge.reset (nullptr); + if (!m_IntermediateQueue.empty ()) + m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue); + for (auto& it: m_SendQueue) + it->Drop (); m_SendQueue.clear (); + SetSendQueueSize (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"); + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity) + LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated"); + else + LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), " terminated"); } } @@ -224,10 +320,12 @@ namespace transport { m_TerminationReason = reason; SendTermination (); + m_State = eSSU2SessionStateClosing; } - m_State = eSSU2SessionStateClosing; - } - + else + Done (); + } + void SSU2Session::Established () { m_State = eSSU2SessionStateEstablished; @@ -237,72 +335,132 @@ namespace transport m_SentHandshakePacket.reset (nullptr); m_ConnectTimer.cancel (); SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT); + SendQueue (); transports.PeerConnected (shared_from_this ()); - if (m_OnEstablished) - { + + LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established"); + if (m_OnEstablished) + { m_OnEstablished (); m_OnEstablished = nullptr; - } + } } void SSU2Session::Done () { - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::Terminate, shared_from_this ())); } void SSU2Session::SendLocalRouterInfo (bool update) { if (update || !IsOutgoing ()) - { + { auto s = shared_from_this (); - m_Server.GetService ().post ([s]() + boost::asio::post (m_Server.GetService (), [s]() { if (!s->IsEstablished ()) return; uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.GetSharedRouterInfo ()); + size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.CopyRouterInfoBuffer ()); if (payloadSize) { if (payloadSize < s->m_MaxPayloadSize) payloadSize += s->CreatePaddingBlock (payload + payloadSize, s->m_MaxPayloadSize - payloadSize); s->SendData (payload, payloadSize); - } + } else s->SendFragmentedMessage (CreateDatabaseStoreMsg ()); }); - } + } - } - - void SSU2Session::SendI2NPMessages (const std::vector >& msgs) - { - m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), msgs)); } - void SSU2Session::PostI2NPMessages (std::vector > msgs) + void SSU2Session::SendI2NPMessages (std::list >& msgs) + { + if (m_State == eSSU2SessionStateTerminated || msgs.empty ()) + { + msgs.clear (); + return; + } + bool empty = false; + { + std::lock_guard l(m_IntermediateQueueMutex); + empty = m_IntermediateQueue.empty (); + m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); + } + if (empty) + boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ())); + } + + void SSU2Session::PostI2NPMessages () { if (m_State == eSSU2SessionStateTerminated) return; - for (auto it: msgs) - m_SendQueue.push_back (it); - SendQueue (); - - if (m_SendQueue.size () > 0) // windows is full - { - if (m_SendQueue.size () <= SSU2_MAX_OUTGOING_QUEUE_SIZE) - Resend (i2p::util::GetMillisecondsSinceEpoch ()); - else + std::list > msgs; + { + std::lock_guard l(m_IntermediateQueueMutex); + m_IntermediateQueue.swap (msgs); + } + uint64_t mts = i2p::util::GetMonotonicMicroseconds (); + bool isSemiFull = false; + if (m_SendQueue.size ()) + { + int64_t queueLag = (int64_t)mts - (int64_t)m_SendQueue.front ()->GetEnqueueTime (); + isSemiFull = queueLag > m_MsgLocalSemiExpirationTimeout; + if (isSemiFull) { - LogPrint (eLogWarning, "SSU2: Outgoing messages queue size to ", - GetIdentHashBase64(), " exceeds ", SSU2_MAX_OUTGOING_QUEUE_SIZE); - RequestTermination (eSSU2TerminationReasonTimeout); + LogPrint (eLogWarning, "SSU2: Outgoing messages queue to ", + i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), + " is semi-full (size = ", m_SendQueue.size (), ", lag = ", queueLag / 1000, ", rtt = ", (int)m_RTT, ")"); + } + } + if (isSemiFull) + { + for (auto it: msgs) + { + if (it->onDrop) + it->Drop (); // drop earlier because we can handle it + else + { + it->SetEnqueueTime (mts); + m_SendQueue.push_back (std::move (it)); + } } } + else + { + for (auto& it: msgs) it->SetEnqueueTime (mts); + m_SendQueue.splice (m_SendQueue.end (), msgs); + } + if (IsEstablished ()) + { + SendQueue (); + if (m_SendQueue.size () > 0) // windows is full + Resend (i2p::util::GetMillisecondsSinceEpoch ()); + } + SetSendQueueSize (m_SendQueue.size ()); } + void SSU2Session::MoveSendQueue (std::shared_ptr other) + { + if (!other || m_SendQueue.empty ()) return; + std::list > msgs; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + for (auto it: m_SendQueue) + if (!it->IsExpired (ts)) + msgs.push_back (it); + else + it->Drop (); + m_SendQueue.clear (); + if (!msgs.empty ()) + other->SendI2NPMessages (msgs); + } + bool SSU2Session::SendQueue () { - if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) + if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize && IsEstablished ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); + uint64_t mts = i2p::util::GetMonotonicMicroseconds (); auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); size_t ackBlockSize = CreateAckBlock (packet->payload, m_MaxPayloadSize); bool ackBlockSent = false; @@ -310,6 +468,13 @@ namespace transport while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) { auto msg = m_SendQueue.front (); + if (!msg || msg->IsExpired (ts) || msg->GetEnqueueTime() + m_MsgLocalExpirationTimeout < mts) + { + // drop null or expired message + if (msg) msg->Drop (); + m_SendQueue.pop_front (); + continue; + } size_t len = msg->GetNTCP2Length () + 3; if (len > m_MaxPayloadSize) // message too long { @@ -330,12 +495,12 @@ namespace transport newPacket->payloadSize = ackBlockSize; // complete current packet if (packet->payloadSize > ackBlockSize) // more than just ack block - { + { ackBlockSent = true; // try to add padding if (packet->payloadSize + 16 < m_MaxPayloadSize) packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - } + } else { // reduce ack block @@ -344,15 +509,15 @@ namespace transport // keep Ack block and drop some ranges ackBlockSent = true; packet->payloadSize = m_MaxPayloadSize - len; - if (packet->payloadSize & 0x01) packet->payloadSize--; // make it even + if (packet->payloadSize & 0x01) packet->payloadSize--; // make it even htobe16buf (packet->payload + 1, packet->payloadSize - 3); // new block size - } + } else // drop Ack block completely - packet->payloadSize = 0; + packet->payloadSize = 0; // msg fits single packet m_SendQueue.pop_front (); packet->payloadSize += CreateI2NPBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, std::move (msg)); - } + } // send right a way uint32_t packetNum = SendData (packet->payload, packet->payloadSize); packet->sendTime = ts; @@ -362,10 +527,11 @@ namespace transport }; if (packet->payloadSize > ackBlockSize) { + // last ackBlockSent = true; if (packet->payloadSize + 16 < m_MaxPayloadSize) packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); packet->sendTime = ts; m_SentPackets.emplace (packetNum, packet); } @@ -376,6 +542,7 @@ namespace transport bool SSU2Session::SendFragmentedMessage (std::shared_ptr msg) { + if (!msg) return false; size_t lastFragmentSize = (msg->GetNTCP2Length () + 3 - m_MaxPayloadSize) % (m_MaxPayloadSize - 8); size_t extraSize = m_MaxPayloadSize - lastFragmentSize; bool ackBlockSent = false; @@ -384,7 +551,7 @@ namespace transport auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); if (extraSize >= 8) - { + { packet->payloadSize = CreateAckBlock (packet->payload, extraSize); ackBlockSent = true; if (packet->payloadSize + 12 < m_MaxPayloadSize) @@ -393,11 +560,11 @@ namespace transport packet->sendTime = ts; m_SentPackets.emplace (packetNum, packet); packet = m_Server.GetSentPacketsPool ().AcquireShared (); - } - else + } + else extraSize -= packet->payloadSize; - } - size_t offset = extraSize > 0 ? (rand () % extraSize) : 0; + } + size_t offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0; if (offset + packet->payloadSize >= m_MaxPayloadSize) offset = 0; auto size = CreateFirstFragmentBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - offset - packet->payloadSize, msg); if (!size) return false; @@ -409,46 +576,55 @@ namespace transport uint8_t fragmentNum = 0; while (msg->offset < msg->len) { - offset = extraSize > 0 ? (rand () % extraSize) : 0; + offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0; packet = m_Server.GetSentPacketsPool ().AcquireShared (); packet->payloadSize = CreateFollowOnFragmentBlock (packet->payload, m_MaxPayloadSize - offset, msg, fragmentNum, msgID); extraSize -= offset; + uint8_t flags = 0; if (msg->offset >= msg->len && packet->payloadSize + 16 < m_MaxPayloadSize) // last fragment + { packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t followonPacketNum = SendData (packet->payload, packet->payloadSize); + if (fragmentNum > 2) // 3 or more fragments + flags |= SSU2_FLAG_IMMEDIATE_ACK_REQUESTED; + } + uint32_t followonPacketNum = SendData (packet->payload, packet->payloadSize, flags); packet->sendTime = ts; m_SentPackets.emplace (followonPacketNum, packet); } return ackBlockSent; } - void SSU2Session::Resend (uint64_t ts) + size_t SSU2Session::Resend (uint64_t ts) { + if (ts + SSU2_RESEND_ATTEMPT_MIN_INTERVAL < m_LastResendAttemptTime) return 0; + m_LastResendAttemptTime = ts; // resend handshake packet if (m_SentHandshakePacket && ts >= m_SentHandshakePacket->sendTime + SSU2_HANDSHAKE_RESEND_INTERVAL) { LogPrint (eLogDebug, "SSU2: Resending ", (int)m_State); ResendHandshakePacket (); m_SentHandshakePacket->sendTime = ts; - return; - } + return 0; + } // resend data packets - if (m_SentPackets.empty ()) return; + if (m_SentPackets.empty ()) return 0; std::map > resentPackets; for (auto it = m_SentPackets.begin (); it != m_SentPackets.end (); ) - if (ts >= it->second->sendTime + it->second->numResends*m_RTO) + if (ts >= it->second->sendTime + (it->second->numResends + 1) * m_RTO) { if (it->second->numResends > SSU2_MAX_NUM_RESENDS) { LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session"); m_SentPackets.clear (); m_SendQueue.clear (); + SetSendQueueSize (0); RequestTermination (eSSU2TerminationReasonTimeout); - return; - } + return resentPackets.size (); + } else { - uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize); + uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize, + it->second->numResends > 1 ? SSU2_FLAG_IMMEDIATE_ACK_REQUESTED : 0); it->second->numResends++; it->second->sendTime = ts; resentPackets.emplace (packetNum, it->second); @@ -459,29 +635,28 @@ namespace transport it++; if (!resentPackets.empty ()) { -#if (__cplusplus >= 201703L) // C++ 17 or higher + m_LastResendTime = ts; m_SentPackets.merge (resentPackets); -#else - m_SentPackets.insert (resentPackets.begin (), resentPackets.end ()); -#endif m_WindowSize >>= 1; // /2 if (m_WindowSize < SSU2_MIN_WINDOW_SIZE) m_WindowSize = SSU2_MIN_WINDOW_SIZE; + return resentPackets.size (); } + return 0; } void SSU2Session::ResendHandshakePacket () { if (m_SentHandshakePacket) - { - m_Server.Send (m_SentHandshakePacket->header.buf, 16, m_SentHandshakePacket->headerX, 48, + { + m_Server.Send (m_SentHandshakePacket->header.buf, 16, m_SentHandshakePacket->headerX, 48, m_SentHandshakePacket->payload, m_SentHandshakePacket->payloadSize, m_RemoteEndpoint); if (m_SessionConfirmedFragment && m_State == eSSU2SessionStateSessionConfirmedSent) // resend second fragment of SessionConfirmed - m_Server.Send (m_SessionConfirmedFragment->header.buf, 16, + m_Server.Send (m_SessionConfirmedFragment->header.buf, 16, m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize, m_RemoteEndpoint); - } - } - + } + } + bool SSU2Session::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) { // we are Bob @@ -500,16 +675,21 @@ namespace transport break; case eSSU2PeerTest: { - // TODO: remove later - const uint8_t nonce[12] = {0}; - uint64_t headerX[2]; - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + // TODO: remove later + if (len < 32) + { + LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); + break; + } + const uint8_t nonce[12] = {0}; + uint64_t headerX[2]; + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); break; - } + } case eSSU2HolePunch: LogPrint (eLogDebug, "SSU2: Late HolePunch for ", connID); - break; + break; default: { LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " from ", m_RemoteEndpoint, " of ", len, " bytes"); @@ -526,13 +706,13 @@ namespace transport m_SentHandshakePacket.reset (new HandshakePacket); auto ts = i2p::util::GetMillisecondsSinceEpoch (); m_SentHandshakePacket->sendTime = ts; - + Header& header = m_SentHandshakePacket->header; - uint8_t * headerX = m_SentHandshakePacket->headerX, + uint8_t * headerX = m_SentHandshakePacket->headerX, * payload = m_SentHandshakePacket->payload; // fill packet header.h.connID = m_DestConnID; // dest id - header.h.packetNum = 0; + RAND_bytes (header.buf + 8, 4); // random packet num header.h.type = eSSU2SessionRequest; header.h.flags[0] = 2; // ver header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID @@ -543,15 +723,19 @@ 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 ()) { - // relay tag request - payload[payloadSize] = eSSU2BlkRelayTagRequest; - memset (payload + payloadSize + 1, 0, 2); // size = 0 - payloadSize += 3; - } + if (!m_Server.IsMaxNumIntroducers (m_RemoteEndpoint.address ().is_v4 ()) || + m_Server.GetRng ()() & 0x01) // request tag with probability 1/2 if we have enough introducers + { + // relay tag request + payload[payloadSize] = eSSU2BlkRelayTagRequest; + memset (payload + payloadSize + 1, 0, 2); // size = 0 + payloadSize += 3; + } + } payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1); // KDF for session request m_NoiseState->MixHash ({ {header.buf, 16}, {headerX, 16} }); // h = SHA256(h || header) @@ -560,33 +744,39 @@ namespace transport m_EphemeralKeys->Agree (m_Address->s, sharedSecret); m_NoiseState->MixKey (sharedSecret); // encrypt - const uint8_t nonce[12] = {0}; + const uint8_t nonce[12] = {0}; // always 0 i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, m_NoiseState->m_H, 32, m_NoiseState->m_CK + 32, nonce, payload, payloadSize + 16, true); payloadSize += 16; header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); - i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); + m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated m_SentHandshakePacket->payloadSize = payloadSize; // send if (m_State == eSSU2SessionStateTokenReceived || m_Server.AddPendingOutgoingSession (shared_from_this ())) - { + { m_State = eSSU2SessionStateSessionRequestSent; + m_HandshakeInterval = ts; m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); - } + } else { - LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending"); + LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending"); Terminate (); - } + } } void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len) { // we are Bob + if (len < 88) + { + 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); + m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); memcpy (&m_DestConnID, headerX, 8); uint64_t token; memcpy (&token, headerX + 8, 8); @@ -616,8 +806,13 @@ namespace transport m_State = eSSU2SessionStateSessionRequestReceived; HandlePayload (decryptedPayload.data (), decryptedPayload.size ()); - m_Server.AddSession (shared_from_this ()); - SendSessionCreated (headerX + 16); + if (m_TerminationReason == eSSU2TerminationReasonNormalClose) + { + m_Server.AddSession (shared_from_this ()); + SendSessionCreated (headerX + 16); + } + else + SendRetry (); } void SSU2Session::SendSessionCreated (const uint8_t * X) @@ -627,15 +822,15 @@ namespace transport m_SentHandshakePacket.reset (new HandshakePacket); auto ts = i2p::util::GetMillisecondsSinceEpoch (); m_SentHandshakePacket->sendTime = ts; - + uint8_t kh2[32]; i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessCreateHeader", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32) // fill packet Header& header = m_SentHandshakePacket->header; - uint8_t * headerX = m_SentHandshakePacket->headerX, + uint8_t * headerX = m_SentHandshakePacket->headerX, * payload = m_SentHandshakePacket->payload; header.h.connID = m_DestConnID; // dest id - header.h.packetNum = 0; + RAND_bytes (header.buf + 8, 4); // random packet num header.h.type = eSSU2SessionCreated; header.h.flags[0] = 2; // ver header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID @@ -647,7 +842,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) @@ -659,15 +854,13 @@ namespace transport } auto token = m_Server.NewIncomingToken (m_RemoteEndpoint); if (ts + SSU2_TOKEN_EXPIRATION_THRESHOLD > token.second) // not expired? - { + { payload[payloadSize] = eSSU2BlkNewToken; htobe16buf (payload + payloadSize + 1, 12); htobe32buf (payload + payloadSize + 3, token.second - SSU2_TOKEN_EXPIRATION_THRESHOLD); // expires memcpy (payload + payloadSize + 7, &token.first, 8); // token payloadSize += 15; - } - if (m_TerminationReason != eSSU2TerminationReasonNormalClose) - payloadSize += CreateTerminationBlock (payload + payloadSize, maxPayloadSize - payloadSize); + } payloadSize += CreatePaddingBlock (payload + payloadSize, maxPayloadSize - payloadSize); // KDF for SessionCreated m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) @@ -676,20 +869,18 @@ namespace transport m_EphemeralKeys->Agree (X, sharedSecret); m_NoiseState->MixKey (sharedSecret); // encrypt - const uint8_t nonce[12] = {0}; + const uint8_t nonce[12] = {0}; // always zero i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, m_NoiseState->m_H, 32, m_NoiseState->m_CK + 32, nonce, payload, payloadSize + 16, true); payloadSize += 16; m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created) header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); - i2p::crypto::ChaCha20 (headerX, 48, kh2, nonce, headerX); + m_Server.ChaCha20 (headerX, 48, kh2, nonce, headerX); m_State = eSSU2SessionStateSessionCreatedSent; m_SentHandshakePacket->payloadSize = payloadSize; // send + m_HandshakeInterval = ts; m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); - // terminate if errors - if (m_TerminationReason != eSSU2TerminationReasonNormalClose) - Terminate (); } bool SSU2Session::ProcessSessionCreated (uint8_t * buf, size_t len) @@ -704,9 +895,15 @@ namespace transport if (header.h.type != eSSU2SessionCreated) // this situation is valid, because it might be Retry with different encryption return false; + if (len < 80) + { + LogPrint (eLogWarning, "SSU2: SessionCreated message too short ", len); + return false; + } + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; const uint8_t nonce[12] = {0}; uint8_t headerX[48]; - i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX); + m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX); // KDF for SessionCreated m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk); @@ -720,6 +917,8 @@ namespace transport m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) { LogPrint (eLogWarning, "SSU2: SessionCreated AEAD verification failed "); + if (GetRemoteIdentity ()) + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key return false; } m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from SessionCreated) for SessionConfirmed @@ -740,28 +939,28 @@ namespace transport // we are Alice m_SentHandshakePacket.reset (new HandshakePacket); m_SentHandshakePacket->sendTime = i2p::util::GetMillisecondsSinceEpoch (); - + uint8_t kh2[32]; i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessionConfirmed", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32) // fill packet Header& header = m_SentHandshakePacket->header; header.h.connID = m_DestConnID; // dest id - header.h.packetNum = 0; + header.h.packetNum = 0; // always zero header.h.type = eSSU2SessionConfirmed; memset (header.h.flags, 0, 3); header.h.flags[0] = 1; // frag, total fragments always 1 // payload size_t maxPayloadSize = m_MaxPayloadSize - 48; // for part 2, 48 is part1 uint8_t * payload = m_SentHandshakePacket->payload; - size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ()); + size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ()); if (!payloadSize) { // split by two fragments maxPayloadSize += m_MaxPayloadSize; - payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ()); + payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ()); header.h.flags[0] = 0x02; // frag 0, total fragments 2 // TODO: check if we need more fragments - } + } if (payloadSize < maxPayloadSize) payloadSize += CreatePaddingBlock (payload + payloadSize, maxPayloadSize - payloadSize); // KDF for Session Confirmed part 1 @@ -769,7 +968,7 @@ namespace transport // Encrypt part 1 uint8_t * part1 = m_SentHandshakePacket->headerX; uint8_t nonce[12]; - CreateNonce (1, nonce); + CreateNonce (1, nonce); // always one i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetSSU2StaticPublicKey (), 32, m_NoiseState->m_H, 32, m_NoiseState->m_CK + 32, nonce, part1, 48, true); m_NoiseState->MixHash (part1, 48); // h = SHA256(h || ciphertext); // KDF for Session Confirmed part 2 @@ -777,7 +976,7 @@ namespace transport i2p::context.GetSSU2StaticKeys ().Agree (Y, sharedSecret); m_NoiseState->MixKey (sharedSecret); // Encrypt part2 - memset (nonce, 0, 12); + memset (nonce, 0, 12); // always zero i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, m_NoiseState->m_H, 32, m_NoiseState->m_CK + 32, nonce, payload, payloadSize + 16, true); payloadSize += 16; m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || ciphertext); @@ -786,13 +985,13 @@ namespace transport { if (payloadSize > m_MaxPayloadSize - 48) { - payloadSize = m_MaxPayloadSize - 48 - (rand () % 16); + payloadSize = m_MaxPayloadSize - 48 - (m_Server.GetRng ()() % 16); if (m_SentHandshakePacket->payloadSize - payloadSize < 24) payloadSize -= 24; - } + } else header.h.flags[0] = 1; - } + } // Encrypt header header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); @@ -811,12 +1010,12 @@ namespace transport memset (header.h.flags, 0, 3); header.h.flags[0] = 0x12; // frag 1, total fragments 2 m_SessionConfirmedFragment->payloadSize = m_SentHandshakePacket->payloadSize - payloadSize; - memcpy (m_SessionConfirmedFragment->payload, m_SentHandshakePacket->payload + payloadSize, m_SessionConfirmedFragment->payloadSize); + memcpy (m_SessionConfirmedFragment->payload, m_SentHandshakePacket->payload + payloadSize, m_SessionConfirmedFragment->payloadSize); m_SentHandshakePacket->payloadSize = payloadSize; header.ll[0] ^= CreateHeaderMask (m_Address->i, m_SessionConfirmedFragment->payload + (m_SessionConfirmedFragment->payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (kh2, m_SessionConfirmedFragment->payload + (m_SessionConfirmedFragment->payloadSize - 12)); m_Server.Send (header.buf, 16, m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize, m_RemoteEndpoint); - } + } } bool SSU2Session::ProcessSessionConfirmed (uint8_t * buf, size_t len) @@ -834,21 +1033,39 @@ namespace transport // TODO: queue up return true; } + // packet num must be always zero + if (header.h.packetNum) + { + LogPrint (eLogError, "SSU2: Non zero packet number in SessionConfirmed"); + return false; + } // check if fragmented - if ((header.h.flags[0] & 0x0F) > 1) + uint8_t numFragments = header.h.flags[0] & 0x0F; + if (numFragments > 1) { // fragmented + if (numFragments > 2) + { + LogPrint (eLogError, "SSU2: Too many fragments ", (int)numFragments, " in SessionConfirmed from ", m_RemoteEndpoint); + return false; + } + if (len < 32) + { + 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 if (!m_SessionConfirmedFragment) - { + { m_SessionConfirmedFragment.reset (new HandshakePacket); m_SessionConfirmedFragment->header = header; memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); m_SessionConfirmedFragment->payloadSize = len - 16; return true; // wait for second fragment - } + } else if (m_SessionConfirmedFragment->isSecondFragment) { // we have second fragment @@ -856,31 +1073,42 @@ namespace transport memmove (m_SessionConfirmedFragment->payload + (len - 16), m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize); memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); m_SessionConfirmedFragment->payloadSize += (len - 16); + m_SessionConfirmedFragment->isSecondFragment = false; buf = m_SessionConfirmedFragment->payload - 16; len = m_SessionConfirmedFragment->payloadSize + 16; - } + } else return true; } else { // second fragment - if (!m_SessionConfirmedFragment) - { + if (!m_SessionConfirmedFragment) + { // out of sequence, save it m_SessionConfirmedFragment.reset (new HandshakePacket); memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); m_SessionConfirmedFragment->payloadSize = len - 16; m_SessionConfirmedFragment->isSecondFragment = true; - return true; - } + return true; + } header = m_SessionConfirmedFragment->header; - memcpy (m_SessionConfirmedFragment->payload + m_SessionConfirmedFragment->payloadSize, buf + 16, len - 16); - m_SessionConfirmedFragment->payloadSize += (len - 16); + if (m_SessionConfirmedFragment->payloadSize + (len - 16) <= SSU2_MAX_PACKET_SIZE*2) + { + memcpy (m_SessionConfirmedFragment->payload + m_SessionConfirmedFragment->payloadSize, buf + 16, len - 16); + m_SessionConfirmedFragment->payloadSize += (len - 16); + } buf = m_SessionConfirmedFragment->payload - 16; len = m_SessionConfirmedFragment->payloadSize + 16; } } + if (len < 80) + { + LogPrint (eLogWarning, "SSU2: SessionConfirmed message too short ", len); + if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); + return false; + } + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; // KDF for Session Confirmed part 1 m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) // decrypt part1 @@ -891,6 +1119,7 @@ namespace transport m_NoiseState->m_CK + 32, nonce, S, 32, false)) { LogPrint (eLogWarning, "SSU2: SessionConfirmed part 1 AEAD verification failed "); + if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); return false; } m_NoiseState->MixHash (buf + 16, 48); // h = SHA256(h || ciphertext); @@ -907,9 +1136,11 @@ namespace transport m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) { LogPrint (eLogWarning, "SSU2: SessionConfirmed part 2 AEAD verification failed "); + if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); return false; } m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || ciphertext); + if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); // payload // handle RouterInfo block that must be first if (decryptedPayload[0] != eSSU2BlkRouterInfo) @@ -930,23 +1161,80 @@ namespace transport LogPrint (eLogError, "SSU2: SessionConfirmed malformed RouterInfo block"); return false; } - m_Address = ri->GetSSU2AddressWithStaticKey (S, m_RemoteEndpoint.address ().is_v6 ()); - if (!m_Address) + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + if (ts > ri->GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes { - LogPrint (eLogError, "SSU2: No SSU2 address with static key found in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + LogPrint (eLogError, "SSU2: RouterInfo in SessionConfirmed is too old for ", (ts - ri->GetTimestamp ())/1000LL, " seconds"); return false; - } + } + if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri->GetTimestamp ()) // 2 minutes + { + LogPrint (eLogError, "SSU2: RouterInfo in SessionConfirmed is from future for ", (ri->GetTimestamp () - ts)/1000LL, " seconds"); + return false; + } // update RouterInfo in netdb - ri = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now - if (!ri) + auto ri1 = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now + if (!ri1) { LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); return false; + } + + bool isOlder = false; + if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) + { + // received RouterInfo is older than one in netdb + isOlder = true; + if (ri->HasProfile ()) + { + auto profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile + if (profile && profile->IsDuplicated ()) + return false; + } + } + ri = ri1; + + m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address (); + if (!m_Address || memcmp (S, m_Address->s, 32)) + { + 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 + { + if (isOlder) // older router? + i2p::data::UpdateRouterProfile (ri->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->Duplicated (); // mark router as duplicated in profile + }); + else + LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host, + " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + return false; + } + if (!m_Address->published) + { + if (ri->HasProfile ()) + ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); + else + i2p::data::UpdateRouterProfile (ri->GetIdentHash (), + [ep = m_RemoteEndpoint](std::shared_ptr profile) + { + if (profile) profile->SetLastEndpoint (ep); + }); } SetRemoteIdentity (ri->GetRouterIdentity ()); AdjustMaxPayloadSize (); m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now m_RemoteTransports = ri->GetCompatibleTransports (false); + m_RemotePeerTestTransports = 0; + if (ri->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4; + if (ri->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6; + m_RemoteVersion = ri->GetVersion (); + // handle other blocks HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3); Established (); @@ -984,7 +1272,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 @@ -995,15 +1283,15 @@ namespace transport header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); memset (nonce, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); + m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); // send - if (m_Server.AddPendingOutgoingSession (shared_from_this ())) + if (m_Server.AddPendingOutgoingSession (shared_from_this ())) m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); else { - LogPrint (eLogWarning, "SSU2: TokenRequest request to ", m_RemoteEndpoint, " already pending"); + LogPrint (eLogWarning, "SSU2: TokenRequest request to ", m_RemoteEndpoint, " already pending"); Terminate (); - } + } } void SSU2Session::ProcessTokenRequest (Header& header, uint8_t * buf, size_t len) @@ -1013,11 +1301,11 @@ namespace transport { LogPrint (eLogWarning, "SSU2: Incorrect TokenRequest len ", len); return; - } + } uint8_t nonce[12] = {0}; uint8_t h[32]; memcpy (h, header.buf, 16); - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); memcpy (&m_DestConnID, h + 16, 8); // decrypt CreateNonce (be32toh (header.h.packetNum), nonce); @@ -1029,6 +1317,7 @@ namespace transport return; } // payload + m_State = eSSU2SessionStateTokenRequestReceived; HandlePayload (payload, len - 48); SendRetry (); } @@ -1037,7 +1326,7 @@ namespace transport { // we are Bob Header header; - uint8_t h[32], payload[64]; + uint8_t h[32], payload[72]; // fill packet header.h.connID = m_DestConnID; // dest id RAND_bytes (header.buf + 8, 4); // random packet num @@ -1047,15 +1336,19 @@ namespace transport header.h.flags[2] = 0; // flag memcpy (h, header.buf, 16); memcpy (h + 16, &m_SourceConnID, 8); // source id - uint64_t token = m_Server.GetIncomingToken (m_RemoteEndpoint); + uint64_t token = 0; + if (m_TerminationReason == eSSU2TerminationReasonNormalClose) + token = m_Server.GetIncomingToken (m_RemoteEndpoint); memcpy (h + 24, &token, 8); // token // 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, 64 - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePaddingBlock (payload + payloadSize, 64 - payloadSize); + payloadSize += CreateAddressBlock (payload + payloadSize, 56 - payloadSize, m_RemoteEndpoint); + if (m_TerminationReason != eSSU2TerminationReasonNormalClose) + payloadSize += CreateTerminationBlock (payload + payloadSize, 56 - payloadSize); + payloadSize += CreatePaddingBlock (payload + payloadSize, 56 - payloadSize); // encrypt uint8_t nonce[12]; CreateNonce (be32toh (header.h.packetNum), nonce); @@ -1064,7 +1357,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12)); memset (nonce, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + m_Server.ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); // send m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); } @@ -1081,10 +1374,17 @@ namespace transport LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2Retry); return false; } + if (len < 48) + { + 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); - m_Server.UpdateOutgoingToken (m_RemoteEndpoint, headerX[1], i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); + m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); + uint64_t token = headerX[1]; + if (token) + m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); // decrypt and handle payload uint8_t * payload = buf + 32; CreateNonce (be32toh (header.h.packetNum), nonce); @@ -1094,57 +1394,22 @@ namespace transport if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, m_Address->i, nonce, payload, len - 48, false)) { - LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed "); + LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed"); return false; } - HandlePayload (payload, len - 48); - m_State = eSSU2SessionStateTokenReceived; + HandlePayload (payload, len - 48); + if (!token) + { + // we should handle payload even for zero token to handle Datetime block and adjust clock in case of clock skew + LogPrint (eLogWarning, "SSU2: Retry token is zero"); + return false; + } InitNoiseXKState1 (*m_NoiseState, m_Address->s); // reset Noise TODO: check state - SendSessionRequest (headerX[1]); + SendSessionRequest (token); return true; } - - void SSU2Session::SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, - const uint8_t * introKey, uint64_t token) - { - // we are Charlie - LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep); - Header header; - uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; - // fill packet - header.h.connID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2HolePunch; - header.h.flags[0] = 2; // ver - header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID - header.h.flags[2] = 0; // flag - memcpy (h, header.buf, 16); - uint64_t c = ~header.h.connID; - memcpy (h + 16, &c, 8); // source id - RAND_bytes (h + 24, 8); // token - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); - size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, ep); - payloadSize += CreateRelayResponseBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, - eSSU2RelayResponseCodeAccept, nonce, token, ep.address ().is_v4 ()); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // encrypt - uint8_t n[12]; - CreateNonce (be32toh (header.h.packetNum), n); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); - payloadSize += 16; - header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); - memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); - // send - m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); - } - + bool SSU2Session::ProcessHolePunch (uint8_t * buf, size_t len) { // we are Alice @@ -1158,9 +1423,14 @@ namespace transport LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2HolePunch); return false; } + if (len < 48) + { + 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); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); m_DestConnID = headerX[0]; // decrypt and handle payload uint8_t * payload = buf + 32; @@ -1175,81 +1445,20 @@ namespace transport return false; } HandlePayload (payload, len - 48); + m_IsDataReceived = false; // connect to Charlie ConnectAfterIntroduction (); return true; } - void SSU2Session::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey) - { - Header header; - uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; - // fill packet - header.h.connID = m_DestConnID; // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2PeerTest; - header.h.flags[0] = 2; // ver - header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID - header.h.flags[2] = 0; // flag - memcpy (h, header.buf, 16); - memcpy (h + 16, &m_SourceConnID, 8); // source id - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); - size_t payloadSize = 7; - if (msg == 6 || msg == 7) - payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, - msg, eSSU2PeerTestCodeAccept, nullptr, signedData, signedDataLen); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // encrypt - uint8_t n[12]; - CreateNonce (be32toh (header.h.packetNum), n); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); - payloadSize += 16; - header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); - memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); - // send - m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); - } - bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len) { - // we are Alice or Charlie - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); - header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); - if (header.h.type != eSSU2PeerTest) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); - return false; - } - uint8_t nonce[12] = {0}; - uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); - m_DestConnID = headerX[0]; - // decrypt and handle payload - uint8_t * payload = buf + 32; - CreateNonce (be32toh (header.h.packetNum), nonce); - uint8_t h[32]; - memcpy (h, header.buf, 16); - memcpy (h + 16, &headerX, 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, - i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) - { - LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); - return false; - } - HandlePayload (payload, len - 48); - return true; + LogPrint (eLogWarning, "SSU2: Unexpected peer test message for this session type"); + return false; } - uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len) + uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags) { if (len < 8) { @@ -1261,20 +1470,20 @@ namespace transport header.h.packetNum = htobe32 (m_SendPacketNum); header.h.type = eSSU2Data; memset (header.h.flags, 0, 3); + if (flags) header.h.flags[0] = flags; uint8_t nonce[12]; CreateNonce (m_SendPacketNum, nonce); uint8_t payload[SSU2_MAX_PACKET_SIZE]; - i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE, true); + m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8)); header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4)); m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint); m_SendPacketNum++; - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumSentBytes += len + 32; + UpdateNumSentBytes (len + 32); return m_SendPacketNum - 1; } - void SSU2Session::ProcessData (uint8_t * buf, size_t len) + void SSU2Session::ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { Header header; header.ll[0] = m_SourceConnID; @@ -1289,19 +1498,30 @@ namespace transport ResendHandshakePacket (); // assume we receive return; } + if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()) && + (!m_PathChallenge || from != m_PathChallenge->second)) // path challenge was not sent to this endpoint yet + { + LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); + SendPathChallenge (from); + } + if (len < 32) + { + 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); uint8_t nonce[12]; CreateNonce (packetNum, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16, - m_KeyDataReceive, nonce, payload, payloadSize, false)) + if (!m_Server.AEADChaCha20Poly1305Decrypt (buf + 16, payloadSize, header.buf, 16, + m_KeyDataReceive, nonce, payload, payloadSize)) { LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); return; } - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumReceivedBytes += len; + UpdateNumReceivedBytes (len); + if (header.h.flags[0] & SSU2_FLAG_IMMEDIATE_ACK_REQUESTED) m_IsDataReceived = true; if (!packetNum || UpdateReceivePacketNum (packetNum)) HandlePayload (payload, payloadSize); } @@ -1316,7 +1536,7 @@ namespace transport auto size = bufbe16toh (buf + offset); offset += 2; LogPrint (eLogDebug, "SSU2: Block type ", (int)blk, " of size ", size); - if (size > len) + if (offset + size > len) { LogPrint (eLogError, "SSU2: Unexpected block length ", size); break; @@ -1331,14 +1551,9 @@ namespace transport LogPrint (eLogDebug, "SSU2: Options"); break; case eSSU2BlkRouterInfo: - { - // not from SessionConfirmed, we must add it instantly to use in next block LogPrint (eLogDebug, "SSU2: RouterInfo"); - auto ri = ExtractRouterInfo (buf + offset, size); - if (ri) - i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // TODO: add ri - break; - } + HandleRouterInfo (buf + offset, size); + break; case eSSU2BlkI2NPMessage: { LogPrint (eLogDebug, "SSU2: I2NP message"); @@ -1346,7 +1561,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; } @@ -1361,23 +1576,38 @@ namespace transport m_IsDataReceived = true; break; case eSSU2BlkTermination: - LogPrint (eLogDebug, "SSU2: Termination reason=", (int)buf[11]); - if (IsEstablished () && buf[11] != eSSU2TerminationReasonTerminationReceived) - RequestTermination (eSSU2TerminationReasonTerminationReceived); - else - Done (); - break; + { + if (size >= 9) + { + uint8_t rsn = buf[offset + 8]; // reason + LogPrint (eLogDebug, "SSU2: Termination reason=", (int)rsn); + if (IsEstablished () && rsn != eSSU2TerminationReasonTerminationReceived) + RequestTermination (eSSU2TerminationReasonTerminationReceived); + else if (m_State != eSSU2SessionStateTerminated) + { + if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived) + m_State = eSSU2SessionStateClosingConfirmed; + Done (); + } + } + else + LogPrint(eLogWarning, "SSU2: Unexpected termination block size ", size); + break; + } case eSSU2BlkRelayRequest: LogPrint (eLogDebug, "SSU2: RelayRequest"); HandleRelayRequest (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkRelayResponse: LogPrint (eLogDebug, "SSU2: RelayResponse"); HandleRelayResponse (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkRelayIntro: LogPrint (eLogDebug, "SSU2: RelayIntro"); HandleRelayIntro (buf + offset, size); + m_IsDataReceived = true; break; case eSSU2BlkPeerTest: LogPrint (eLogDebug, "SSU2: PeerTest msg=", (int)buf[offset], " code=", (int)buf[offset+1]); @@ -1401,8 +1631,12 @@ namespace transport LogPrint (eLogDebug, "SSU2: RelayTagRequest"); if (!m_RelayTag) { - RAND_bytes ((uint8_t *)&m_RelayTag, 4); - m_Server.AddRelay (m_RelayTag, shared_from_this ()); + auto addr = FindLocalAddress (); + if (addr && addr->IsIntroducer ()) + { + RAND_bytes ((uint8_t *)&m_RelayTag, 4); + m_Server.AddRelay (m_RelayTag, shared_from_this ()); + } } break; case eSSU2BlkRelayTag: @@ -1422,8 +1656,18 @@ namespace transport SendPathResponse (buf + offset, size); break; case eSSU2BlkPathResponse: + { LogPrint (eLogDebug, "SSU2: Path response"); - break; + if (m_PathChallenge) + { + if (buf64toh (buf + offset) == m_PathChallenge->first) + { + m_RemoteEndpoint = m_PathChallenge->second; + m_PathChallenge.reset (nullptr); + } + } + break; + } case eSSU2BlkFirstPacketNumber: break; case eSSU2BlkPadding: @@ -1442,30 +1686,67 @@ namespace transport switch (m_State) { case eSSU2SessionStateSessionRequestReceived: + case eSSU2SessionStateTokenRequestReceived: + case eSSU2SessionStateEstablished: if (std::abs (offset) > SSU2_CLOCK_SKEW) m_TerminationReason = eSSU2TerminationReasonClockSkew; break; - case eSSU2SessionStateSessionCreatedReceived: - if ((m_RemoteEndpoint.address ().is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) || - (m_RemoteEndpoint.address ().is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusTesting)) + case eSSU2SessionStateSessionCreatedReceived: + case eSSU2SessionStateTokenReceived: + if ((m_RemoteEndpoint.address ().is_v4 () && i2p::context.GetTesting ()) || + (m_RemoteEndpoint.address ().is_v6 () && i2p::context.GetTestingV6 ())) { if (m_Server.IsSyncClockFromPeers ()) { if (std::abs (offset) > SSU2_CLOCK_THRESHOLD) - { - LogPrint (eLogWarning, "SSU2: Clock adjusted by ", -offset, " seconds"); - i2p::util::AdjustTimeOffset (-offset); - } + { + LogPrint (eLogWarning, "SSU2: Time offset ", offset, " from ", m_RemoteEndpoint); + m_Server.AdjustTimeOffset (-offset, GetRemoteIdentity ()); + } + else + m_Server.AdjustTimeOffset (0, nullptr); } else if (std::abs (offset) > SSU2_CLOCK_SKEW) { LogPrint (eLogError, "SSU2: Clock skew detected ", offset, ". Check your clock"); i2p::context.SetError (eRouterErrorClockSkew); } + } + break; + default: ; + }; + } + + void SSU2Session::HandleRouterInfo (const uint8_t * buf, size_t len) + { + if (len < 2) return; + // not from SessionConfirmed, we must add it instantly to use in next block + std::shared_ptr newRi; + if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) // compressed? + { + auto ri = ExtractRouterInfo (buf, len); + if (ri) + newRi = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); + } + else // use buffer directly. TODO: handle frag + newRi = i2p::data::netdb.AddRouterInfo (buf + 2, len - 2); + + if (newRi) + { + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) + { + // peer's RouterInfo update + SetRemoteIdentity (newRi->GetIdentity ()); + auto address = m_RemoteEndpoint.address ().is_v6 () ? newRi->GetSSU2V6Address () : newRi->GetSSU2V4Address (); + if (address) + { + m_Address = address; + if (IsOutgoing () && m_RelayTag && !address->IsIntroducer ()) + m_RelayTag = 0; // not longer introducer } - break; - default: ; - }; + } + } } void SSU2Session::HandleAck (const uint8_t * buf, size_t len) @@ -1474,7 +1755,7 @@ namespace transport { Established (); return; - } + } if (m_SentPackets.empty ()) return; if (len < 5) return; // acnt @@ -1483,8 +1764,9 @@ namespace transport HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt // ranges len -= 5; + if (!len || m_SentPackets.empty ()) return; // don't handle ranges if nothing to acknowledge const uint8_t * ranges = buf + 5; - while (len > 0 && firstPacketNum) + while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS) { uint32_t lastPacketNum = firstPacketNum - 1; if (*ranges > lastPacketNum) break; @@ -1504,76 +1786,100 @@ namespace transport if (it == m_SentPackets.end () || it->first > lastPacketNum) return; // not found auto it1 = it; int numPackets = 0; - while (it1 != m_SentPackets.end () && it1->first <= lastPacketNum) - { + while (it1 != m_SentPackets.end () && it1->first <= lastPacketNum) + { if (ts && !it1->second->numResends) { if (ts > it1->second->sendTime) { auto rtt = ts - it1->second->sendTime; - m_RTT = (m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1); + if (m_RTT != SSU2_UNKNOWN_RTT) + m_RTT = SSU2_RTT_EWMA_ALPHA * rtt + (1.0 - SSU2_RTT_EWMA_ALPHA) * m_RTT; + else + m_RTT = rtt; m_RTO = m_RTT*SSU2_kAPPA; + m_MsgLocalExpirationTimeout = std::max (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN, + std::min (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX, + (unsigned int)(m_RTT * 1000 * I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR))); + m_MsgLocalSemiExpirationTimeout = m_MsgLocalExpirationTimeout / 2; if (m_RTO < SSU2_MIN_RTO) m_RTO = SSU2_MIN_RTO; if (m_RTO > SSU2_MAX_RTO) m_RTO = SSU2_MAX_RTO; - } + } ts = 0; // update RTT one time per range - } + } it1++; numPackets++; - } + } m_SentPackets.erase (it, it1); if (numPackets > 0) { m_WindowSize += numPackets; if (m_WindowSize > SSU2_MAX_WINDOW_SIZE) m_WindowSize = SSU2_MAX_WINDOW_SIZE; - } + } } void SSU2Session::HandleAddress (const uint8_t * buf, size_t len) { boost::asio::ip::udp::endpoint ep; if (ExtractEndpoint (buf, len, ep)) - { + { LogPrint (eLogInfo, "SSU2: Our external address is ", ep); - if (!i2p::util::net::IsInReservedRange (ep.address ())) - { + if (!i2p::transport::transports.IsInReservedRange (ep.address ())) + { i2p::context.UpdateAddress (ep.address ()); // check our port 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) + if (i2p::context.GetTesting ()) i2p::context.SetError (eRouterErrorSymmetricNAT); - } + else if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetError (eRouterErrorFullConeNAT); + } else { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + if (i2p::context.GetTestingV6 ()) i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT); - } - } + else if (m_State == eSSU2SessionStatePeerTest) + i2p::context.SetErrorV6 (eRouterErrorFullConeNAT); + } + } else { if (isV4) { - if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) - i2p::context.SetStatus (eRouterStatusTesting); - } + 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.GetStatusV6 () == eRouterStatusError && i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT) - i2p::context.SetStatusV6 (eRouterStatusTesting); - } - } - } - } - } - + 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); + } + } + } + } + } + void SSU2Session::HandleFirstFragment (const uint8_t * buf, size_t len) { + auto msg = (buf[0] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); uint32_t msgID; memcpy (&msgID, buf + 1, 4); - auto msg = NewI2NPShortMessage (); // same format as I2NP message block msg->len = msg->offset + len + 7; memcpy (msg->GetNTCP2Header (), buf, len); @@ -1587,17 +1893,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); } } @@ -1606,12 +1912,18 @@ 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 (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS && + if (fragmentNum < it->second->nextFragmentNum) return; // duplicate + if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS && it->second->msg) { // in sequence @@ -1619,14 +1931,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 @@ -1638,148 +1950,181 @@ 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) { // we are Bob + if (len < 9) return; + auto mts = i2p::util::GetMillisecondsSinceEpoch (); + uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t relayTag = bufbe32toh (buf + 5); // relay tag auto session = m_Server.FindRelaySession (relayTag); if (!session) { LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found"); // send relay response back to Alice - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, - eSSU2RelayResponseCodeBobRelayTagNotFound, bufbe32toh (buf + 1), 0, false); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); - return; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreateAckBlock (packet->payload, m_MaxPayloadSize); + packet->payloadSize += CreateRelayResponseBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, + eSSU2RelayResponseCodeBobRelayTagNotFound, nonce, 0, false); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) + { + // sometimes Alice doesn't ack this RelayResponse in older versions + packet->sendTime = mts; + m_SentPackets.emplace (packetNum, packet); + } + return; } - session->m_RelaySessions.emplace (bufbe32toh (buf + 1), // nonce - std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()) ); + if (session->m_RelaySessions.emplace (nonce, std::make_pair (shared_from_this (), mts/1000)).second) + { + // send relay intro to Charlie + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI + if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; + if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); - // send relay intro to Charlie - auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI - if (r) - i2p::data::netdb.PopulateRouterInfoBuffer (r); + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) + session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len - 1); + if (packet->payloadSize < m_MaxPayloadSize) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize); + packet->sendTime = mts; + // Charlie always responds with RelayResponse + session->m_SentPackets.emplace (packetNum, packet); + } else - 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) - session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - payloadSize += CreateRelayIntroBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, buf + 1, len -1); - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - session->SendData (payload, payloadSize); + LogPrint (eLogInfo, "SSU2: Relay request nonce ", nonce, " already exists. Ignore"); } - 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 + if (len < 47) return; SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; - uint64_t token = 0; - bool isV4 = false; + boost::asio::ip::udp::endpoint ep; + std::shared_ptr addr; auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice if (r) - { - SignedData s; + { + SignedData<128> s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (i2p::context.GetIdentHash (), 32); // chash s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz uint8_t asz = buf[46]; + if (asz + 47 + r->GetIdentity ()->GetSignatureLen () > len) + { + LogPrint (eLogWarning, "SSU2: Malformed RelayIntro len=", len); + return; + } s.Insert (buf + 47, asz); // Alice Port, Alice IP if (s.Verify (r->GetIdentity (), buf + 47 + asz)) { - // send HolePunch - boost::asio::ip::udp::endpoint ep; + // obtain and check endpoint and address for HolePunch if (ExtractEndpoint (buf + 47, asz, ep)) { - auto addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); - if (addr) + if (!ep.address ().is_unspecified () && ep.port ()) { if (m_Server.IsSupported (ep.address ())) { - token = m_Server.GetIncomingToken (ep); - isV4 = ep.address ().is_v4 (); - SendHolePunch (bufbe32toh (buf + 33), ep, addr->i, token); - } + addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (!addr) + { + LogPrint (eLogWarning, "SSU2: RelayIntro address for endpoint not found"); + code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; + } + } else - { + { LogPrint (eLogWarning, "SSU2: RelayIntro unsupported address"); code = eSSU2RelayResponseCodeCharlieUnsupportedAddress; } - } + } else { - LogPrint (eLogWarning, "SSU2: RelayIntro unknown address"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } + LogPrint (eLogWarning, "SSU2: RelayIntro invalid endpoint"); + code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; + } } else { LogPrint (eLogWarning, "SSU2: RelayIntro can't extract endpoint"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } + code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; + } } else { LogPrint (eLogWarning, "SSU2: RelayIntro signature verification failed"); 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 (); + boost::asio::post (m_Server.GetService (), [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 - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, - code, bufbe32toh (buf + 33), token, isV4); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint32_t nonce = bufbe32toh (buf + 33); + packet->payloadSize = CreateRelayResponseBlock (packet->payload, m_MaxPayloadSize, + code, nonce, m_Server.GetIncomingToken (ep), ep.address ().is_v4 ()); + if (code == eSSU2RelayResponseCodeAccept && addr) + { + // send HolePunch + auto holePunchSession = std::make_shared(m_Server, nonce, ep, addr); + if (m_Server.AddSession (holePunchSession)) + holePunchSession->SendHolePunch (packet->payload, packet->payloadSize); // relay response block + else + { + LogPrint (eLogInfo, "SSU2: Relay intro nonce ", nonce, " already exists. Ignore"); + return; + } + } + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) + { + // sometimes Bob doesn't ack this RelayResponse in older versions + packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); + m_SentPackets.emplace (packetNum, packet); + } } void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) - { + { + if (len < 6) return; uint32_t nonce = bufbe32toh (buf + 2); - if (m_State == eSSU2SessionStateIntroduced) - { + if (m_State == eSSU2SessionStateIntroduced) + { // HolePunch from Charlie // TODO: verify address and signature // verify nonce @@ -1788,132 +2133,162 @@ namespace transport if (len >= 8) { // new token - uint64_t token; + uint64_t token; memcpy (&token, buf + len - 8, 8); m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); - } - return; - } - auto it = m_RelaySessions.find (nonce); + } + return; + } + auto it = m_RelaySessions.find (nonce); if (it != m_RelaySessions.end ()) { - if (it->second.first && it->second.first->IsEstablished ()) - { + auto relaySession = it->second.first; + m_RelaySessions.erase (it); + if (relaySession && relaySession->IsEstablished ()) + { // we are Bob, message from Charlie - uint8_t payload[SSU2_MAX_PACKET_SIZE]; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayResponse; htobe16buf (payload + 1, len); memcpy (payload + 3, buf, len); // forward to Alice as is - size_t payloadSize = len + 3; - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - it->second.first->SendData (payload, payloadSize); - } + packet->payloadSize = len + 3; + packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); + if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) + { + // sometimes Alice doesn't ack this RelayResponse in older versions + packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); + relaySession->m_SentPackets.emplace (packetNum, packet); + } + } else { // we are Alice, message from Bob if (!buf[1]) // status code accepted? { // verify signature - uint8_t csz = buf[11]; - SignedData s; + uint8_t csz = (len >= 12) ? buf[11] : 0; + if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len) + { + LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len); + relaySession->Done (); + return; + } + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint - if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz)) + if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz)) { - if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet - { + if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet + { // update Charlie's endpoint - if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint)) - { + if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) + { // update token - uint64_t token; + uint64_t token; memcpy (&token, buf + len - 8, 8); - m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint, + m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); // connect to Charlie, HolePunch will be ignored - it->second.first->ConnectAfterIntroduction (); + relaySession->ConnectAfterIntroduction (); } else LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); - } + } } else - { + { LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); - it->second.first->Done (); - } + relaySession->Done (); + } } else - { - LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1]); - it->second.first->Done (); - } + { + LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); + relaySession->Done (); + } } - m_RelaySessions.erase (it); } else - LogPrint (eLogWarning, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); + LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); } void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len) { + // msgs 1-4 if (len < 3) return; uint8_t msg = buf[0]; size_t offset = 3; // points to signed data - if (msg == 2 || msg == 4) offset += 32; // hash is presented for msg 2 and 4 only - if (len < offset + 5) return; - uint32_t nonce = bufbe32toh (buf + offset + 1); + if (msg == 2 || msg == 4) offset += 32; // hash is presented for msg 2 and 4 only + if (len < offset + 5) return; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + uint32_t nonce = bufbe32toh (buf + offset + 1); switch (msg) // msg { case 1: // Bob from Alice - { - auto session = m_Server.GetRandomSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6, + { + auto session = m_Server.GetRandomPeerTestSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6, GetRemoteIdentity ()->GetIdentHash ()); if (session) // session with Charlie { - session->m_PeerTests.emplace (nonce, std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ())); - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - // Alice's RouterInfo - auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); - if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) - session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (payloadSize + len + 48 > m_MaxPayloadSize) - { - // doesn't fit one message, send RouterInfo in separate message - session->SendData (payload, payloadSize); - payloadSize = 0; - } - // PeerTest to Charlie - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, 2, - eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - session->SendData (payload, payloadSize); + if (m_Server.AddPeerTest (nonce, shared_from_this (), ts/1000)) + { + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + // Alice's RouterInfo + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); + 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)); + if (packet->payloadSize + len + 48 > m_MaxPayloadSize) + { + // doesn't fit one message, send RouterInfo in separate message + uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); + packet->sendTime = ts; + session->m_SentPackets.emplace (packetNum, packet); + packet = m_Server.GetSentPacketsPool ().AcquireShared (); // new packet + } + // PeerTest to Charlie + packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, 2, + eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); + packet->sendTime = ts; + session->m_SentPackets.emplace (packetNum, packet); + } + else + LogPrint (eLogInfo, "SSU2: Peer test 1 nonce ", nonce, " already exists. Ignored"); } else { // Charlie not found, send error back to Alice - uint8_t payload[SSU2_MAX_PACKET_SIZE], zeroHash[32] = {0}; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 4, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t zeroHash[32] = {0}; + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 4, eSSU2PeerTestCodeBobNoCharlieAvailable, zeroHash, buf + offset, len - offset); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); - } + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); + } break; } case 2: // Charlie from Bob { // sign with Charlie's key + if (len < offset + 9) return; uint8_t asz = buf[offset + 9]; - std::vector newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ()); + size_t l = asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen (); + if (len < offset + l) return; + std::vector newSignedData (l); memcpy (newSignedData.data (), buf + offset, asz + 10); - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 3, 32); // ahash - s.Insert (newSignedData.data (), asz + 10); // ver, nonce, ts, asz, Alice's endpoint + s.Insert (newSignedData.data (), asz + 10); // ver, nonce, ts, asz, Alice's endpoint s.Sign (i2p::context.GetPrivateKeys (), newSignedData.data () + 10 + asz); // send response (msg 3) back and msg 5 if accepted SSU2PeerTestCode code = eSSU2PeerTestCodeAccept; @@ -1922,185 +2297,212 @@ namespace transport { size_t signatureLen = r->GetIdentity ()->GetSignatureLen (); if (len >= offset + asz + 10 + signatureLen) - { + { s.Reset (); s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + offset, asz + 10); // signed data if (s.Verify (r->GetIdentity (), buf + offset + asz + 10)) - { + { if (!m_Server.FindSession (r->GetIdentity ()->GetIdentHash ())) - { + { boost::asio::ip::udp::endpoint ep; std::shared_ptr addr; - if (ExtractEndpoint (buf + offset + 10, asz, ep)) + if (ExtractEndpoint (buf + offset + 10, asz, ep) && !ep.address ().is_unspecified () && ep.port ()) addr = r->GetSSU2Address (ep.address ().is_v4 ()); - if (addr && m_Server.IsSupported (ep.address ())) - { - // send msg 5 to Alice - auto session = std::make_shared (m_Server, r, addr); - session->SetState (eSSU2SessionStatePeerTest); - session->m_RemoteEndpoint = ep; // might be different - session->m_DestConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_SourceConnID = ~session->m_DestConnID; - m_Server.AddSession (session); - session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr->i); - } + if (addr && m_Server.IsSupported (ep.address ()) && + i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ())) + { + if (!m_Server.IsConnectedRecently (ep)) // no alive hole punch + { + // send msg 5 to Alice + auto session = std::make_shared (m_Server, + 0, htobe64 (((uint64_t)nonce << 32) | nonce)); + session->m_RemoteEndpoint = ep; // might be different + m_Server.AddSession (session); + session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr); + } + else + code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; + } else code = eSSU2PeerTestCodeCharlieUnsupportedAddress; } else code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; } - else - code = eSSU2PeerTestCodeCharlieSignatureFailure; - } + else + code = eSSU2PeerTestCodeCharlieSignatureFailure; + } else // maformed message code = eSSU2PeerTestCodeCharlieReasonUnspecified; } else code = eSSU2PeerTestCodeCharlieAliceIsUnknown; // send msg 3 back to Bob - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 3, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 3, code, nullptr, newSignedData.data (), newSignedData.size ()); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); break; - } + } case 3: // Bob from Charlie { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end () && it->second.first) - { - uint8_t payload[SSU2_MAX_PACKET_SIZE]; + auto aliceSession = m_Server.GetPeerTest (nonce); + if (aliceSession && aliceSession->IsEstablished ()) + { + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Charlie's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); - if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) - it->second.first->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (payloadSize + len + 16 > m_MaxPayloadSize) + 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) + aliceSession->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + if (packet->payloadSize + len + 16 > m_MaxPayloadSize) { // doesn't fit one message, send RouterInfo in separate message - it->second.first->SendData (payload, payloadSize); - payloadSize = 0; + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + packet = m_Server.GetSentPacketsPool ().AcquireShared (); } // PeerTest to Alice - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4, + packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize, 4, (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - it->second.first->SendData (payload, payloadSize); - m_PeerTests.erase (it); - } + if (packet->payloadSize < m_MaxPayloadSize) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 3 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 3 nonce ", nonce); break; } case 4: // Alice from Bob - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) + { + auto session = m_Server.GetRequestedPeerTest (nonce); + if (session) { if (buf[1] == eSSU2PeerTestCodeAccept) - { + { if (GetRouterStatus () == eRouterStatusUnknown) - SetRouterStatus (eRouterStatusTesting); + SetTestingState (true); auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie - if (r && it->second.first) - { + if (r && len >= offset + 9) + { uint8_t asz = buf[offset + 9]; - SignedData s; + if (len < offset + asz + 10 + r->GetIdentity ()->GetSignatureLen ()) + { + LogPrint (eLogWarning, "Malformed PeerTest 4 len=", len); + session->Done (); + return; + } + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash - s.Insert (buf + offset, asz + 10); // ver, nonce, ts, asz, Alice's endpoint + s.Insert (buf + offset, asz + 10); // ver, nonce, ts, asz, Alice's endpoint if (s.Verify (r->GetIdentity (), buf + offset + asz + 10)) { - it->second.first->SetRemoteIdentity (r->GetIdentity ()); + session->SetRemoteIdentity (r->GetIdentity ()); auto addr = r->GetSSU2Address (m_Address->IsV4 ()); - if (addr) - { - it->second.first->m_Address = addr; - if (it->second.first->m_State == eSSU2SessionStatePeerTestReceived) + if (addr && addr->IsPeerTesting ()) + { + if (session->GetMsgNumReceived () >= 5) { - // msg 5 already received. send msg 6 - SetRouterStatus (eRouterStatusOK); - it->second.first->m_State = eSSU2SessionStatePeerTest; - it->second.first->SendPeerTest (6, buf + offset, len - offset, addr->i); + // msg 5 already received and we know remote endpoint + if (session->GetMsgNumReceived () == 5) + { + if (!session->IsConnectedRecently ()) + SetRouterStatus (eRouterStatusOK); + // send msg 6 immeditely + session->SendPeerTest (6, buf + offset, len - offset, addr); + } + else + LogPrint (eLogWarning, "SSU2: PeerTest 4 received, but msg ", session->GetMsgNumReceived (), " already received"); } else { - if (GetRouterStatus () == eRouterStatusTesting) - { - SetRouterStatus (eRouterStatusFirewalled); - if (m_Address->IsV4 ()) - m_Server.RescheduleIntroducersUpdateTimer (); - else - m_Server.RescheduleIntroducersUpdateTimerV6 (); - } - } + session->m_Address = addr; + if (GetTestingState ()) + { + // schedule msg 6 with delay + if (!addr->host.is_unspecified () && addr->port) + { + session->SetRemoteEndpoint (boost::asio::ip::udp::endpoint (addr->host, addr->port)); + session->SendPeerTest (6, buf + offset, len - offset, addr, true); + } + SetTestingState (false); + if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ()) + { + SetRouterStatus (eRouterStatusFirewalled); + session->SetStatusChanged (); + if (m_Address->IsV4 ()) + m_Server.RescheduleIntroducersUpdateTimer (); + else + m_Server.RescheduleIntroducersUpdateTimerV6 (); + } + } + } + LogPrint (eLogDebug, "SSU2: Peer test 4 received from ", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), + " with information about ", i2p::data::GetIdentHashAbbreviation (i2p::data::IdentHash (buf + 3))); } else { - LogPrint (eLogWarning, "SSU2: Peer test 4 address not found"); - it->second.first->Done (); - } + LogPrint (eLogWarning, "SSU2: Peer test 4 address not found or not supported"); + session->Done (); + } } else - { + { LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed"); - it->second.first->Done (); - } - } + session->Done (); + } + } + else + { + LogPrint (eLogWarning, "SSU2: Peer test 4 router not found"); + session->Done (); + } } else { LogPrint (eLogInfo, "SSU2: Peer test 4 error code ", (int)buf[1], " from ", i2p::data::GetIdentHashAbbreviation (buf[1] < 64 ? GetRemoteIdentity ()->GetIdentHash () : i2p::data::IdentHash (buf + 3))); - if (GetRouterStatus () == eRouterStatusTesting) + if (GetTestingState () && GetRouterStatus () != eRouterStatusFirewalled) SetRouterStatus (eRouterStatusUnknown); - it->second.first->Done (); - } - m_PeerTests.erase (it); - } + session->Done (); + } + } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 4 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 4 nonce ", nonce); break; - } - case 5: // Alice from Charlie 1 - if (htobe64 (((uint64_t)nonce << 32) | nonce) == m_SourceConnID) - { - if (m_Address) - { - SetRouterStatus (eRouterStatusOK); - SendPeerTest (6, buf + offset, len - offset, m_Address->i); - } - else - // we received msg 5 before msg 4 - m_State = eSSU2SessionStatePeerTestReceived; - } - else - LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", m_SourceConnID); - break; - case 6: // Charlie from Alice - if (m_Address) - SendPeerTest (7, buf + offset, len - offset, m_Address->i); - else - LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); - m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce)); - break; - case 7: // Alice from Charlie 2 - 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 - 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)GetLastActivityTimestamp ()).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; @@ -2153,49 +2555,79 @@ namespace transport { if (m_Address) return i2p::context.GetRouterInfo ().GetSSU2Address (m_Address->IsV4 ()); + else if (!m_RemoteEndpoint.address ().is_unspecified ()) + return i2p::context.GetRouterInfo ().GetSSU2Address (m_RemoteEndpoint.address ().is_v4 ()); return nullptr; - } + } - void SSU2Session::AdjustMaxPayloadSize () + void SSU2Session::AdjustMaxPayloadSize (size_t maxMtu) { auto addr = FindLocalAddress (); - if (addr && addr->ssu) - { + if (addr && addr->ssu) + { int mtu = addr->ssu->mtu; if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE; - if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu)) + if (mtu > (int)maxMtu) mtu = maxMtu; + if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu)) mtu = m_Address->ssu->mtu; if (mtu) - { + { + if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; + if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32; LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize); - } - } - } - + } + } + } + RouterStatus SSU2Session::GetRouterStatus () const { if (m_Address) { - if (m_Address->IsV4 ()) + if (m_Address->IsV4 ()) return i2p::context.GetStatus (); - if (m_Address->IsV6 ()) + if (m_Address->IsV6 ()) return i2p::context.GetStatusV6 (); - } + } return eRouterStatusUnknown; - } - + } + void SSU2Session::SetRouterStatus (RouterStatus status) const { if (m_Address) - { + { if (m_Address->IsV4 ()) - i2p::context.SetStatusSSU2 (status); + i2p::context.SetStatus (status); else if (m_Address->IsV6 ()) - i2p::context.SetStatusV6SSU2 (status); - } - } - + i2p::context.SetStatusV6 (status); + } + } + + bool SSU2Session::GetTestingState () const + { + if (m_Address) + { + if (m_Address->IsV4 ()) + return i2p::context.GetTesting (); + if (m_Address->IsV6 ()) + return i2p::context.GetTestingV6 (); + } + return false; + } + + void SSU2Session::SetTestingState (bool testing) const + { + if (m_Address) + { + if (m_Address->IsV4 ()) + i2p::context.SetTesting (testing); + else if (m_Address->IsV6 ()) + i2p::context.SetTestingV6 (testing); + } + if (!testing) + m_Server.AdjustTimeOffset (0, nullptr); // reset time offset when testing is over + } + size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) { if (len < 9) return 0; @@ -2208,140 +2640,161 @@ namespace transport size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r) { - if (!r || !r->GetBuffer () || len < 5) return 0; + if (!r || len < 5) return 0; + return CreateRouterInfoBlock (buf, len, r->GetSharedBuffer ()); + } + + size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr riBuffer) + { + if (!riBuffer || len < 5) return 0; buf[0] = eSSU2BlkRouterInfo; - size_t size = r->GetBufferLen (); + size_t size = riBuffer->GetBufferLen (); if (size + 5 < len) { - memcpy (buf + 5, r->GetBuffer (), size); + memcpy (buf + 5, riBuffer->data (), size); buf[3] = 0; // flag } else { i2p::data::GzipDeflator deflator; deflator.SetCompressionLevel (9); - size = deflator.Deflate (r->GetBuffer (), r->GetBufferLen (), buf + 5, len - 5); + size = deflator.Deflate (riBuffer->data (), riBuffer->GetBufferLen (), buf + 5, len - 5); if (!size) return 0; // doesn't fit buf[3] = SSU2_ROUTER_INFO_FLAG_GZIP; // flag } htobe16buf (buf + 1, size + 2); // size buf[4] = 1; // frag return size + 5; - } - + } + + size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len) { if (len < 8) return 0; - int maxNumRanges = (len - 8) >> 1; - if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; buf[0] = eSSU2BlkAck; uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); htobe32buf (buf + 3, ackThrough); // Ack Through uint16_t acnt = 0; - int numRanges = 0; if (ackThrough) { if (m_OutOfSequencePackets.empty ()) - acnt = std::min ((int)ackThrough, 255); // no gaps + { + acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps + m_NumRanges = 0; + } 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) - { - auto d = std::div (acnt - 255, 255); - acnt = 255; - if (d.quot > maxNumRanges) - { - d.quot = maxNumRanges; - d.rem = 0; - } - // Acks only ragnes for acnt - for (int i = 0; i < d.quot; i++) + if (!m_NumRanges) + { + int maxNumRanges = (len - 8) >> 1; + if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; + int numRanges = 0; + uint32_t lastNum = ackThrough - acnt; + if (acnt > SSU2_MAX_NUM_ACNT) { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = 255; // NACKs 0, Acks 255 - numRanges++; - } - if (d.rem > 0) - { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem; - numRanges++; - } - } - while (it != m_OutOfSequencePackets.rend () && numRanges < maxNumRanges) - { - if (lastNum - (*it) > 255) - { - // NACKs only ranges - if (lastNum > (*it) + 255*(maxNumRanges - numRanges)) break; // too many NACKs - while (lastNum - (*it) > 255) + auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); + acnt = SSU2_MAX_NUM_ACNT; + if (d.quot > maxNumRanges) { - buf[8 + numRanges*2] = 255; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 - lastNum -= 255; + d.quot = maxNumRanges; + d.rem = 0; + } + // Acks only ranges for acnt + for (int i = 0; i < d.quot; i++) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 + numRanges++; + } + if (d.rem > 0) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = d.rem; numRanges++; } - } - // NACKs and Acks ranges - buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs - lastNum = *it; it++; - int numAcks = 1; - while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) - { - numAcks++; lastNum--; - it++; } - while (numAcks > 255) + int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; + while (it != m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - // Acks only ranges - buf[8 + numRanges*2 + 1] = 255; // Acks 255 - numAcks -= 255; + if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + // NACKs only ranges + if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + m_Ranges[numRanges*2] = SSU2_MAX_NUM_ACNT; m_Ranges[numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + } + } + // NACKs and Acks ranges + m_Ranges[numRanges*2] = lastNum - (*it) - 1; // NACKs + numPackets += m_Ranges[numRanges*2]; + lastNum = *it; it++; + int numAcks = 1; + while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + while (numAcks > SSU2_MAX_NUM_ACNT) + { + // Acks only ranges + m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 + numAcks -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = 0; // NACKs 0 + if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; + } + if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numPackets += numAcks; numRanges++; - buf[8 + numRanges*2] = 0; // NACKs 0 - if (numRanges >= maxNumRanges) break; - } - if (numAcks > 255) numAcks = 255; - buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks - numRanges++; - } - if (numRanges < maxNumRanges && it == m_OutOfSequencePackets.rend ()) - { - // add range between out-of-seqence and received - int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; - if (nacks > 0) + } + if (it == m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - if (nacks > 255) nacks = 255; - buf[8 + numRanges*2] = nacks; - buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, 255); - numRanges++; - } + // add range between out-of-sequence and received + int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; + if (nacks > 0) + { + if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = nacks; + m_Ranges[numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + numRanges++; + } + } + m_NumRanges = numRanges; } + if (m_NumRanges) + memcpy (buf + 8, m_Ranges, m_NumRanges*2); } } buf[7] = (uint8_t)acnt; // acnt - htobe16buf (buf + 1, 5 + numRanges*2); - return 8 + numRanges*2; + htobe16buf (buf + 1, 5 + m_NumRanges*2); + return 8 + m_NumRanges*2; } size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) { - if (len < minSize) return 0; - uint8_t paddingSize = rand () & 0x0F; // 0 - 15 - if (paddingSize > len) paddingSize = len; - else if (paddingSize < minSize) paddingSize = minSize; - if (paddingSize) - { - buf[0] = eSSU2BlkPadding; - htobe16buf (buf + 1, paddingSize); - memset (buf + 3, 0, paddingSize); - } - else - return 0; + if (len < 3 || len < minSize) return 0; + size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 + if (paddingSize + 3 > len) paddingSize = len - 3; + else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; + buf[0] = eSSU2BlkPadding; + htobe16buf (buf + 1, paddingSize); + memset (buf + 3, 0, paddingSize); return paddingSize + 3; } @@ -2405,7 +2858,7 @@ namespace transport return payloadSize + 3; } - size_t SSU2Session::CreateRelayResponseBlock (uint8_t * buf, size_t len, + size_t SSU2Session::CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4) { buf[0] = eSSU2BlkRelayResponse; @@ -2416,20 +2869,20 @@ namespace transport buf[13] = 2; // ver size_t csz = 0; if (code == eSSU2RelayResponseCodeAccept) - { + { auto addr = i2p::context.GetRouterInfo ().GetSSU2Address (v4); if (!addr) { LogPrint (eLogError, "SSU2: Can't find local address for RelayResponse"); return 0; - } + } csz = CreateEndpoint (buf + 15, len - 15, boost::asio::ip::udp::endpoint (addr->host, addr->port)); - if (!csz) - { + if (!csz) + { LogPrint (eLogError, "SSU2: Can't create local endpoint for RelayResponse"); return 0; - } - } + } + } buf[14] = csz; // csz // signature size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); @@ -2437,8 +2890,8 @@ namespace transport { LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); return 0; - } - SignedData s; + } + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash @@ -2453,14 +2906,14 @@ namespace transport { LogPrint (eLogError, "SSU2: Buffer for RelayResponse token is too small ", len); return 0; - } + } memcpy (buf + 3 + payloadSize, &token, 8); payloadSize += 8; - } + } htobe16buf (buf + 1, payloadSize); // size return payloadSize + 3; } - + size_t SSU2Session::CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen) { @@ -2474,23 +2927,23 @@ namespace transport buf[5] = 0; //flag size_t offset = 6; if (routerHash) - { + { memcpy (buf + offset, routerHash, 32); // router hash offset += 32; - } + } memcpy (buf + offset, signedData, signedDataLen); return payloadSize + 3; } size_t SSU2Session::CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce) { - auto localAddress = FindLocalAddress (); + auto localAddress = FindLocalAddress (); if (!localAddress || !localAddress->port || localAddress->host.is_unspecified () || - localAddress->host.is_v4 () != m_RemoteEndpoint.address ().is_v4 ()) + localAddress->host.is_v4 () != m_RemoteEndpoint.address ().is_v4 ()) { LogPrint (eLogWarning, "SSU2: Can't find local address for peer test"); return 0; - } + } // signed data auto ts = i2p::util::GetSecondsSinceEpoch (); uint8_t signedData[96]; @@ -2500,24 +2953,24 @@ namespace transport size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); signedData[9] = asz; // signature - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash - s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint + s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint s.Sign (i2p::context.GetPrivateKeys (), signedData + 10 + asz); - return CreatePeerTestBlock (buf, len, 1, eSSU2PeerTestCodeAccept, nullptr, + return CreatePeerTestBlock (buf, len, 1, eSSU2PeerTestCodeAccept, nullptr, signedData, 10 + asz + i2p::context.GetIdentity ()->GetSignatureLen ()); - } + } size_t SSU2Session::CreateTerminationBlock (uint8_t * buf, size_t len) - { + { buf[0] = eSSU2BlkTermination; htobe16buf (buf + 1, 9); htobe64buf (buf + 3, m_ReceivePacketNum); buf[11] = (uint8_t)m_TerminationReason; return 12; } - + std::shared_ptr SSU2Session::ExtractRouterInfo (const uint8_t * buf, size_t size) { if (size < 2) return nullptr; @@ -2528,48 +2981,69 @@ namespace transport i2p::data::GzipInflator inflator; uint8_t uncompressed[i2p::data::MAX_RI_BUFFER_SIZE]; size_t uncompressedSize = inflator.Inflate (buf + 2, size - 2, uncompressed, i2p::data::MAX_RI_BUFFER_SIZE); - if (uncompressedSize && uncompressedSize < i2p::data::MAX_RI_BUFFER_SIZE) + if (uncompressedSize && uncompressedSize <= i2p::data::MAX_RI_BUFFER_SIZE) ri = std::make_shared(uncompressed, uncompressedSize); else LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize); } - else + else if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 2) ri = std::make_shared(buf + 2, size - 2); + else + LogPrint (eLogInfo, "SSU2: RouterInfo is too long ", size); return ri; } - void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); - } - bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) { if (packetNum <= m_ReceivePacketNum) return false; // duplicate 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_NumRanges = 0; // recalculate ranges when create next Ack } m_ReceivePacketNum = packetNum; } else + { + if (m_NumRanges && (m_OutOfSequencePackets.empty () || + packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1)) + m_NumRanges = 0; // reset ranges if received packet is not next m_OutOfSequencePackets.insert (packetNum); + } return true; } 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); } @@ -2584,18 +3058,69 @@ namespace transport void SSU2Session::SendPathResponse (const uint8_t * data, size_t len) { - if (len < 8 || len > m_MaxPayloadSize - 3) + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = 0; + // datetime block + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + payloadSize += 7; + // address block + payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint); + // path response + if (payloadSize + len > m_MaxPayloadSize) { LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len); return; } - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - payload[0] = eSSU2BlkPathResponse; - htobe16buf (payload + 1, len); - memcpy (payload + 3, data, len); - SendData (payload, len + 3); - } + payload[payloadSize] = eSSU2BlkPathResponse; + htobe16buf (payload + payloadSize + 1, len); + memcpy (payload + payloadSize + 3, data, len); + payloadSize += len + 3; + // ack block + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // padding + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + SendData (payload, payloadSize); + } + + void SSU2Session::SendPathChallenge (const boost::asio::ip::udp::endpoint& to) + { + AdjustMaxPayloadSize (SSU2_MIN_PACKET_SIZE); // reduce to minimum + m_WindowSize = SSU2_MIN_WINDOW_SIZE; // reduce window to minimum + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = 0; + // datetime block + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + payloadSize += 7; + // address block with new address + payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, to); + // path challenge block + payload[payloadSize] = eSSU2BlkPathChallenge; + uint64_t challenge; + RAND_bytes ((uint8_t *)&challenge, 8); + htobe16buf (payload + payloadSize + 1, 8); // always 8 bytes + htobuf64 (payload + payloadSize + 3, challenge); + payloadSize += 11; + m_PathChallenge = std::make_unique >(challenge, to); + // ack block + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // padding block + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // send to new endpoint + auto existing = m_RemoteEndpoint; + m_RemoteEndpoint = to; // send path challenge to new endpoint + SendData (payload, payloadSize); + m_RemoteEndpoint = existing; // restore endpoint back until path response received + } + void SSU2Session::CleanUp (uint64_t ts) { for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) @@ -2608,10 +3133,26 @@ namespace transport else ++it; } + if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > GetLastActivityTimestamp () + 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) @@ -2620,9 +3161,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); + { + LogPrint (eLogError, "SSU2: Out of sequence packet ", packet, " is less than last received ", m_ReceivePacketNum); + break; + } } if (m_OutOfSequencePackets.size () > 255*4) { @@ -2630,40 +3175,40 @@ namespace transport m_ReceivePacketNum = *m_OutOfSequencePackets.rbegin (); m_OutOfSequencePackets.clear (); } - } - + } + for (auto it = m_RelaySessions.begin (); it != m_RelaySessions.end ();) { if (ts > it->second.second + SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT) { - LogPrint (eLogWarning, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted"); + LogPrint (eLogInfo, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted"); it = m_RelaySessions.erase (it); } else ++it; } - for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) - { - if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) - { - LogPrint (eLogWarning, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds, deleted"); - it = m_PeerTests.erase (it); - } - else - ++it; - } + if (m_PathChallenge) + RequestTermination (eSSU2TerminationReasonNormalClose); } void SSU2Session::FlushData () { bool sent = SendQueue (); // if we have something to send + if (sent) + SetSendQueueSize (m_SendQueue.size ()); if (m_IsDataReceived) { if (!sent) SendQuickAck (); m_Handler.Flush (); m_IsDataReceived = false; } + else if (!sent && !m_SentPackets.empty ()) // if only acks received, nothing sent and we still have something to resend + Resend (i2p::util::GetMillisecondsSinceEpoch ()); // than right time to resend } + i2p::data::RouterInfo::SupportedTransports SSU2Session::GetTransportType () const + { + return m_RemoteEndpoint.address ().is_v4 () ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6; + } } } diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index 1730a939..ee26255f 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ #include #include #include +#include "version.h" #include "Crypto.h" #include "RouterInfo.h" #include "RouterContext.h" @@ -25,7 +26,7 @@ namespace i2p namespace transport { const int SSU2_CONNECT_TIMEOUT = 5; // 5 seconds - const int SSU2_TERMINATION_TIMEOUT = 330; // 5.5 minutes + const int SSU2_TERMINATION_TIMEOUT = 165; // in seconds const int SSU2_CLOCK_SKEW = 60; // in seconds const int SSU2_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust const int SSU2_TOKEN_EXPIRATION_TIMEOUT = 9; // for Retry message, in seconds @@ -35,18 +36,30 @@ namespace transport const int SSU2_PEER_TEST_EXPIRATION_TIMEOUT = 60; // 60 seconds const size_t SSU2_MAX_PACKET_SIZE = 1500; const size_t SSU2_MIN_PACKET_SIZE = 1280; - const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in millseconds - const int SSU2_RESEND_INTERVAL = 300; // in milliseconds + const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds const int SSU2_MAX_NUM_RESENDS = 5; + const int SSU2_RESEND_ATTEMPT_MIN_INTERVAL = 3; // in milliseconds 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_INITIAL_RTO = 540; // in milliseconds const size_t SSU2_MAX_RTO = 2500; // in milliseconds + const double SSU2_UNKNOWN_RTT = -1; + const double SSU2_RTT_EWMA_ALPHA = 0.125; 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 = 511; // ackthrough + acnt + 1 range 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 = 256; + const int SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION = MAKE_VERSION_NUMBER(0, 9, 64); // 0.9.64 + + // flags + const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01; enum SSU2MessageType { @@ -97,11 +110,13 @@ namespace transport eSSU2SessionStateSessionConfirmedSent, eSSU2SessionStateEstablished, eSSU2SessionStateClosing, + eSSU2SessionStateClosingConfirmed, eSSU2SessionStateTerminated, eSSU2SessionStateFailed, eSSU2SessionStateIntroduced, + eSSU2SessionStateHolePunch, eSSU2SessionStatePeerTest, - eSSU2SessionStatePeerTestReceived // 5 before 4 + eSSU2SessionStateTokenRequestReceived }; enum SSU2PeerTestCode @@ -119,7 +134,7 @@ namespace transport eSSU2PeerTestCodeCharlieAliceIsBanned = 69, eSSU2PeerTestCodeCharlieAliceIsUnknown = 70, eSSU2PeerTestCodeUnspecified = 128 - }; + }; enum SSU2RelayResponseCode { @@ -128,7 +143,7 @@ namespace transport eSSU2RelayResponseCodeCharlieUnsupportedAddress = 65, eSSU2RelayResponseCodeCharlieSignatureFailure = 67, eSSU2RelayResponseCodeCharlieAliceIsUnknown = 70 - }; + }; enum SSU2TerminationReason { @@ -155,23 +170,27 @@ namespace transport eSSU2TerminationReasonIncompatibleVersion = 20, eSSU2TerminationReasonWrongNetID = 21, eSSU2TerminationReasonReplacedByNewSession = 22 - }; - + }; + struct SSU2IncompleteMessage { struct Fragment { 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 @@ -181,7 +200,7 @@ namespace transport uint64_t sendTime; // in milliseconds int numResends = 0; }; - + // RouterInfo flags const uint8_t SSU2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; const uint8_t SSU2_ROUTER_INFO_FLAG_GZIP = 0x02; @@ -189,45 +208,50 @@ namespace transport class SSU2Server; class SSU2Session: public TransportSession, public std::enable_shared_from_this { - union Header - { - uint64_t ll[2]; - uint8_t buf[16]; - struct + protected: + + union Header { - uint64_t connID; - uint32_t packetNum; - uint8_t type; - uint8_t flags[3]; - } h; - }; + uint64_t ll[2]; + uint8_t buf[16]; + struct + { + uint64_t connID; + uint32_t packetNum; + uint8_t type; + uint8_t flags[3]; + } h; + }; - struct HandshakePacket - { - Header header; - uint8_t headerX[48]; // part1 for SessionConfirmed - uint8_t payload[SSU2_MAX_PACKET_SIZE*2]; - size_t payloadSize = 0; - uint64_t sendTime = 0; // in milliseconds - bool isSecondFragment = false; // for SessionConfirmed - }; + private: + + struct HandshakePacket + { + Header header; + uint8_t headerX[48]; // part1 for SessionConfirmed + uint8_t payload[SSU2_MAX_PACKET_SIZE*2]; + size_t payloadSize = 0; + uint64_t sendTime = 0; // in milliseconds + bool isSecondFragment = false; // for SessionConfirmed + }; - typedef std::function OnEstablished; + typedef std::function OnEstablished; public: SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter = nullptr, - std::shared_ptr addr = nullptr); - ~SSU2Session (); + std::shared_ptr addr = nullptr, bool noise = true); + virtual ~SSU2Session (); void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; }; const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; }; i2p::data::RouterInfo::CompatibleTransports GetRemoteTransports () const { return m_RemoteTransports; }; + i2p::data::RouterInfo::CompatibleTransports GetRemotePeerTestTransports () const { return m_RemotePeerTestTransports; }; std::shared_ptr GetAddress () const { return m_Address; }; void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; }; OnEstablished GetOnEstablished () const { return m_OnEstablished; }; - void Connect (); + virtual void Connect (); bool Introduce (std::shared_ptr session, uint32_t relayTag); void WaitForIntroduction (); void SendPeerTest (); // Alice, Data message @@ -237,34 +261,59 @@ namespace transport void FlushData (); void Done () override; void SendLocalRouterInfo (bool update) override; - void SendI2NPMessages (const std::vector >& msgs) override; + void SendI2NPMessages (std::list >& msgs) override; + void MoveSendQueue (std::shared_ptr other); uint32_t GetRelayTag () const override { return m_RelayTag; }; - void Resend (uint64_t ts); - bool IsEstablished () const { return m_State == eSSU2SessionStateEstablished; }; + size_t Resend (uint64_t ts); // return number of resent packets + uint64_t GetLastResendTime () const { return m_LastResendTime; }; + bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; }; + i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; uint64_t GetConnID () const { return m_SourceConnID; }; SSU2SessionState GetState () const { return m_State; }; void SetState (SSU2SessionState state) { m_State = state; }; - bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len); + virtual bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len); bool ProcessSessionCreated (uint8_t * buf, size_t len); bool ProcessSessionConfirmed (uint8_t * buf, size_t len); bool ProcessRetry (uint8_t * buf, size_t len); bool ProcessHolePunch (uint8_t * buf, size_t len); - bool ProcessPeerTest (uint8_t * buf, size_t len); - void ProcessData (uint8_t * buf, size_t len); + virtual bool ProcessPeerTest (uint8_t * buf, size_t len); + void ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + protected: + + SSU2Server& GetServer () { return m_Server; } + RouterStatus GetRouterStatus () const; + void SetRouterStatus (RouterStatus status) const; + size_t GetMaxPayloadSize () const { return m_MaxPayloadSize; } + void SetIsDataReceived (bool dataReceived) { m_IsDataReceived = dataReceived; }; + + uint64_t GetSourceConnID () const { return m_SourceConnID; } + void SetSourceConnID (uint64_t sourceConnID) { m_SourceConnID = sourceConnID; } + uint64_t GetDestConnID () const { return m_DestConnID; } + void SetDestConnID (uint64_t destConnID) { m_DestConnID = destConnID; } + + void SetAddress (std::shared_ptr addr) { m_Address = addr; } + void HandlePayload (const uint8_t * buf, size_t len); + + size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); + size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + + bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep); + private: void Terminate (); void Established (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); - void PostI2NPMessages (std::vector > msgs); + void PostI2NPMessages (); bool SendQueue (); // returns true if ack block was sent bool SendFragmentedMessage (std::shared_ptr msg); void ResendHandshakePacket (); void ConnectAfterIntroduction (); - + void ProcessSessionRequest (Header& header, uint8_t * buf, size_t len); void ProcessTokenRequest (Header& header, uint8_t * buf, size_t len); @@ -274,48 +323,44 @@ namespace transport void KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba); void SendTokenRequest (); void SendRetry (); - uint32_t SendData (const uint8_t * buf, size_t len); // returns packet num + uint32_t SendData (const uint8_t * buf, size_t len, uint8_t flags = 0); // returns packet num void SendQuickAck (); void SendTermination (); - void SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey, uint64_t token); - void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message void SendPathResponse (const uint8_t * data, size_t len); - - void HandlePayload (const uint8_t * buf, size_t len); + void SendPathChallenge (const boost::asio::ip::udp::endpoint& to); + void HandleDateTime (const uint8_t * buf, size_t len); + void HandleRouterInfo (const uint8_t * buf, size_t len); void HandleAck (const uint8_t * buf, size_t len); void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts); - void HandleAddress (const uint8_t * buf, size_t len); - bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep); + virtual void HandleAddress (const uint8_t * buf, size_t len); size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); std::shared_ptr FindLocalAddress () const; - void AdjustMaxPayloadSize (); - RouterStatus GetRouterStatus () const; - void SetRouterStatus (RouterStatus status) const; + void AdjustMaxPayloadSize (size_t maxMtu = SSU2_MAX_PACKET_SIZE); + bool GetTestingState () const; + void SetTestingState(bool testing) const; std::shared_ptr ExtractRouterInfo (const uint8_t * buf, size_t size); - void CreateNonce (uint64_t seqn, uint8_t * nonce); bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate void HandleFirstFragment (const uint8_t * buf, size_t len); void HandleFollowOnFragment (const uint8_t * buf, size_t len); - 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); + virtual void HandlePeerTest (const uint8_t * buf, size_t len); + void HandleI2NPMsg (std::shared_ptr&& msg); - size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r); + size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr riBuffer); size_t CreateAckBlock (uint8_t * buf, size_t len); - size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg); size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg); size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID); size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen); - size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4); - size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice size_t CreateTerminationBlock (uint8_t * buf, size_t len); - + private: SSU2Server& m_Server; @@ -325,33 +370,49 @@ namespace transport std::unique_ptr m_SentHandshakePacket; // SessionRequest, SessionCreated or SessionConfirmed std::shared_ptr m_Address; boost::asio::ip::udp::endpoint m_RemoteEndpoint; - i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports; // for peer tests + i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports, m_RemotePeerTestTransports; + int m_RemoteVersion; 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::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice - std::map, uint64_t > > m_PeerTests; // same as for relay sessions + std::unordered_map > m_IncompleteMessages; // msgID -> I2NP + std::unordered_map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice std::list > m_SendQueue; i2p::I2NPMessagesHandler m_Handler; + std::list > m_IntermediateQueue; // from transports + mutable std::mutex m_IntermediateQueueMutex; bool m_IsDataReceived; - size_t m_WindowSize, m_RTT, m_RTO; + double m_RTT; + int m_MsgLocalExpirationTimeout; + int m_MsgLocalSemiExpirationTimeout; + size_t m_WindowSize, m_RTO; uint32_t m_RelayTag; // between Bob and Charlie OnEstablished m_OnEstablished; // callback from Established boost::asio::deadline_timer m_ConnectTimer; SSU2TerminationReason m_TerminationReason; size_t m_MaxPayloadSize; + std::unique_ptr > m_PathChallenge; + std::unordered_map m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds + uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds + int m_NumRanges; + uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any }; - + inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) { uint64_t data = 0; i2p::crypto::ChaCha20 ((uint8_t *)&data, 8, kh, nonce, (uint8_t *)&data); return data; } + + inline void CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } } } diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp deleted file mode 100644 index 6365381e..00000000 --- a/libi2pd/SSUData.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#include -#include "Log.h" -#include "Timestamp.h" -#include "NetDb.hpp" -#include "SSU.h" -#include "SSUData.h" - -namespace i2p -{ -namespace transport -{ - void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) - { - if (msg->len + fragmentSize > msg->maxLen) - { - LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); - *newMsg = *msg; - msg = newMsg; - } - if (msg->Concat (fragment, fragmentSize) < fragmentSize) - LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); - nextFragmentNum++; - } - - SSUData::SSUData (SSUSession& session): - m_Session (session), m_ResendTimer (session.GetService ()), - m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), - m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) - { - } - - SSUData::~SSUData () - { - } - - void SSUData::Start () - { - } - - void SSUData::Stop () - { - m_ResendTimer.cancel (); - m_IncompleteMessages.clear (); - m_SentMessages.clear (); - m_ReceivedMessages.clear (); - } - - void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) - { - if (!remoteRouter) return; - auto ssuAddress = remoteRouter->GetSSUAddress (); - if (ssuAddress && ssuAddress->ssu->mtu) - { - if (m_Session.IsV6 ()) - m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; - else - m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; - if (m_PacketSize > 0) - { - // make sure packet size multiple of 16 - m_PacketSize >>= 4; - m_PacketSize <<= 4; - if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; - LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize); - } - else - { - LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu); - m_PacketSize = m_MaxPacketSize; - } - } - } - - void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent) - { - auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); - if (routerInfo) - AdjustPacketSize (routerInfo); - } - - void SSUData::ProcessSentMessageAck (uint32_t msgID) - { - auto it = m_SentMessages.find (msgID); - if (it != m_SentMessages.end ()) - { - m_SentMessages.erase (it); - if (m_SentMessages.empty ()) - m_ResendTimer.cancel (); - } - } - - void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag) - { - if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) - { - // explicit ACKs - uint8_t numAcks =*buf; - buf++; - for (int i = 0; i < numAcks; i++) - ProcessSentMessageAck (bufbe32toh (buf+i*4)); - buf += numAcks*4; - } - if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) - { - // explicit ACK bitfields - uint8_t numBitfields =*buf; - buf++; - for (int i = 0; i < numBitfields; i++) - { - uint32_t msgID = bufbe32toh (buf); - buf += 4; // msgID - auto it = m_SentMessages.find (msgID); - // process individual Ack bitfields - bool isNonLast = false; - int fragment = 0; - do - { - uint8_t bitfield = *buf; - isNonLast = bitfield & 0x80; - bitfield &= 0x7F; // clear MSB - if (bitfield && it != m_SentMessages.end ()) - { - int numSentFragments = it->second->fragments.size (); - // process bits - uint8_t mask = 0x01; - for (int j = 0; j < 7; j++) - { - if (bitfield & mask) - { - if (fragment < numSentFragments) - it->second->fragments[fragment] = nullptr; - } - fragment++; - mask <<= 1; - } - } - buf++; - } - while (isNonLast); - } - } - } - - void SSUData::ProcessFragments (uint8_t * buf) - { - uint8_t numFragments = *buf; // number of fragments - buf++; - for (int i = 0; i < numFragments; i++) - { - uint32_t msgID = bufbe32toh (buf); // message ID - buf += 4; - uint8_t frag[4] = {0}; - memcpy (frag + 1, buf, 3); - buf += 3; - uint32_t fragmentInfo = bufbe32toh (frag); // fragment info - uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 - bool isLast = fragmentInfo & 0x010000; // bit 16 - uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 - if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) - { - LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); - return; - } - - // find message with msgID - auto it = m_IncompleteMessages.find (msgID); - if (it == m_IncompleteMessages.end ()) - { - // create new message - auto msg = NewI2NPShortMessage (); - msg->len -= I2NP_SHORT_HEADER_SIZE; - it = m_IncompleteMessages.insert (std::make_pair (msgID, - m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (std::move (msg)))).first; - } - auto& incompleteMessage = it->second; - // mark fragment as received - if (fragmentNum < 64) - incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum); - else - LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); - - // handle current fragment - if (fragmentNum == incompleteMessage->nextFragmentNum) - { - // expected fragment - incompleteMessage->AttachNextFragment (buf, fragmentSize); - if (!isLast && !incompleteMessage->savedFragments.empty ()) - { - // try saved fragments - for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) - { - auto& savedFragment = *it1; - if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) - { - incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); - isLast = savedFragment->isLast; - incompleteMessage->savedFragments.erase (it1++); - } - else - break; - } - if (isLast) - LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); - } - } - else - { - if (fragmentNum < incompleteMessage->nextFragmentNum) - // duplicate fragment - LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); - else - { - // missing fragment - LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); - auto savedFragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (savedFragment).second) - incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - else - LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); - } - isLast = false; - } - - if (isLast) - { - // delete incomplete message - auto msg = incompleteMessage->msg; - incompleteMessage->msg = nullptr; - m_IncompleteMessages.erase (msgID); - // process message - SendMsgAck (msgID); - msg->FromSSU (msgID); - if (m_Session.GetState () == eSessionStateEstablished) - { - if (!m_ReceivedMessages.count (msgID)) - { - m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); - m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime); - if (!msg->IsExpired ()) - { - m_Handler.PutNextMessage (std::move (msg)); - } - else - LogPrint (eLogDebug, "SSU: message expired"); - } - else - LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); - } - else - { - // we expect DeliveryStatus - if (msg->GetTypeID () == eI2NPDeliveryStatus) - { - LogPrint (eLogDebug, "SSU: session established"); - m_Session.Established (); - } - else - LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); - } - } - else - SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits); - buf += fragmentSize; - } - } - - void SSUData::FlushReceivedMessage () - { - m_Handler.Flush (); - } - - void SSUData::ProcessMessage (uint8_t * buf, size_t len) - { - //uint8_t * start = buf; - uint8_t flag = *buf; - buf++; - LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); - // process acks if presented - if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) - ProcessAcks (buf, flag); - // extended data if presented - if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) - { - uint8_t extendedDataSize = *buf; - buf++; // size - LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); - buf += extendedDataSize; - } - // process data - ProcessFragments (buf); - } - - void SSUData::Send (std::shared_ptr msg) - { - uint32_t msgID = msg->ToSSU (); - if (m_SentMessages.find (msgID) != m_SentMessages.end()) - { - LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); - return; - } - if (m_SentMessages.empty ()) // schedule resend at first message only - ScheduleResend (); - - auto ret = m_SentMessages.emplace (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ()); - auto& sentMessage = ret.first->second; - if (ret.second) - { - sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; - sentMessage->numResends = 0; - } - auto& fragments = sentMessage->fragments; - size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) - size_t len = msg->GetLength (); - uint8_t * msgBuf = msg->GetSSUHeader (); - - uint32_t fragmentNum = 0; - while (len > 0 && fragmentNum <= 127) - { - auto fragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (); - fragment->fragmentNum = fragmentNum; - uint8_t * payload = fragment->buf + sizeof (SSUHeader); - *payload = DATA_FLAG_WANT_REPLY; // for compatibility - payload++; - *payload = 1; // always 1 message fragment per message - payload++; - htobe32buf (payload, msgID); - payload += 4; - bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max - size_t size = isLast ? len : payloadSize; - uint32_t fragmentInfo = (fragmentNum << 17); - if (isLast) - fragmentInfo |= 0x010000; - - fragmentInfo |= size; - fragmentInfo = htobe32 (fragmentInfo); - memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3); - payload += 3; - memcpy (payload, msgBuf, size); - - size += payload - fragment->buf; - uint8_t rem = size & 0x0F; - if (rem) // make sure 16 bytes boundary - { - auto padding = 16 - rem; - memset (fragment->buf + size, 0, padding); - size += padding; - } - fragment->len = size; - fragments.push_back (fragment); - - // encrypt message with session key - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf); - try - { - m_Session.Send (buf, size); - } - catch (boost::system::system_error& ec) - { - LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); - } - if (!isLast) - { - len -= payloadSize; - msgBuf += payloadSize; - } - else - len = 0; - fragmentNum++; - } - } - - void SSUData::SendMsgAck (uint32_t msgID) - { - uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16 - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag - payload++; - *payload = 1; // number of ACKs - payload++; - htobe32buf (payload, msgID); // msgID - payload += 4; - *payload = 0; // number of fragments - - // encrypt message with session key - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); - m_Session.Send (buf, 48); - } - - void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits) - { - if (!bits) return; - uint8_t buf[64 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag - payload++; - *payload = 1; // number of ACK bitfields - payload++; - // one ack - *(uint32_t *)(payload) = htobe32 (msgID); // msgID - payload += 4; - size_t len = 0; - while (bits) - { - *payload = (bits & 0x7F); // next 7 bits - bits >>= 7; - if (bits) *payload &= 0x80; // 0x80 means non-last - payload++; len++; - } - *payload = 0; // number of fragments - len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4 - // encrypt message with session key - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); - m_Session.Send (buf, len); - } - - void SSUData::ScheduleResend() - { - m_ResendTimer.cancel (); - m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL)); - auto s = m_Session.shared_from_this(); - m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleResendTimer (ecode); }); - } - - void SSUData::HandleResendTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - int numResent = 0; - for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) - { - if (ts >= it->second->nextResendTime) - { - if (it->second->numResends < MAX_NUM_RESENDS) - { - for (auto& f: it->second->fragments) - if (f) - { - try - { - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf); - m_Session.Send (buf, f->len); // resend - numResent++; - } - catch (boost::system::system_error& ec) - { - LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ()); - } - } - - it->second->numResends++; - it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; - ++it; - } - else - { - LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); - it = m_SentMessages.erase (it); - } - } - else - ++it; - } - if (m_SentMessages.empty ()) return; // nothing to resend - if (numResent < MAX_OUTGOING_WINDOW_SIZE) - ScheduleResend (); - else - { - LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); - m_Session.Close (); - } - } - } - - void SSUData::CleanUp (uint64_t ts) - { - for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) - { - if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) - { - LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); - it = m_IncompleteMessages.erase (it); - } - else - ++it; - } - - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) - // decay - m_ReceivedMessages.clear (); - else - { - // delete old received messages - for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) - { - if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) - it = m_ReceivedMessages.erase (it); - else - ++it; - } - } - } -} -} diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h deleted file mode 100644 index eba0fc28..00000000 --- a/libi2pd/SSUData.h +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#ifndef SSU_DATA_H__ -#define SSU_DATA_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "I2NPProtocol.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "TransportSession.h" - -namespace i2p -{ -namespace transport -{ - const size_t SSU_MTU_V4 = 1484; - const size_t SSU_MTU_V6 = 1488; - const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 - const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 - const int RESEND_INTERVAL = 3; // in seconds - const int MAX_NUM_RESENDS = 5; - const int DECAY_INTERVAL = 20; // in seconds - const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds - const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds - const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check - const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store - // data flags - const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; - const uint8_t DATA_FLAG_WANT_REPLY = 0x04; - const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; - const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; - const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; - const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; - - struct Fragment - { - int fragmentNum; - size_t len; - bool isLast; - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest - - Fragment () = default; - Fragment (int n, const uint8_t * b, int l, bool last): - fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; - }; - - struct FragmentCmp - { - bool operator() (const std::shared_ptr& f1, const std::shared_ptr& f2) const - { - return f1->fragmentNum < f2->fragmentNum; - }; - }; - - struct IncompleteMessage - { - std::shared_ptr msg; - int nextFragmentNum; - uint32_t lastFragmentInsertTime; // in seconds - uint64_t receivedFragmentsBits; - std::set, FragmentCmp> savedFragments; - - IncompleteMessage (std::shared_ptr&& m): msg (m), nextFragmentNum (0), - lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; - void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); - }; - - struct SentMessage - { - std::vector > fragments; - uint32_t nextResendTime; // in seconds - int numResends; - }; - - class SSUSession; - class SSUData - { - public: - - SSUData (SSUSession& session); - ~SSUData (); - - void Start (); - void Stop (); - void CleanUp (uint64_t ts); - - void ProcessMessage (uint8_t * buf, size_t len); - void FlushReceivedMessage (); - void Send (std::shared_ptr msg); - - void AdjustPacketSize (std::shared_ptr remoteRouter); - void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); - - private: - - void SendMsgAck (uint32_t msgID); - void SendFragmentAck (uint32_t msgID, uint64_t bits); - void ProcessAcks (uint8_t *& buf, uint8_t flag); - void ProcessFragments (uint8_t * buf); - void ProcessSentMessageAck (uint32_t msgID); - - void ScheduleResend (); - void HandleResendTimer (const boost::system::error_code& ecode); - - private: - - SSUSession& m_Session; - std::map > m_IncompleteMessages; - std::map > m_SentMessages; - std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds - boost::asio::deadline_timer m_ResendTimer; - int m_MaxPacketSize, m_PacketSize; - i2p::I2NPMessagesHandler m_Handler; - uint32_t m_LastMessageReceivedTime; // in second - }; -} -} - -#endif diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp deleted file mode 100644 index 76b6486b..00000000 --- a/libi2pd/SSUSession.cpp +++ /dev/null @@ -1,1318 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#include "version.h" -#include "Crypto.h" -#include "Log.h" -#include "Timestamp.h" -#include "RouterContext.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "SSU.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router, bool peerTest ): - TransportSession (router, SSU_TERMINATION_TIMEOUT), - m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), - m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), - m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) - { - if (router) - { - // we are client - auto address = IsV6 () ? router->GetSSUV6Address () : router->GetSSUAddress (true); - if (address) m_IntroKey = address->i; - m_Data.AdjustPacketSize (router); // mtu - } - else - { - // we are server - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (address) m_IntroKey = address->i; - } - } - - SSUSession::~SSUSession () - { - } - - boost::asio::io_service& SSUSession::GetService () - { - return m_Server.GetService (); - } - - void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) - { - uint8_t sharedKey[256]; - m_DHKeysPair->Agree (pubKey, sharedKey); - - uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; - if (sharedKey[0] & 0x80) - { - sessionKey[0] = 0; - memcpy (sessionKey + 1, sharedKey, 31); - memcpy (macKey, sharedKey + 31, 32); - } - else if (sharedKey[0]) - { - memcpy (sessionKey, sharedKey, 32); - memcpy (macKey, sharedKey + 32, 32); - } - else - { - // find first non-zero byte - uint8_t * nonZero = sharedKey + 1; - while (!*nonZero) - { - nonZero++; - if (nonZero - sharedKey > 32) - { - LogPrint (eLogWarning, "SSU: First 32 bytes of shared key is all zeros. Ignored"); - return; - } - } - - memcpy (sessionKey, nonZero, 32); - SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); - } - m_IsSessionKey = true; - m_SessionKeyEncryption.SetKey (m_SessionKey); - m_SessionKeyDecryption.SetKey (m_SessionKey); - } - - void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - m_NumReceivedBytes += len; - i2p::transport::transports.UpdateReceivedBytes (len); - if (m_State == eSessionStateIntroduced) - { - // HolePunch received - LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); - m_State = eSessionStateUnknown; - Connect (); - } - else - { - if (!len) return; // ignore zero-length packets - if (m_State == eSessionStateEstablished) - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - - if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first - DecryptSessionKey (buf, len); - else - { - if (m_State == eSessionStateEstablished) Reset (); // new session key required - // try intro key depending on side - if (Validate (buf, len, m_IntroKey)) - Decrypt (buf, len, m_IntroKey); - else - { - // try own intro key - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - if (Validate (buf, len, address->i)) - Decrypt (buf, len, address->i); - else - { - LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); - m_Server.DeleteSession (shared_from_this ()); - return; - } - } - } - // successfully decrypted - ProcessMessage (buf, len, senderEndpoint); - } - } - - size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const - { - size_t s = sizeof (SSUHeader); - if (((const SSUHeader *)buf)->IsExtendedOptions ()) - s += buf[s] + 1; // byte right after header is extended options length - return s; - } - - void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - len -= (len & 0x0F); // %16, delete extra padding - if (len <= sizeof (SSUHeader)) return; // drop empty message - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU: SSU header size ", headerSize, " exceeds packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - switch (header->GetPayloadType ()) - { - case PAYLOAD_TYPE_DATA: - ProcessData (buf + headerSize, len - headerSize); - break; - case PAYLOAD_TYPE_SESSION_REQUEST: - ProcessSessionRequest (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CREATED: - ProcessSessionCreated (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CONFIRMED: - ProcessSessionConfirmed (buf, len); // buf with header - break; - case PAYLOAD_TYPE_PEER_TEST: - LogPrint (eLogDebug, "SSU: Peer test received"); - ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_SESSION_DESTROYED: - { - LogPrint (eLogDebug, "SSU: Session destroy received"); - m_Server.DeleteSession (shared_from_this ()); - break; - } - case PAYLOAD_TYPE_RELAY_RESPONSE: - ProcessRelayResponse (buf + headerSize, len - headerSize); - if (m_State != eSessionStateEstablished) - m_Server.DeleteSession (shared_from_this ()); - break; - case PAYLOAD_TYPE_RELAY_REQUEST: - LogPrint (eLogDebug, "SSU: Relay request received"); - ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_RELAY_INTRO: - LogPrint (eLogDebug, "SSU: Relay intro received"); - ProcessRelayIntro (buf + headerSize, len - headerSize); - break; - default: - LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); - } - } - - void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: Session request"); - bool sendRelayTag = true; - auto headerSize = sizeof (SSUHeader); - if (((SSUHeader *)buf)->IsExtendedOptions ()) - { - uint8_t extendedOptionsLen = buf[headerSize]; - headerSize++; - if (extendedOptionsLen >= 2) // options are presented - { - uint16_t flags = bufbe16toh (buf + headerSize); - sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; - } - headerSize += extendedOptionsLen; - } - if (headerSize >= len) - { - LogPrint (eLogError, "SSU message: Session request header size ", headerSize, " exceeds packet length ", len); - return; - } - if (!m_DHKeysPair) - { - auto pair = std::make_shared (); - pair->GenerateKeys (); - m_DHKeysPair = pair; - } - CreateAESandMacKey (buf + headerSize); - SendSessionCreated (buf + headerSize, sendRelayTag); - } - - void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) - { - if (!IsOutgoing () || !m_DHKeysPair) - { - LogPrint (eLogWarning, "SSU: Unsolicited session created message"); - return; - } - - LogPrint (eLogDebug, "SSU message: session created"); - m_ConnectTimer.cancel (); // connect timer - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU message: Session created header size ", headerSize, " exceeds packet length ", len); - return; - } - uint8_t * payload = buf + headerSize; - uint8_t * y = payload; - CreateAESandMacKey (y); - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - payload += 256; - boost::asio::ip::address ourIP; - uint16_t ourPort = 0; - auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort); - if (!addressAndPortLen) return; - uint8_t * ourAddressAndPort = payload + 1; - payload += addressAndPortLen; - addressAndPortLen--; // -1 byte address size - s.Insert (ourAddressAndPort, addressAndPortLen); // address + port - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (payload, 8); // relayTag and signed on time - m_RelayTag = bufbe32toh (payload); - payload += 4; // relayTag - uint32_t signedOnTime = bufbe32toh(payload); - payload += 4; // signed on time - // decrypt signature - size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) signatureLen += (16 - paddingSize); - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); - m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload - // verify signature - if (s.Verify (m_RemoteIdentity, payload)) - { - if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - int offset = (int)ts - signedOnTime; - if (m_Server.IsSyncClockFromPeers ()) - { - if (std::abs (offset) > SSU_CLOCK_THRESHOLD) - { - LogPrint (eLogWarning, "SSU: Clock adjusted by ", -offset, " seconds"); - i2p::util::AdjustTimeOffset (-offset); - } - } - else if (std::abs (offset) > SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU: Clock skew detected ", offset, ". Check your clock"); - i2p::context.SetError (eRouterErrorClockSkew); - } - } - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - if (!i2p::util::net::IsInReservedRange (ourIP)) - { - i2p::context.UpdateAddress (ourIP); - SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen); - } - else - { - LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); - Failed (); - } - } - else - { - LogPrint (eLogError, "SSU: Message 'created' signature verification failed"); - Failed (); - } - } - - void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU: Session confirmed received"); - m_ConnectTimer.cancel (); - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU: Session confirmed header size ", headerSize, " exceeds packet length ", len); - return; - } - const uint8_t * payload = buf + headerSize; - payload++; // identity fragment info - uint16_t identitySize = bufbe16toh (payload); - if (identitySize + headerSize + 7 > len) // 7 = fragment info + fragment size + signed on time - { - LogPrint (eLogError, "SSU: Session confirmed identity size ", identitySize, " exceeds packet length ", len); - return; - } - payload += 2; // size of identity fragment - auto identity = std::make_shared (payload, identitySize); - auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); - m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); - payload += identitySize; // identity - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t signedOnTime = bufbe32toh(payload); - if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU: Message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); - Failed (); - return; - } - if (m_SignedData) - m_SignedData->Insert (payload, 4); // insert Alice's signed on time - payload += 4; // signed-on time - size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = fullSize & 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - payload += paddingSize; - if (fullSize + paddingSize > len) - { - LogPrint (eLogError, "SSU: Session confirmed message is too short ", len); - return; - } - // verify signature - if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) - { - m_Data.Send (CreateDeliveryStatusMsg (0)); - Established (); - } - else - { - LogPrint (eLogError, "SSU: Message 'confirmed' signature verification failed"); - Failed (); - } - } - - void SSUSession::SendSessionRequest () - { - uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - uint8_t flag = 0; - // fill extended options, 3 bytes extended options don't change message size - bool isV4 = m_RemoteEndpoint.address ().is_v4 (); - if ((isV4 && i2p::context.GetStatus () == eRouterStatusOK) || - (!isV4 && i2p::context.GetStatusV6 () == eRouterStatusOK)) // we don't need relays - { - // tell out peer to now assign relay tag - flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length - uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG - htobe16buf (payload, flags); - payload += 2; - } - // fill payload - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x - if (isV4) - { - payload[256] = 4; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - } - else - { - payload[256] = 16; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - } - // encrypt and send - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); - m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); - } - - void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) - { - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - - uint8_t buf[96 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, introducer.iTag); - payload += 4; - *payload = 0; // no address - payload++; - htobuf16(payload, 0); // port = 0 - payload += 2; - *payload = 0; // challenge - payload++; - memcpy (payload, (const uint8_t *)address->i, 32); - payload += 32; - htobe32buf (payload, nonce); // nonce - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - if (m_State == eSessionStateEstablished) - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); - else - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); - m_Server.Send (buf, 96, m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU: Relay request sent"); - } - - void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) - { - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time - s.Insert (x, 256); // x - - uint8_t buf[384 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); - s.Insert (payload, 256); // y - payload += 256; - if (m_RemoteEndpoint.address ().is_v4 ()) - { - // ipv4 - *payload = 4; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - s.Insert (payload, 4); // remote endpoint IP V4 - payload += 4; - } - else - { - // ipv6 - *payload = 16; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - s.Insert (payload, 16); // remote endpoint IP V6 - payload += 16; - } - htobe16buf (payload, m_RemoteEndpoint.port ()); - s.Insert (payload, 2); // remote port - payload += 2; - if (address->host.is_v4 ()) - s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 - else - s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 - s.Insert (htobe16 (address->port)); // our port - if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer (!IsV6 ())) - { - RAND_bytes((uint8_t *)&m_SentRelayTag, 4); - if (!m_SentRelayTag) m_SentRelayTag = 1; - } - htobe32buf (payload, m_SentRelayTag); - payload += 4; // relay tag - htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time - payload += 4; - s.Insert (payload - 8, 4); // relayTag - // we have to store this signed data for session confirmed - // same data but signed on time, it will Alice's there - m_SignedData = std::unique_ptr(new SignedData (s)); - s.Insert (payload - 4, 4); // BOB's signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt signature and padding with newly created session key - size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) - { - // fill random padding - RAND_bytes(payload + signatureLen, (16 - paddingSize)); - signatureLen += (16 - paddingSize); - } - m_SessionKeyEncryption.SetIV (iv); - m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); - payload += signatureLen; - size_t msgLen = payload - buf; - - // encrypt message with intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); - Send (buf, msgLen); - } - - void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) - { - uint8_t buf[512 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 1; // 1 fragment - payload++; // info - size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes - htobe16buf (payload, identLen); - payload += 2; // cursize - i2p::context.GetIdentity ()->ToBuffer (payload, identLen); - payload += identLen; - uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); - htobe32buf (payload, signedOnTime); // signed on time - payload += 4; - auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = ((payload - buf) + signatureLen)%16; - if (paddingSize > 0) paddingSize = 16 - paddingSize; - RAND_bytes(payload, paddingSize); // fill padding with random - payload += paddingSize; // padding size - // signature - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (htobe32 (m_RelayTag)); // relay tag - s.Insert (htobe32 (signedOnTime)); // signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - payload += signatureLen; - - size_t msgLen = payload - buf; - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); - Send (buf, msgLen); - } - - void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) - { - uint32_t relayTag = bufbe32toh (buf); - auto session = m_Server.FindRelaySession (relayTag); - if (session) - { - buf += 4; // relay tag - uint8_t size = *buf; - buf++; // size - buf += size; // address - buf += 2; // port - uint8_t challengeSize = *buf; - buf++; // challenge size - buf += challengeSize; - const uint8_t * introKey = buf; - buf += 32; // introkey - uint32_t nonce = bufbe32toh (buf); - SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); - SendRelayIntro (session, from); - } - } - - void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) - { - bool isV4 = to.address ().is_v4 (); // Charle's - bool isV4A = from.address ().is_v4 (); // Alice's - if ((isV4 && !isV4A) || (!isV4 && isV4A)) - { - LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay response"); - return; - } - uint8_t buf[80 + 18] = {0}; // 64 for ipv4 and 80 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - // Charlie - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Charlie's IP V6 - payload += 16; // address - } - htobe16buf (payload, to.port ()); // Charlie's port - payload += 2; // port - // Alice - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 - payload += 16; // address - } - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - htobe32buf (payload, nonce); - - if (m_State == eSessionStateEstablished) - { - // encrypt with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); - Send (buf, isV4 ? 64 : 80); - } - else - { - // ecrypt with Alice's intro key - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); - m_Server.Send (buf, isV4 ? 64 : 80, from); - } - LogPrint (eLogDebug, "SSU: Relay response sent"); - } - - void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) - { - if (!session) return; - bool isV4 = from.address ().is_v4 (); // Alice's - bool isV4C = session->m_RemoteEndpoint.address ().is_v4 (); // Charlie's - if ((isV4 && !isV4C) || (!isV4 && isV4C)) - { - LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay intro"); - return; - } - uint8_t buf[64 + 18] = {0}; // 48 for ipv4 and 64 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 - payload += 16; // address - } - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - *payload = 0; // challenge size - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey); - m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU: Relay intro sent"); - } - - void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: Relay response received"); - boost::asio::ip::address remoteIP; - uint16_t remotePort = 0; - auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort); - if (!remoteSize) return; - buf += remoteSize; len -= remoteSize; - boost::asio::ip::address ourIP; - uint16_t ourPort = 0; - auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort); - if (!ourSize) return; - buf += ourSize; len -= ourSize; - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - if (!i2p::util::net::IsInReservedRange (ourIP)) - i2p::context.UpdateAddress (ourIP); - else - LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); - if (ourIP.is_v4 ()) - { - if (ourPort != m_Server.GetPort ()) - { - if (i2p::context.GetStatus () == eRouterStatusTesting) - i2p::context.SetError (eRouterErrorSymmetricNAT); - } - else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) - i2p::context.SetStatus (eRouterStatusTesting); - } - uint32_t nonce = bufbe32toh (buf); - buf += 4; // nonce - auto it = m_RelayRequests.find (nonce); - if (it != m_RelayRequests.end ()) - { - // check if we are waiting for introduction - boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); - if (!m_Server.FindSession (remoteEndpoint)) - { - // we didn't have correct endpoint when sent relay request - // now we do - LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); - if ((remoteIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || - (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) - m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch - // we assume that HolePunch has been sent by this time and our SessionRequest will go through - m_Server.CreateDirectSession (it->second.first, remoteEndpoint, false); - } - // delete request - m_RelayRequests.erase (it); - // cancel connect timer - m_ConnectTimer.cancel (); - } - else - LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); - } - - void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) - { - boost::asio::ip::address ip; - uint16_t port = 0; - ExtractIPAddressAndPort (buf, len, ip, port); - if (!ip.is_unspecified () && port) - // send hole punch of 0 bytes - m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port)); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, - const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - memcpy (header->iv, iv, 16); - header->flag = flag | (payloadType << 4); // MSB is 0 - htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCEncryption encryption; - encryption.SetKey (aesKey); - encryption.SetIV (iv); - encryption.Encrypt (encrypted, encryptedLen, encrypted); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) - { - FillHeaderAndEncrypt (payloadType, buf, len, buf); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)out; - RAND_bytes (header->iv, 16); // random iv - m_SessionKeyEncryption.SetIV (header->iv); - SSUHeader * inHeader = (SSUHeader *)in; - inHeader->flag = payloadType << 4; // MSB is 0 - htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; - uint16_t encryptedLen = len - (encrypted - out); - m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); - // assume actual out buffer size is 18 (16 + 2) bytes more - memcpy (out + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); - } - - void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (aesKey); - decryption.SetIV (header->iv); - decryption.Decrypt (encrypted, encryptedLen, encrypted); - } - - void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - if (encryptedLen > 0) - { - m_SessionKeyDecryption.SetIV (header->iv); - m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); - } - } - - bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return false; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - uint8_t digest[16]; - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); - return !memcmp (header->mac, digest, 16); - } - - void SSUSession::Connect () - { - if (m_State == eSessionStateUnknown) - { - ScheduleConnectTimer (); // set connect timer - m_DHKeysPair = std::make_shared (); - m_DHKeysPair->GenerateKeys (); - SendSessionRequest (); - } - } - - void SSUSession::WaitForConnect () - { - if (!IsOutgoing ()) // incoming session - ScheduleConnectTimer (); - else - LogPrint (eLogError, "SSU: Wait for connect for outgoing session"); - } - - void SSUSession::ScheduleConnectTimer () - { - m_ConnectTimer.cancel (); - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); -} - - void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) - { - if (!ecode) - { - // timeout expired - LogPrint (eLogWarning, "SSU: Session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); - Failed (); - } - } - - void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to) - { - if (m_State == eSessionStateUnknown) - { - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); - m_RelayRequests.emplace (nonce, std::make_pair (to, ts)); - SendRelayRequest (introducer, nonce); - } - - void SSUSession::WaitForIntroduction () - { - m_State = eSessionStateIntroduced; - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - - void SSUSession::Close () - { - SendSessionDestroyed (); - Reset (); - m_State = eSessionStateClosed; - } - - void SSUSession::Reset () - { - m_State = eSessionStateUnknown; - transports.PeerDisconnected (shared_from_this ()); - m_Data.Stop (); - m_ConnectTimer.cancel (); - if (m_SentRelayTag) - { - m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore - m_SentRelayTag = 0; - } - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_IsSessionKey = false; - } - - void SSUSession::Done () - { - GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); - } - - void SSUSession::Established () - { - m_State = eSessionStateEstablished; - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_Data.Start (); - transports.PeerConnected (shared_from_this ()); - if (m_IsPeerTest) - SendPeerTest (); - if (m_SentRelayTag) - m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - - void SSUSession::Failed () - { - if (m_State != eSessionStateFailed) - { - m_State = eSessionStateFailed; - m_Server.DeleteSession (shared_from_this ()); - } - } - - void SSUSession::SendI2NPMessages (const std::vector >& msgs) - { - GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); - } - - void SSUSession::PostI2NPMessages (std::vector > msgs) - { - if (m_State == eSessionStateEstablished) - { - for (const auto& it: msgs) - if (it) - { - if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) - m_Data.Send (it); - else - LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); - } - } - } - - void SSUSession::ProcessData (uint8_t * buf, size_t len) - { - m_Data.ProcessMessage (buf, len); - m_IsDataReceived = true; - } - - void SSUSession::FlushData () - { - if (m_IsDataReceived) - { - m_Data.FlushReceivedMessage (); - m_IsDataReceived = false; - } - } - - void SSUSession::CleanUp (uint64_t ts) - { - m_Data.CleanUp (ts); - for (auto it = m_RelayRequests.begin (); it != m_RelayRequests.end ();) - { - if (ts > it->second.second + SSU_CONNECT_TIMEOUT) - it = m_RelayRequests.erase (it); - else - ++it; - } - } - - void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - uint32_t nonce = bufbe32toh (buf); // 4 bytes - boost::asio::ip::address addr; // Alice's address - uint16_t port = 0; // and port - auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port); - if (port && (size != 7) && (size != 19)) - { - LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported"); - return; - } - const uint8_t * introKey = buf + 4 + size; - switch (m_Server.GetPeerTestParticipant (nonce)) - { - // existing test - case ePeerTestParticipantAlice1: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - { - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); - if (IsV6 ()) - { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) - { - i2p::context.SetStatusV6 (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimerV6 (); - } - } - else if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK - { - i2p::context.SetStatus (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimer (); - } - } - else - { - LogPrint (eLogDebug, "SSU: First peer test from Charlie. We are Alice"); - if (m_State == eSessionStateEstablished) - LogPrint (eLogWarning, "SSU: First peer test from Charlie through established session. We are Alice"); - if (IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); - else - i2p::context.SetStatus (eRouterStatusOK); - m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie - } - break; - } - case ePeerTestParticipantAlice2: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); - else - { - // peer test successive - LogPrint (eLogDebug, "SSU: Second peer test from Charlie. We are Alice"); - if (IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); - else - i2p::context.SetStatus (eRouterStatusOK); - m_Server.RemovePeerTest (nonce); - } - break; - } - case ePeerTestParticipantBob: - { - LogPrint (eLogDebug, "SSU: Peer test from Charlie. We are Bob"); - auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest - if (session && session->m_State == eSessionStateEstablished) - { - const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob - session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice - } - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - case ePeerTestParticipantCharlie: - { - LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Charlie"); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - // test not found - case ePeerTestParticipantUnknown: - { - if (m_State == eSessionStateEstablished) - { - // new test - if (port) - { - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Charlie"); - Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) - { - m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); - SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob - } - } - else - { - LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Bob"); - auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie - if (session) - { - m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); - session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address - } - } - } - else - LogPrint (eLogError, "SSU: Unexpected peer test"); - } - } - } - - void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, - const uint8_t * introKey, bool toAddress, bool sendAddress) - // toAddress is true for Alice<->Chalie communications only - // sendAddress is false if message comes from Alice - { - uint8_t buf[80 + 18] = {0}; - uint8_t iv[16]; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, nonce); - payload += 4; // nonce - // address and port - if (sendAddress) - { - if (address.is_v4 ()) - { - *payload = 4; - memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 - } - else if (address.is_v6 ()) - { - *payload = 16; - memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 - } - else - *payload = 0; - payload += (payload[0] + 1); - } - else - { - *payload = 0; - payload++; //size - } - htobe16buf (payload, port); - payload += 2; // port - // intro key - if (toAddress) - { - // send our intro key to address instead of its own - auto addr = address.is_v4 () ? i2p::context.GetRouterInfo ().GetSSUAddress (true) : // ipv4 - i2p::context.GetRouterInfo ().GetSSUV6Address (); - if (addr) - memcpy (payload, addr->i, 32); // intro key - else - LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); - } - else - memcpy (payload, introKey, 32); // intro key - - // send - RAND_bytes (iv, 16); // random iv - if (toAddress) - { - // encrypt message with specified intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); - boost::asio::ip::udp::endpoint e (address, port); - m_Server.Send (buf, 80, e); - } - else - { - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); - Send (buf, 80); - } - } - - void SSUSession::SendPeerTest () - { - // we are Alice - LogPrint (eLogDebug, "SSU: Sending peer test"); - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); - return; - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - if (!nonce) nonce = 1; - m_IsPeerTest = false; - m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); - SendPeerTest (nonce, boost::asio::ip::address(), 0, address->i, false, false); // address and port always zero for Alice - } - - void SSUSession::SendKeepAlive () - { - if (m_State == eSessionStateEstablished) - { - uint8_t buf[48 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 0; // flags - payload++; - *payload = 0; // num fragments - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); - Send (buf, 48); - LogPrint (eLogDebug, "SSU: keep-alive sent"); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - } - - void SSUSession::SendSessionDestroyed () - { - if (m_IsSessionKey) - { - uint8_t buf[48 + 18] = {0}; - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); - try - { - Send (buf, 48); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "SSU: Exception while sending session destoroyed: ", ex.what ()); - } - LogPrint (eLogDebug, "SSU: Session destroyed sent"); - } - } - - void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) - { - uint8_t buf[SSU_MTU_V4 + 18] = {0}; - size_t msgSize = len + sizeof (SSUHeader); - size_t paddingSize = msgSize & 0x0F; // %16 - if (paddingSize > 0) msgSize += (16 - paddingSize); - if (msgSize > SSU_MTU_V4) - { - LogPrint (eLogWarning, "SSU: Payload size ", msgSize, " exceeds MTU"); - return; - } - memcpy (buf + sizeof (SSUHeader), payload, len); - // encrypt message with session key - FillHeaderAndEncrypt (type, buf, msgSize); - Send (buf, msgSize); - } - - void SSUSession::Send (const uint8_t * buf, size_t size) - { - m_NumSentBytes += size; - i2p::transport::transports.UpdateSentBytes (size); - m_Server.Send (buf, size, m_RemoteEndpoint); - } - - size_t SSUSession::ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port) - { - if (!len) return 0; - uint8_t size = *buf; - size_t s = 1 + size + 2; // size + address + port - if (len < s) - { - LogPrint (eLogWarning, "SSU: Address is too short ", len); - port = 0; - return len; - } - buf++; // size - if (size == 4) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf, 4); - ip = boost::asio::ip::address_v4 (bytes); - } - else if (size == 16) - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), buf, 16); - ip = boost::asio::ip::address_v6 (bytes); - } - else - LogPrint (eLogWarning, "SSU: Address size ", int(size), " is not supported"); - buf += size; - port = bufbe16toh (buf); - return s; - } -} -} diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h deleted file mode 100644 index e28b4991..00000000 --- a/libi2pd/SSUSession.h +++ /dev/null @@ -1,177 +0,0 @@ -/* -* Copyright (c) 2013-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 -*/ - -#ifndef SSU_SESSION_H__ -#define SSU_SESSION_H__ - -#include -#include -#include -#include "Crypto.h" -#include "I2NPProtocol.h" -#include "TransportSession.h" -#include "SSUData.h" - -namespace i2p -{ -namespace transport -{ - const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; - struct SSUHeader - { - uint8_t mac[16]; - uint8_t iv[16]; - uint8_t flag; - uint8_t time[4]; - - uint8_t GetPayloadType () const { return flag >> 4; }; - bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; - }; - - const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds - const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes - const int SSU_CLOCK_SKEW = 60; // in seconds - const int SSU_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust - const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; - - // payload types (4 bits) - const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; - const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; - const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; - const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; - const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; - const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; - const uint8_t PAYLOAD_TYPE_DATA = 6; - const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; - const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; - - // extended options - const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; - - enum SessionState - { - eSessionStateUnknown, - eSessionStateIntroduced, - eSessionStateEstablished, - eSessionStateClosed, - eSessionStateFailed - }; - - enum PeerTestParticipant - { - ePeerTestParticipantUnknown = 0, - ePeerTestParticipantAlice1, - ePeerTestParticipantAlice2, - ePeerTestParticipantBob, - ePeerTestParticipantCharlie - }; - - class SSUServer; - class SSUSession: public TransportSession, public std::enable_shared_from_this - { - public: - - SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router = nullptr, bool peerTest = false); - void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - ~SSUSession (); - - void Connect (); - void WaitForConnect (); - void Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to); // Alice to Charlie - void WaitForIntroduction (); - void Close (); - void Done (); - void Failed (); - const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - SSUServer& GetServer () { return m_Server; }; - - bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; - void SendI2NPMessages (const std::vector >& msgs); - void SendPeerTest (); // Alice - - SessionState GetState () const { return m_State; }; - size_t GetNumSentBytes () const { return m_NumSentBytes; }; - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - - void SendKeepAlive (); - uint32_t GetRelayTag () const { return m_RelayTag; }; - const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; - - void FlushData (); - void CleanUp (uint64_t ts); - - private: - - boost::asio::io_service& GetService (); - void CreateAESandMacKey (const uint8_t * pubKey); - size_t GetSSUHeaderSize (const uint8_t * buf) const; - void PostI2NPMessages (std::vector > msgs); - void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session - void ProcessSessionRequest (const uint8_t * buf, size_t len); - void SendSessionRequest (); - void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); - void ProcessSessionCreated (uint8_t * buf, size_t len); - void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); - void ProcessSessionConfirmed (const uint8_t * buf, size_t len); - void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); - void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); - void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); - void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); - void ProcessRelayResponse (const uint8_t * buf, size_t len); - void ProcessRelayIntro (const uint8_t * buf, size_t len); - void Established (); - void ScheduleConnectTimer (); - void HandleConnectTimer (const boost::system::error_code& ecode); - void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); - void ProcessData (uint8_t * buf, size_t len); - void SendSessionDestroyed (); - void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key - void Send (const uint8_t * buf, size_t size); - - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, - const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key - void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); - void DecryptSessionKey (uint8_t * buf, size_t len); - bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); - - void Reset (); - - static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size - - private: - - friend class SSUData; // TODO: change in later - SSUServer& m_Server; - const boost::asio::ip::udp::endpoint m_RemoteEndpoint; - boost::asio::deadline_timer m_ConnectTimer; - bool m_IsPeerTest; - SessionState m_State; - bool m_IsSessionKey; - uint32_t m_RelayTag; // received from peer - uint32_t m_SentRelayTag; // sent by us - i2p::crypto::CBCEncryption m_SessionKeyEncryption; - i2p::crypto::CBCDecryption m_SessionKeyDecryption; - i2p::crypto::AESKey m_SessionKey; - i2p::crypto::MACKey m_MacKey; - i2p::data::RouterInfo::IntroKey m_IntroKey; - SSUData m_Data; - bool m_IsDataReceived; - std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) - std::shared_ptr m_DHKeysPair; // X - for client and Y - for server - }; -} -} - -#endif diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index ebc188a9..3e4b451b 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,6 +7,10 @@ */ #include +#include +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +#include +#endif #include "Log.h" #include "Signature.h" @@ -14,27 +18,193 @@ namespace i2p { namespace crypto { -#if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier () +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + DSAVerifier::DSAVerifier (): + m_PublicKey (nullptr) + { + } + + DSAVerifier::~DSAVerifier () + { + if (m_PublicKey) + EVP_PKEY_free (m_PublicKey); + } + + void DSAVerifier::SetPublicKey (const uint8_t * signingKey) + { + if (m_PublicKey) + EVP_PKEY_free (m_PublicKey); + BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); + m_PublicKey = CreateDSA (pub); + BN_free (pub); + } + + bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + // calculate SHA1 digest + uint8_t digest[20], sign[48]; + SHA1 (buf, len, digest); + // signature + DSA_SIG * sig = DSA_SIG_new(); + DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); + // to DER format + uint8_t * s = sign; + auto l = i2d_DSA_SIG (sig, &s); + DSA_SIG_free(sig); + // verify + auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL); + EVP_PKEY_verify_init(ctx); + EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); + bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20); + EVP_PKEY_CTX_free(ctx); + return ret; + } + + DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) + { + BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL); + m_PrivateKey = CreateDSA (nullptr, priv); + BN_free (priv); + } + + DSASigner::~DSASigner () + { + if (m_PrivateKey) + EVP_PKEY_free (m_PrivateKey); + } + + void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[20], sign[48]; + SHA1 (buf, len, digest); + auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL); + EVP_PKEY_sign_init(ctx); + EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); + size_t l = 48; + EVP_PKEY_sign(ctx, sign, &l, digest, 20); + const uint8_t * s1 = sign; + DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l); + const BIGNUM * r, * s; + DSA_SIG_get0 (sig, &r, &s); + bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); + bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); + DSA_SIG_free(sig); + EVP_PKEY_CTX_free(ctx); + } + + void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + EVP_PKEY * paramskey = CreateDSA(); + EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL); + EVP_PKEY_keygen_init(ctx); + EVP_PKEY * pkey = nullptr; + EVP_PKEY_keygen(ctx, &pkey); + BIGNUM * pub = NULL, * priv = NULL; + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub); + bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv); + bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); + BN_free (pub); BN_free (priv); + EVP_PKEY_free (pkey); + EVP_PKEY_free (paramskey); + EVP_PKEY_CTX_free (ctx); + } +#else + + DSAVerifier::DSAVerifier () + { + m_PublicKey = CreateDSA (); + } + + DSAVerifier::~DSAVerifier () + { + DSA_free (m_PublicKey); + } + + void DSAVerifier::SetPublicKey (const uint8_t * signingKey) + { + DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); + } + + bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + // calculate SHA1 digest + uint8_t digest[20]; + SHA1 (buf, len, digest); + // signature + DSA_SIG * sig = DSA_SIG_new(); + DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); + // DSA verification + int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); + DSA_SIG_free(sig); + return ret; + } + + DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) + { + m_PrivateKey = CreateDSA (); + DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); + } + + DSASigner::~DSASigner () + { + DSA_free (m_PrivateKey); + } + + void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[20]; + SHA1 (buf, len, digest); + DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); + const BIGNUM * r, * s; + DSA_SIG_get0 (sig, &r, &s); + bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); + bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); + DSA_SIG_free(sig); + } + + void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + DSA * dsa = CreateDSA (); + DSA_generate_key (dsa); + const BIGNUM * pub_key, * priv_key; + DSA_get0_key(dsa, &pub_key, &priv_key); + bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); + bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); + DSA_free (dsa); + } +#endif + +#if OPENSSL_EDDSA + EDDSA25519Verifier::EDDSA25519Verifier (): + m_Pkey (nullptr) { - m_MDCtx = EVP_MD_CTX_create (); } EDDSA25519Verifier::~EDDSA25519Verifier () { - EVP_MD_CTX_destroy (m_MDCtx); + EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); - EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey); - EVP_PKEY_free (pkey); + if (m_Pkey) EVP_PKEY_free (m_Pkey); + m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); + if (m_Pkey) + { + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, m_Pkey); + auto ret = EVP_DigestVerify (ctx, signature, 64, buf, len); + EVP_MD_CTX_destroy (ctx); + return ret; + } + else + LogPrint (eLogError, "EdDSA verification key is not set"); + return false; } #else @@ -99,42 +269,219 @@ namespace crypto #if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_MDCtx (nullptr), m_Fallback (nullptr) + m_Pkey (nullptr), m_Fallback (nullptr) { - EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); + m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_raw_public_key (pkey, publicKey, &len); + EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); + EVP_PKEY_free (m_Pkey); + m_Pkey = nullptr; } - else - { - m_MDCtx = EVP_MD_CTX_create (); - EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, pkey); - } - EVP_PKEY_free (pkey); } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; - EVP_MD_CTX_destroy (m_MDCtx); + if (m_Pkey) EVP_PKEY_free (m_Pkey); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - if (m_Fallback) return m_Fallback->Sign (buf, len, signature); - else + if (m_Fallback) + return m_Fallback->Sign (buf, len, signature); + else if (m_Pkey) { + + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); size_t l = 64; uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 - EVP_DigestSign (m_MDCtx, sig, &l, buf, len); + EVP_DigestSignInit (ctx, NULL, NULL, NULL, m_Pkey); + if (!EVP_DigestSign (ctx, sig, &l, buf, len)) + LogPrint (eLogError, "EdDSA signing failed"); memcpy (signature, sig, 64); + EVP_MD_CTX_destroy (ctx); } + else + LogPrint (eLogError, "EdDSA signing key is not set"); } #endif + +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) + static const OSSL_PARAM EDDSA25519phParams[] = + { + OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9), + OSSL_PARAM_END + }; + + bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + auto pkey = GetPkey (); + if (pkey) + { + uint8_t digest[64]; + SHA512 (buf, len, digest); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); + auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64); + EVP_MD_CTX_destroy (ctx); + return ret; + } + else + LogPrint (eLogError, "EdDSA verification key is not set"); + return false; + } + + EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): + EDDSA25519Signer (signingPrivateKey) + { + } + + void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + auto pkey = GetPkey (); + if (pkey) + { + uint8_t digest[64]; + SHA512 (buf, len, digest); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + size_t l = 64; + uint8_t sig[64]; + EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); + if (!EVP_DigestSign (ctx, sig, &l, digest, 64)) + LogPrint (eLogError, "EdDSA signing failed"); + memcpy (signature, sig, 64); + EVP_MD_CTX_destroy (ctx); + } + else + LogPrint (eLogError, "EdDSA signing key is not set"); + } +#endif + +#if OPENSSL_PQ + + MLDSA44Verifier::MLDSA44Verifier (): + m_Pkey (nullptr) + { + } + + MLDSA44Verifier::~MLDSA44Verifier () + { + EVP_PKEY_free (m_Pkey); + } + + void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey) + { + if (m_Pkey) + { + EVP_PKEY_free (m_Pkey); + m_Pkey = nullptr; + } + OSSL_PARAM params[] = + { + OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()), + OSSL_PARAM_END + }; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); + if (ctx) + { + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLDSA44 can't create PKEY context"); + } + + bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + bool ret = false; + if (m_Pkey) + { + EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (vctx) + { + EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); + if (sig) + { + int encode = 1; + OSSL_PARAM params[] = + { + OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), + OSSL_PARAM_END + }; + EVP_PKEY_verify_message_init (vctx, sig, params); + ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len); + EVP_SIGNATURE_free (sig); + } + EVP_PKEY_CTX_free (vctx); + } + else + LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); + } + else + LogPrint (eLogError, "MLDSA44 verification key is not set"); + return ret; + } + + MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey): + m_Pkey (nullptr) + { + OSSL_PARAM params[] = + { + OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH), + OSSL_PARAM_END + }; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); + if (ctx) + { + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "MLDSA44 can't create PKEY context"); + } + + MLDSA44Signer::~MLDSA44Signer () + { + if (m_Pkey) EVP_PKEY_free (m_Pkey); + } + + void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + if (m_Pkey) + { + EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); + if (sctx) + { + EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); + if (sig) + { + int encode = 1; + OSSL_PARAM params[] = + { + OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), + OSSL_PARAM_END + }; + EVP_PKEY_sign_message_init (sctx, sig, params); + size_t siglen = MLDSA44_SIGNATURE_LENGTH; + EVP_PKEY_sign (sctx, signature, &siglen, buf, len); + EVP_SIGNATURE_free (sig); + } + EVP_PKEY_CTX_free (sctx); + } + else + LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); + } + else + LogPrint (eLogError, "MLDSA44 signing key is not set"); + } + +#endif } } diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index e153e66d..20c7e11b 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -43,94 +43,55 @@ namespace crypto virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; }; + // DSA const size_t DSA_PUBLIC_KEY_LENGTH = 128; const size_t DSA_SIGNATURE_LENGTH = 40; const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2; class DSAVerifier: public Verifier { public: + + DSAVerifier (); + ~DSAVerifier (); - DSAVerifier () - { - m_PublicKey = CreateDSA (); - } - - void SetPublicKey (const uint8_t * signingKey) - { - DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); - } - - ~DSAVerifier () - { - DSA_free (m_PublicKey); - } - - bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // calculate SHA1 digest - uint8_t digest[20]; - SHA1 (buf, len, digest); - // signature - DSA_SIG * sig = DSA_SIG_new(); - DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); - // DSA verification - int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); - DSA_SIG_free(sig); - return ret; - } - - size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; }; - size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; }; - + // implements Verifier + void SetPublicKey (const uint8_t * signingKey) override; + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const override; + size_t GetPublicKeyLen () const override { return DSA_PUBLIC_KEY_LENGTH; }; + size_t GetSignatureLen () const override { return DSA_SIGNATURE_LENGTH; }; + private: +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + EVP_PKEY * m_PublicKey; +#else DSA * m_PublicKey; +#endif }; class DSASigner: public Signer { public: - DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) + DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey); // openssl 1.1 always requires DSA public key even for signing - { - m_PrivateKey = CreateDSA (); - DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); - } + ~DSASigner (); - ~DSASigner () - { - DSA_free (m_PrivateKey); - } - - void Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - uint8_t digest[20]; - SHA1 (buf, len, digest); - DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); - const BIGNUM * r, * s; - DSA_SIG_get0 (sig, &r, &s); - bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); - bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); - DSA_SIG_free(sig); - } + // implements Signer + void Sign (const uint8_t * buf, int len, uint8_t * signature) const override; private: +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + EVP_PKEY * m_PrivateKey; +#else DSA * m_PrivateKey; +#endif }; - inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - DSA * dsa = CreateDSA (); - DSA_generate_key (dsa); - const BIGNUM * pub_key, * priv_key; - DSA_get0_key(dsa, &pub_key, &priv_key); - bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); - DSA_free (dsa); - } + void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey); + // ECDSA struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) @@ -161,7 +122,6 @@ namespace crypto enum { hashLen = 64 }; }; - // EcDSA template class ECDSAVerifier: public Verifier { @@ -303,14 +263,28 @@ namespace crypto private: -#if OPENSSL_EDDSA - EVP_MD_CTX * m_MDCtx; +#if OPENSSL_EDDSA + + EVP_PKEY * m_Pkey; + + protected: + + EVP_PKEY * GetPkey () const { return m_Pkey; }; #else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; #endif }; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + class EDDSA25519phVerifier: public EDDSA25519Verifier + { + public: + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + }; +#endif + class EDDSA25519SignerCompat: public Signer { public: @@ -339,9 +313,13 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + protected: + + EVP_PKEY * GetPkey () const { return m_Pkey; }; + private: - EVP_MD_CTX * m_MDCtx; + EVP_PKEY * m_Pkey; EDDSA25519SignerCompat * m_Fallback; }; #else @@ -350,6 +328,18 @@ namespace crypto #endif +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + class EDDSA25519phSigner: public EDDSA25519Signer + { + public: + + EDDSA25519phSigner (const uint8_t * signingPrivateKey); + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + }; + +#endif + inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { #if OPENSSL_EDDSA @@ -530,6 +520,57 @@ namespace crypto RedDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); } + +#if OPENSSL_PQ +#include + + // Post-Quantum + const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312; + const size_t MLDSA44_SIGNATURE_LENGTH = 2420; + const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560; + class MLDSA44Verifier: public Verifier + { + public: + + MLDSA44Verifier (); + void SetPublicKey (const uint8_t * signingKey); + ~MLDSA44Verifier (); + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + + size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; }; + size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; }; + size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; }; + + private: + + EVP_PKEY * m_Pkey; + }; + + class MLDSA44Signer: public Signer + { + public: + + MLDSA44Signer (const uint8_t * signingPrivateKey); + ~MLDSA44Signer (); + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + + private: + + EVP_PKEY * m_Pkey; + }; + + inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44"); + size_t len = MLDSA44_PUBLIC_KEY_LENGTH; + EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len); + len = MLDSA44_PRIVATE_KEY_LENGTH; + EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len); + EVP_PKEY_free (pkey); + } +#endif } } diff --git a/libi2pd/Socks5.h b/libi2pd/Socks5.h new file mode 100644 index 00000000..8db8939b --- /dev/null +++ b/libi2pd/Socks5.h @@ -0,0 +1,210 @@ +/* +* Copyright (c) 2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#ifndef SOCKS5_H__ +#define SOCKS5_H__ + +#include +#include +#include +#include "I2PEndian.h" + +namespace i2p +{ +namespace transport +{ + // SOCKS5 constants + const uint8_t SOCKS5_VER = 0x05; + const uint8_t SOCKS5_CMD_CONNECT = 0x01; + const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03; + const uint8_t SOCKS5_ATYP_IPV4 = 0x01; + const uint8_t SOCKS5_ATYP_IPV6 = 0x04; + const uint8_t SOCKS5_ATYP_NAME = 0x03; + const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10; + const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22; + + const uint8_t SOCKS5_REPLY_SUCCESS = 0x00; + const uint8_t SOCKS5_REPLY_SERVER_FAILURE = 0x01; + const uint8_t SOCKS5_REPLY_CONNECTION_NOT_ALLOWED = 0x02; + const uint8_t SOCKS5_REPLY_NETWORK_UNREACHABLE = 0x03; + const uint8_t SOCKS5_REPLY_HOST_UNREACHABLE = 0x04; + const uint8_t SOCKS5_REPLY_CONNECTION_REFUSED = 0x05; + const uint8_t SOCKS5_REPLY_TTL_EXPIRED = 0x06; + const uint8_t SOCKS5_REPLY_COMMAND_NOT_SUPPORTED = 0x07; + const uint8_t SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; + + // SOCKS5 handshake + template + void Socks5ReadReply (Socket& s, Handler handler) + { + auto readbuff = std::make_shared >(258); // max possible + boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), 5), boost::asio::transfer_all(), // read 4 bytes of header + first byte of address + [readbuff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) + { + if (!ec) + { + if ((*readbuff)[1] == SOCKS5_REPLY_SUCCESS) + { + size_t len = 0; + switch ((*readbuff)[3]) // ATYP + { + case SOCKS5_ATYP_IPV4: len = 3; break; // address length 4 bytes + case SOCKS5_ATYP_IPV6: len = 15; break; // address length 16 bytes + case SOCKS5_ATYP_NAME: len += (*readbuff)[4]; break; // first byte of address is length + default: ; + } + if (len) + { + len += 2; // port + boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), len), boost::asio::transfer_all(), + [readbuff, handler](const boost::system::error_code& ec, std::size_t transferred) + { + if (!ec) + handler (boost::system::error_code ()); // success + else + handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); + }); + } + else + handler (boost::asio::error::make_error_code (boost::asio::error::fault)); // unknown address type + } + else + switch ((*readbuff)[1]) // REP + { + case SOCKS5_REPLY_SERVER_FAILURE: + handler (boost::asio::error::make_error_code (boost::asio::error::access_denied )); + break; + case SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: + handler (boost::asio::error::make_error_code (boost::asio::error::no_permission)); + break; + case SOCKS5_REPLY_HOST_UNREACHABLE: + handler (boost::asio::error::make_error_code (boost::asio::error::host_unreachable)); + break; + case SOCKS5_REPLY_NETWORK_UNREACHABLE: + handler (boost::asio::error::make_error_code (boost::asio::error::network_unreachable)); + break; + case SOCKS5_REPLY_CONNECTION_REFUSED: + handler (boost::asio::error::make_error_code (boost::asio::error::connection_refused)); + break; + case SOCKS5_REPLY_TTL_EXPIRED: + handler (boost::asio::error::make_error_code (boost::asio::error::timed_out)); + break; + case SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: + handler (boost::asio::error::make_error_code (boost::asio::error::operation_not_supported)); + break; + case SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: + handler (boost::asio::error::make_error_code (boost::asio::error::no_protocol_option)); + break; + default: + handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); + } + } + else + handler (ec); + }); + } + + template + void Socks5Connect (Socket& s, Handler handler, std::shared_ptr > buff, uint16_t port) + { + if (buff && buff->size () >= 6) + { + (*buff)[0] = SOCKS5_VER; + (*buff)[1] = SOCKS5_CMD_CONNECT; + (*buff)[2] = 0x00; + htobe16buf(buff->data () + buff->size () - 2, port); + boost::asio::async_write(s, boost::asio::buffer(*buff), boost::asio::transfer_all(), + [buff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) + { + (void) transferred; + if (!ec) + Socks5ReadReply (s, handler); + else + handler (ec); + }); + } + else + handler (boost::asio::error::make_error_code (boost::asio::error::no_buffer_space)); + } + + template + void Socks5Connect (Socket& s, const boost::asio::ip::tcp::endpoint& ep, Handler handler) + { + std::shared_ptr > buff; + if(ep.address ().is_v4 ()) + { + buff = std::make_shared >(10); + (*buff)[3] = SOCKS5_ATYP_IPV4; + auto addrbytes = ep.address ().to_v4().to_bytes(); + memcpy(buff->data () + 4, addrbytes.data(), 4); + } + else if (ep.address ().is_v6 ()) + { + buff = std::make_shared >(22); + (*buff)[3] = SOCKS5_ATYP_IPV6; + auto addrbytes = ep.address ().to_v6().to_bytes(); + memcpy(buff->data () + 4, addrbytes.data(), 16); + } + if (buff) + Socks5Connect (s, handler, buff, ep.port ()); + else + handler (boost::asio::error::make_error_code (boost::asio::error::fault)); + } + + template + void Socks5Connect (Socket& s, const std::pair& ep, Handler handler) + { + auto& addr = ep.first; + if (addr.length () <= 255) + { + auto buff = std::make_shared >(addr.length () + 7); + (*buff)[3] = SOCKS5_ATYP_NAME; + (*buff)[4] = addr.length (); + memcpy (buff->data () + 5, addr.c_str (), addr.length ()); + Socks5Connect (s, handler, buff, ep.second); + } + else + handler (boost::asio::error::make_error_code (boost::asio::error::name_too_long)); + } + + + template + void Socks5Handshake (Socket& s, Endpoint ep, Handler handler) + { + static const uint8_t methodSelection[3] = { SOCKS5_VER, 0x01, 0x00 }; // 1 method, no auth + boost::asio::async_write(s, boost::asio::buffer(methodSelection, 3), boost::asio::transfer_all(), + [&s, ep, handler] (const boost::system::error_code& ec, std::size_t transferred) + { + (void) transferred; + if (!ec) + { + auto readbuff = std::make_shared >(2); + boost::asio::async_read(s, boost::asio::buffer(*readbuff), boost::asio::transfer_all(), + [&s, ep, handler, readbuff] (const boost::system::error_code& ec, std::size_t transferred) + { + if (!ec) + { + if (transferred == 2 && (*readbuff)[1] == 0x00) // no auth + Socks5Connect (s, ep, handler); + else + handler (boost::asio::error::make_error_code (boost::asio::error::invalid_argument)); + } + else + handler (ec); + }); + } + else + handler (ec); + }); + } + +} +} + +#endif diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index af0a359f..99da5fd2 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,44 +19,55 @@ namespace i2p { namespace stream { - void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) - { - Add (std::make_shared(buf, len, handler)); - } - - void SendBufferQueue::Add (std::shared_ptr buf) + void SendBufferQueue::Add (std::shared_ptr&& buf) { if (buf) { - m_Buffers.push_back (buf); m_Size += buf->len; + m_Buffers.push_back (std::move (buf)); } } size_t SendBufferQueue::Get (uint8_t * buf, size_t len) { + if (!m_Size) return 0; size_t offset = 0; - while (!m_Buffers.empty () && offset < len) + if (len >= m_Size) { - auto nextBuffer = m_Buffers.front (); - auto rem = nextBuffer->GetRemainingSize (); - if (offset + rem <= len) + for (auto& it: m_Buffers) { - // whole buffer - memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); + auto rem = it->GetRemainingSize (); + memcpy (buf + offset, it->GetRemaningBuffer (), rem); offset += rem; - m_Buffers.pop_front (); // delete it } - else + m_Buffers.clear (); + m_Size = 0; + return offset; + } + else + { + while (!m_Buffers.empty () && offset < len) { - // partially - rem = len - offset; - memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); - nextBuffer->offset += (len - offset); - offset = len; // break + auto nextBuffer = m_Buffers.front (); + auto rem = nextBuffer->GetRemainingSize (); + if (offset + rem <= len) + { + // whole buffer + memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); + offset += rem; + m_Buffers.pop_front (); // delete it + } + else + { + // partially + rem = len - offset; + memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); + nextBuffer->offset += rem; + offset = len; // break + } } - } - m_Size -= offset; + m_Size -= offset; + } return offset; } @@ -64,36 +75,66 @@ namespace stream { if (!m_Buffers.empty ()) { - for (auto it: m_Buffers) + for (auto& it: m_Buffers) it->Cancel (); m_Buffers.clear (); m_Size = 0; } } - Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, + Stream::Stream (boost::asio::io_context& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), - m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), - m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), + m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0), + m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), + m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed + m_Status (eStreamStatusNew), m_IsIncoming (false), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), + m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), - m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), - m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) + m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), + m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), + m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), + m_Jitter (0), m_MinPacingTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), + m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed + m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); + auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed (); + if (outboundSpeed) + m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed; + + auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed + if (inboundSpeed) + m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed; } - Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): - m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), - m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), - m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) + Stream::Stream (boost::asio::io_context& service, StreamingDestination& local): + m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0), + m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), + m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed + m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), + m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), + m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), + m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), + m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), + m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), + m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), + m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed + m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); + auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed (); + if (outboundSpeed) + m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed; + + auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed + if (inboundSpeed) + m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed; } Stream::~Stream () @@ -108,6 +149,7 @@ namespace stream m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); + m_SendTimer.cancel (); //CleanUp (); /* Need to recheck - broke working on windows */ if (deleteFromDestination) m_LocalDestination.DeleteStream (shared_from_this ()); @@ -115,16 +157,15 @@ namespace stream void Stream::CleanUp () { - { - std::unique_lock l(m_SendBufferMutex); - m_SendBuffer.CleanUp (); - } + m_SendBuffer.CleanUp (); while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } + + m_NACKedPackets.clear (); for (auto it: m_SentPackets) m_LocalDestination.DeletePacket (it); @@ -137,19 +178,51 @@ namespace stream void Stream::HandleNextPacket (Packet * packet) { + if (m_Status == eStreamStatusTerminated) + { + m_LocalDestination.DeletePacket (packet); + return; + } m_NumReceivedBytes += packet->GetLength (); if (!m_SendStreamID) + { m_SendStreamID = packet->GetReceiveStreamID (); + if (!m_RemoteIdentity && packet->GetNACKCount () == 8 && // first incoming packet + memcmp (packet->GetNACKs (), m_LocalDestination.GetOwner ()->GetIdentHash (), 32)) + { + LogPrint (eLogWarning, "Streaming: Destination mismatch for ", m_LocalDestination.GetOwner ()->GetIdentHash ().ToBase32 ()); + m_LocalDestination.DeletePacket (packet); + return; + } + } if (!packet->IsNoAck ()) // ack received ProcessAck (packet); int32_t receivedSeqn = packet->GetSeqn (); - bool isSyn = packet->IsSYN (); - if (!receivedSeqn && !isSyn) + if (!receivedSeqn && m_LastReceivedSequenceNumber >= 0) { - // plain ack - LogPrint (eLogDebug, "Streaming: Plain ACK received"); + uint16_t flags = packet->GetFlags (); + if (flags) + // plain ack with options + ProcessOptions (flags, packet); + else + // plain ack + { + LogPrint (eLogDebug, "Streaming: Plain ACK received"); + if (m_IsImmediateAckRequested) + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (m_IsFirstRttSample) + { + m_RTT = ts - m_LastSendTime; + m_IsFirstRttSample = false; + } + else + m_RTT = (m_RTT + (ts - m_LastSendTime)) / 2; + m_IsImmediateAckRequested = false; + } + } m_LocalDestination.DeletePacket (packet); return; } @@ -159,7 +232,8 @@ namespace stream { // we have received next in sequence message ProcessPacket (packet); - + if (m_Status == eStreamStatusTerminated) return; + // we should also try stored messages if any for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();) { @@ -169,6 +243,7 @@ namespace stream m_SavedPackets.erase (it++); ProcessPacket (savedPacket); + if (m_Status == eStreamStatusTerminated) return; } else break; @@ -179,16 +254,12 @@ namespace stream { if (!m_IsAckSendScheduled) { - m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; - else if (ackTimeout < MIN_SEND_ACK_TIMEOUT) ackTimeout = MIN_SEND_ACK_TIMEOUT; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); - m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, - shared_from_this (), std::placeholders::_1)); + ScheduleAck (ackTimeout); } } - else if (isSyn) + else if (packet->IsSYN ()) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } @@ -198,8 +269,21 @@ namespace stream { // we have received duplicate LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); - SendQuickAck (); // resend ack for previous message again + if (receivedSeqn <= m_PreviousReceivedSequenceNumber || receivedSeqn == m_LastReceivedSequenceNumber) + { + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + CancelRemoteLeaseChange (); + UpdateCurrentRemoteLease (); + } + m_PreviousReceivedSequenceNumber = receivedSeqn; m_LocalDestination.DeletePacket (packet); // packet dropped + if (!m_IsAckSendScheduled) + { + SendQuickAck (); // resend ack for previous message again + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } } else { @@ -208,22 +292,18 @@ namespace stream SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) { - // send NACKs for missing messages ASAP - if (m_IsAckSendScheduled) - { - m_IsAckSendScheduled = false; - m_AckSendTimer.cancel (); - } - SendQuickAck (); - } + if (!m_IsAckSendScheduled) + { + // send NACKs for missing messages + SendQuickAck (); + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } + } else - { // wait for SYN - m_IsAckSendScheduled = true; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT)); - m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, - shared_from_this (), std::placeholders::_1)); - } + ScheduleAck (SYN_TIMEOUT); } } } @@ -277,18 +357,41 @@ namespace stream { const uint8_t * optionData = packet->GetOptionData (); size_t optionSize = packet->GetOptionSize (); + if (optionSize > packet->len) + { + LogPrint (eLogInfo, "Streaming: Invalid option size ", optionSize, " Discarded"); + return false; + } + if (!flags) return true; + bool immediateAckRequested = false; if (flags & PACKET_FLAG_DELAY_REQUESTED) { - if (!m_IsAckSendScheduled) + uint16_t delayRequested = bufbe16toh (optionData); + if (!delayRequested) // 0 requests an immediate ack + immediateAckRequested = true; + else if (!m_IsAckSendScheduled) { - uint16_t delayRequested = bufbe16toh (optionData); - if (delayRequested > 0 && delayRequested < m_RTT) + if (delayRequested < m_RTT) { m_IsAckSendScheduled = true; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } + if (delayRequested >= DELAY_CHOKING) + { + if (!m_IsClientChoked) + { + LogPrint (eLogDebug, "Streaming: Client choked, set min. window size"); + m_WindowDropTargetSize = MIN_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsClientChoked = true; + m_IsWinDropped = false; + m_DropWindowDelaySequenceNumber = m_SequenceNumber; + UpdatePacingTime (); + } + } } optionData += 2; } @@ -347,30 +450,45 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - uint8_t signature[256]; + bool verified = false; auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); - if(signatureLen <= sizeof(signature)) - { - memcpy (signature, optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - bool verified = m_TransientVerifier ? - m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : - m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); - if (!verified) - { - LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); - Close (); - flags |= PACKET_FLAG_CLOSE; - } - memcpy (const_cast(optionData), signature, signatureLen); - optionData += signatureLen; - } - else + if (signatureLen > packet->GetLength ()) { LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); return false; + } + if(signatureLen <= 256) + { + // standard + uint8_t signature[256]; + memcpy (signature, optionData, signatureLen); + memset (const_cast(optionData), 0, signatureLen); + verified = m_TransientVerifier ? + m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : + m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); + if (verified) + memcpy (const_cast(optionData), signature, signatureLen); } + else + { + // post quantum + std::vector signature(signatureLen); + memcpy (signature.data (), optionData, signatureLen); + memset (const_cast(optionData), 0, signatureLen); + verified = m_TransientVerifier ? + m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()) : + m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()); + } + if (verified) + optionData += signatureLen; + else + { + LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + return false; + } } + if (immediateAckRequested) + SendQuickAck (); return true; } @@ -384,7 +502,7 @@ namespace stream memset (p.buf, 0, 22); // minimal header all zeroes memcpy (p.buf + 4, packet->buf, 4); // but receiveStreamID is the sendStreamID from the ping htobe16buf (p.buf + 18, PACKET_FLAG_ECHO); // and echo flag - ssize_t payloadLen = packet->len - (packet->GetPayload () - packet->buf); + auto payloadLen = int(packet->len) - (packet->GetPayload () - packet->buf); if (payloadLen > 0) memcpy (p.buf + 22, packet->GetPayload (), payloadLen); else @@ -401,11 +519,16 @@ namespace stream bool acknowledged = false; auto ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t ackThrough = packet->GetAckThrough (); + m_NACKedPackets.clear (); if (ackThrough > m_SequenceNumber) { LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber); return; } + int rttSample = INT_MAX; + int incCounter = 0; + m_IsNAcked = false; + m_IsResendNeeded = false; int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) { @@ -418,6 +541,8 @@ namespace stream for (int i = 0; i < nackCount; i++) if (seqn == packet->GetNACK (i)) { + m_NACKedPackets.insert (*it); + m_IsNAcked = true; nacked = true; break; } @@ -429,43 +554,134 @@ namespace stream } } auto sentPacket = *it; - uint64_t rtt = ts - sentPacket->sendTime; - if(ts < sentPacket->sendTime) + int64_t rtt = (int64_t)ts - (int64_t)sentPacket->sendTime; + if (rtt < 0) + LogPrint (eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); + if (!seqn) { - LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); - rtt = 1; + m_IsFirstRttSample = true; + rttSample = rtt < 0 ? 1 : rtt; } - m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); - m_RTO = m_RTT*1.5; // TODO: implement it better + else if (!sentPacket->resent && seqn > m_TunnelsChangeSequenceNumber && rtt >= 0) + rttSample = std::min (rttSample, (int)rtt); LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; - if (m_WindowSize < WINDOW_SIZE) - m_WindowSize++; // slow start - else - { - // linear growth - if (ts > m_LastWindowSizeIncreaseTime + m_RTT) - { - m_WindowSize++; - if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; - m_LastWindowSizeIncreaseTime = ts; - } - } - if (!seqn && m_RoutingSession) // first message confirmed - m_RoutingSession->SetSharedRoutingPath ( - std::make_shared ( - i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0})); + if (m_WindowIncCounter < MAX_WINDOW_SIZE && !m_IsFirstACK && !m_IsWinDropped) + incCounter++; } else break; } - if (m_SentPackets.empty ()) + if (m_LastACKRecieveTime) + { + uint64_t interval = ts - m_LastACKRecieveTime; + if (m_ACKRecieveInterval) + m_ACKRecieveInterval = (m_ACKRecieveInterval + interval) / 2; + else + m_ACKRecieveInterval = interval; + } + m_LastACKRecieveTime = ts; + if (rttSample != INT_MAX) + { + if (m_IsFirstRttSample && !m_IsFirstACK) + { + m_RTT = rttSample; + m_SlowRTT = rttSample; + m_SlowRTT2 = rttSample; + m_PrevRTTSample = rttSample; + m_Jitter = rttSample / 10; // 10% + m_Jitter += 15; // for low-latency connections + m_IsFirstRttSample = false; + } + else + m_RTT = (m_PrevRTTSample + rttSample) / 2; + if (!m_IsWinDropped) + { + m_SlowRTT = SLOWRTT_EWMA_ALPHA * m_RTT + (1.0 - SLOWRTT_EWMA_ALPHA) * m_SlowRTT; + m_SlowRTT2 = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT2; + // calculate jitter + double jitter = 0; + if (rttSample > m_PrevRTTSample) + jitter = rttSample - m_PrevRTTSample; + else if (rttSample < m_PrevRTTSample) + jitter = m_PrevRTTSample - rttSample; + else + jitter = rttSample / 10; // 10% + jitter += 15; // for low-latency connections + m_Jitter = (0.05 * jitter) + (1.0 - 0.05) * m_Jitter; + } + if (rttSample > m_SlowRTT) + { + incCounter = 0; + m_DoubleWinIncCounter = 1; + } + else if (rttSample < m_SlowRTT) + { + if (m_DoubleWinIncCounter) + { + incCounter = incCounter * 2; + m_DoubleWinIncCounter = 0; + } + } + m_WindowIncCounter = m_WindowIncCounter + incCounter; + // + // delay-based CC + if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast, late detection + { + LogPrint (eLogDebug, "Streaming: Congestion detected, reduce window size"); + ProcessWindowDrop (); + } + UpdatePacingTime (); + m_PrevRTTSample = rttSample; + + bool wasInitial = m_RTO == INITIAL_RTO; + m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter + m_ACKRecieveInterval)); // TODO: implement it better + + if (wasInitial) + ScheduleResend (); + } + if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber) + m_IsClientChoked = false; + if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) + { + m_IsFirstRttSample = true; + m_IsWinDropped = false; + } + if (m_WindowDropTargetSize && int(m_SentPackets.size ()) <= m_WindowDropTargetSize) + { + m_WindowSize = m_WindowDropTargetSize; + m_WindowDropTargetSize = 0; + } + if (acknowledged || m_IsNAcked) + { + ScheduleResend (); + } + if (m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss + { + m_IsResendNeeded = true; + m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter + m_ACKRecieveInterval)); // to prevent spurious retransmit + } + if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) + { m_ResendTimer.cancel (); + m_SendTimer.cancel (); + m_LastACKRecieveTime = 0; + m_ACKRecieveInterval = m_AckDelay; + } + if (acknowledged && m_IsFirstACK) + { + if (m_RoutingSession) + m_RoutingSession->SetSharedRoutingPath ( + std::make_shared ( + i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0})); + m_IsFirstACK = false; + } if (acknowledged) { m_NumResendAttempts = 0; + m_IsTimeOutResend = false; SendBuffer (); } if (m_Status == eStreamStatusClosed) @@ -474,6 +690,48 @@ namespace stream Close (); // check is all outgoing messages have been sent and we can send close } + size_t Stream::Receive (uint8_t * buf, size_t len, int timeout) + { + if (!len) return 0; + size_t ret = 0; + volatile bool done = false; + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + AsyncReceive (boost::asio::buffer (buf, len), + [&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode == boost::asio::error::timed_out) + ret = 0; + else + ret = bytes_transferred; + std::unique_lock l(newDataReceivedMutex); + newDataReceived.notify_all (); + done = true; + }, + timeout); + 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(); + boost::asio::post (m_Service, [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); @@ -482,97 +740,117 @@ namespace stream void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) { + std::shared_ptr buffer; if (len > 0 && buf) - { - std::unique_lock l(m_SendBufferMutex); - m_SendBuffer.Add (buf, len, handler); - } + buffer = std::make_shared(buf, len, handler); else if (handler) handler(boost::system::error_code ()); - m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); + auto s = shared_from_this (); + boost::asio::post (m_Service, [s, buffer = std::move(buffer)]() mutable + { + if (buffer) + s->m_SendBuffer.Add (std::move(buffer)); + s->SendBuffer (); + }); } void Stream::SendBuffer () { + if (m_RemoteLeaseSet) // don't scheudle send for first SYN for incoming stream + ScheduleSend (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); int numMsgs = m_WindowSize - m_SentPackets.size (); - if (numMsgs <= 0) return; // window is full - + if (numMsgs <= 0 || !m_IsSendTime) // window is full + { + m_LastSendTime = ts; + return; + } + else if (numMsgs > m_NumPacketsToSend) + numMsgs = m_NumPacketsToSend; bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; + while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) { - std::unique_lock l(m_SendBufferMutex); - while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) + Packet * p = m_LocalDestination.NewPacket (); + uint8_t * packet = p->GetBuffer (); + // TODO: implement setters + size_t size = 0; + htobe32buf (packet + size, m_SendStreamID); + size += 4; // sendStreamID + htobe32buf (packet + size, m_RecvStreamID); + size += 4; // receiveStreamID + htobe32buf (packet + size, m_SequenceNumber++); + size += 4; // sequenceNum + if (isNoAck) + htobuf32 (packet + size, 0); + else + htobe32buf (packet + size, m_LastReceivedSequenceNumber); + size += 4; // ack Through + if (m_Status == eStreamStatusNew && !m_SendStreamID && m_RemoteIdentity) { - Packet * p = m_LocalDestination.NewPacket (); - uint8_t * packet = p->GetBuffer (); - // TODO: implement setters - size_t size = 0; - htobe32buf (packet + size, m_SendStreamID); - size += 4; // sendStreamID - htobe32buf (packet + size, m_RecvStreamID); - size += 4; // receiveStreamID - htobe32buf (packet + size, m_SequenceNumber++); - size += 4; // sequenceNum - if (isNoAck) - htobuf32 (packet + size, 0); - else - htobe32buf (packet + size, m_LastReceivedSequenceNumber); - size += 4; // ack Through + // first SYN packet + packet[size] = 8; + size++; // NACK count + memcpy (packet + size, m_RemoteIdentity->GetIdentHash (), 32); + size += 32; + } + else + { packet[size] = 0; size++; // NACK count - packet[size] = m_RTO/1000; - size++; // resend delay - if (m_Status == eStreamStatusNew) + } + packet[size] = m_RTO/1000; + size++; // resend delay + if (m_Status == eStreamStatusNew) + { + // initial packet + m_Status = eStreamStatusOpen; + if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; + if (m_RemoteLeaseSet) { - // initial packet - m_Status = eStreamStatusOpen; - if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; - if (m_RemoteLeaseSet) - { - m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); - m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; - } - uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | - PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; - if (isNoAck) flags |= PACKET_FLAG_NO_ACK; - bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); - if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; - htobe16buf (packet + size, flags); - size += 2; // flags - size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); - size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); - uint8_t * optionsSize = packet + size; // set options size later - size += 2; // options size - m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); - size += identityLen; // from - htobe16buf (packet + size, m_MTU); - size += 2; // max packet size - if (isOfflineSignature) - { - const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); - memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); - size += offlineSignature.size (); // offline signature - } - uint8_t * signature = packet + size; // set it later - memset (signature, 0, signatureLen); // zeroes for now - size += signatureLen; // signature - htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size - size += m_SendBuffer.Get (packet + size, m_MTU); // payload - m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming); + m_MTU = (m_RoutingSession && m_RoutingSession->IsRatchets ()) ? STREAMING_MTU_RATCHETS : STREAMING_MTU; } - else + uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | + PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; + if (isNoAck) flags |= PACKET_FLAG_NO_ACK; + bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); + if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + uint8_t * optionsSize = packet + size; // set options size later + size += 2; // options size + m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); + size += identityLen; // from + htobe16buf (packet + size, m_MTU); + size += 2; // max packet size + if (isOfflineSignature) { - // follow on packet - htobuf16 (packet + size, 0); - size += 2; // flags - htobuf16 (packet + size, 0); // no options - size += 2; // options size - size += m_SendBuffer.Get(packet + size, m_MTU); // payload + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature } - p->len = size; - packets.push_back (p); - numMsgs--; + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + size += m_SendBuffer.Get (packet + size, m_MTU); // payload + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } + else + { + // follow on packet + htobuf16 (packet + size, 0); + size += 2; // flags + htobuf16 (packet + size, 0); // no options + size += 2; // options size + size += m_SendBuffer.Get(packet + size, m_MTU); // payload + } + p->len = size; + packets.push_back (p); + numMsgs--; } if (packets.size () > 0) { @@ -582,13 +860,15 @@ namespace stream m_AckSendTimer.cancel (); } bool isEmpty = m_SentPackets.empty (); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); +// auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto& it: packets) { it->sendTime = ts; m_SentPackets.insert (it); } SendPackets (packets); + m_LastSendTime = ts; + m_IsSendTime = false; if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) SendClose (); if (isEmpty) @@ -599,10 +879,53 @@ namespace stream void Stream::SendQuickAck () { int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber; + // for limit inbound speed + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + int numPackets = 0; + bool lostPackets = false; + int64_t passedTime = m_PacketACKInterval * INITIAL_WINDOW_SIZE; // in microseconds // while m_LastACKSendTime == 0 + if (m_LastACKSendTime) + passedTime = (ts - m_LastACKSendTime)*1000; // in microseconds + numPackets = (passedTime + m_PacketACKIntervalRem) / m_PacketACKInterval; + m_PacketACKIntervalRem = (passedTime + m_PacketACKIntervalRem) - (numPackets * m_PacketACKInterval); + if (m_LastConfirmedReceivedSequenceNumber + numPackets < m_LastReceivedSequenceNumber) + { + lastReceivedSeqn = m_LastConfirmedReceivedSequenceNumber + numPackets; + if (!m_IsAckSendScheduled) + { + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } + } + if (numPackets == 0) return; + // for limit inbound speed if (!m_SavedPackets.empty ()) { - int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn (); - if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn; + for (auto it: m_SavedPackets) + { + auto seqn = it->GetSeqn (); + // for limit inbound speed + if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) + { + if (!m_IsAckSendScheduled) + { + auto ackTimeout = m_RTT/10; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + ScheduleAck (ackTimeout); + } + if (lostPackets) + break; + else + return; + } + // for limit inbound speed + if ((int)seqn > lastReceivedSeqn) + { + lastReceivedSeqn = seqn; + lostPackets = true; // for limit inbound speed + } + } } if (lastReceivedSeqn < 0) { @@ -622,6 +945,7 @@ namespace stream htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; + bool choking = false; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs @@ -630,10 +954,16 @@ namespace stream for (auto it: m_SavedPackets) { auto seqn = it->GetSeqn (); + if (m_LastConfirmedReceivedSequenceNumber + numPackets < int(seqn)) // for limit inbound speed + { + htobe32buf (packet + 12, nextSeqn - 1); + break; + } if (numNacks + (seqn - nextSeqn) >= 256) { LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); - htobe32buf (packet + 12, nextSeqn); // change ack Through + htobe32buf (packet + 12, nextSeqn - 1); // change ack Through back + choking = true; break; } for (uint32_t i = nextSeqn; i < seqn; i++) @@ -655,14 +985,32 @@ namespace stream size++; // NACK count } packet[size] = 0; - size++; // resend delay - htobuf16 (packet + size, 0); // no flags set + size++; // resend delay + bool requestImmediateAck = false; + if (!choking) + requestImmediateAck = m_LastSendTime && ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL && + ts > m_LastSendTime + REQUEST_IMMEDIATE_ACK_INTERVAL + m_LocalDestination.GetRandom () % REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE; + htobe16buf (packet + size, (choking || requestImmediateAck) ? PACKET_FLAG_DELAY_REQUESTED : 0); // no flags set or delay requested size += 2; // flags - htobuf16 (packet + size, 0); // no options + if (choking || requestImmediateAck) + { + htobe16buf (packet + size, 2); // 2 bytes delay interval + htobe16buf (packet + size + 2, choking ? DELAY_CHOKING : 0); // set choking or immediate ack interval + size += 2; + if (requestImmediateAck) // ack request sent + { + m_LastSendTime = ts; + m_IsImmediateAckRequested = true; + } + } + else + htobuf16 (packet + size, 0); // no options size += 2; // options size p.len = size; SendPackets (std::vector { &p }); + m_LastACKSendTime = ts; // for limit inbound speed + m_LastConfirmedReceivedSequenceNumber = lastReceivedSeqn; // for limit inbound speed LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } @@ -729,7 +1077,7 @@ namespace stream Terminate (); break; default: - LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: Unexpected stream status=", (int)m_Status, " for sSID=", m_SendStreamID); }; } @@ -761,7 +1109,7 @@ namespace stream m_LocalDestination.GetOwner ()->Sign (packet, size, signature); p->len = size; - m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); + boost::asio::post (m_Service, std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } @@ -793,6 +1141,7 @@ namespace stream m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } + if (!packet->sendTime) packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); SendPackets (std::vector { packet }); bool isEmpty = m_SentPackets.empty (); m_SentPackets.insert (packet); @@ -808,6 +1157,7 @@ namespace stream { if (!m_RemoteLeaseSet) { + CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { @@ -816,7 +1166,15 @@ namespace stream } } if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent - m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); + { + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming || m_SequenceNumber > 1); + if (!m_RoutingSession) + { + LogPrint (eLogError, "Streaming: Can't obtain routing session, sSID=", m_SendStreamID); + Terminate (); + return; + } + } if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first @@ -826,36 +1184,65 @@ namespace stream m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; - m_RTO = m_RTT*1.5; // TODO: implement it better } } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet - ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) + if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate) // excluded from LeaseSet + { + CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (true); + } + if (m_RemoteLeaseChangeTime && m_IsRemoteLeaseChangeInProgress && ts > m_RemoteLeaseChangeTime + INITIAL_RTO) + { + LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); + CancelRemoteLeaseChange (); + m_CurrentRemoteLease = m_NextRemoteLease; + ResetWindowSize (); + } + auto currentRemoteLease = m_CurrentRemoteLease; + if (!m_IsRemoteLeaseChangeInProgress && m_RemoteLeaseSet && m_CurrentRemoteLease && ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) + { + auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); + if (leases.size ()) + { + m_IsRemoteLeaseChangeInProgress = true; + UpdateCurrentRemoteLease (true); + m_NextRemoteLease = m_CurrentRemoteLease; + } + else + UpdateCurrentRemoteLease (true); + } if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { + bool freshTunnel = false; if (!m_CurrentOutboundTunnel) { auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr, leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + freshTunnel = true; } else if (!m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + std::tie(m_CurrentOutboundTunnel, freshTunnel) = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); m_CurrentRemoteLease = nullptr; return; } + if (freshTunnel) + { + LogPrint (eLogDebug, "Streaming: OutboundTunnel changed, set initial window size"); + ResetWindowSize (); +// m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely + } std::vector msgs; for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( - it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); + it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets (), it->IsSYN ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -863,8 +1250,13 @@ namespace stream msg }); m_NumSentBytes += it->GetLength (); + if (m_IsRemoteLeaseChangeInProgress && !m_RemoteLeaseChangeTime) + { + m_RemoteLeaseChangeTime = ts; + m_CurrentRemoteLease = currentRemoteLease; // change it back before new lease is confirmed + } } - m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); + m_CurrentOutboundTunnel->SendTunnelDataMsgs (msgs); } else { @@ -881,10 +1273,10 @@ namespace stream if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) + if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels - LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); + LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; @@ -901,6 +1293,103 @@ namespace stream SendQuickAck (); } + void Stream::ScheduleSend () + { + if (m_Status != eStreamStatusTerminated) + { + m_SendTimer.cancel (); + m_SendTimer.expires_from_now (boost::posix_time::microseconds( + SEND_INTERVAL + m_LocalDestination.GetRandom () % SEND_INTERVAL_VARIANCE)); + m_SendTimer.async_wait (std::bind (&Stream::HandleSendTimer, + shared_from_this (), std::placeholders::_1)); + } + } + + void Stream::HandleSendTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (m_LastSendTime && ts*1000 > m_LastSendTime*1000 + m_PacingTime) + { + if (m_PacingTime) + { + auto numPackets = std::lldiv (m_PacingTimeRem + ts*1000 - m_LastSendTime*1000, m_PacingTime); + m_NumPacketsToSend = numPackets.quot; + m_PacingTimeRem = numPackets.rem; + } + else + { + LogPrint (eLogError, "Streaming: pacing time is zero"); + m_NumPacketsToSend = 1; m_PacingTimeRem = 0; + } + m_IsSendTime = true; + if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT) + { + float winSize = m_WindowSize; + if (m_WindowDropTargetSize) + winSize = m_WindowDropTargetSize; + float maxWinSize = MAX_WINDOW_SIZE; + if (m_LastWindowIncTime) + maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize; + for (int i = 0; i < m_NumPacketsToSend; i++) + { + if (m_WindowIncCounter) + { + if (m_WindowDropTargetSize) + { + if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize)) + m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here + else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize)) + m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here + else + m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; + if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE; + m_WindowIncCounter--; + if (m_WindowDropTargetSize >= maxWinSize) + { + m_WindowDropTargetSize = maxWinSize; + break; + } + } + else + { + if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowSize)) + m_WindowSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowSize)); // some magic here + else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize)) + m_WindowSize += (m_WindowSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; // some magic here + else + m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; + if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; + m_WindowIncCounter--; + if (m_WindowSize >= maxWinSize) + { + m_WindowSize = maxWinSize; + break; + } + } + } + else + break; + } + m_LastWindowIncTime = ts; + UpdatePacingTime (); + } + else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) + { + m_WindowSizeTail = m_WindowSizeTail + m_WindowIncCounter; + if (m_WindowSizeTail > MAX_WINDOW_SIZE) m_WindowSizeTail = MAX_WINDOW_SIZE; + } + if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) // resend packets + ResendPacket (); + else if (m_WindowSize > int(m_SentPackets.size ())) // send packets + SendBuffer (); + } + else // pass + ScheduleSend (); + } + } + void Stream::ScheduleResend () { if (m_Status != eStreamStatusTerminated) @@ -918,63 +1407,137 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { - // check for resend attempts - if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) - { - LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); - m_Status = eStreamStatusReset; - Close (); - return; - } + m_IsSendTime = true; + if (m_RTO > INITIAL_RTO) m_RTO = INITIAL_RTO; + m_SendTimer.cancel (); // if no ack's in RTO, disable fast retransmit + m_IsTimeOutResend = true; + m_IsNAcked = false; + m_IsResendNeeded = false; + m_NumPacketsToSend = 1; + ResendPacket (); // send one packet per RTO, waiting for ack + } + } + + void Stream::ResendPacket () + { + // check for resend attempts + if (m_IsIncoming && m_SequenceNumber == 1 && m_NumResendAttempts > 0) + { + LogPrint (eLogWarning, "Streaming: SYNACK packet was not ACKed after ", m_NumResendAttempts, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + m_Status = eStreamStatusReset; + Close (); + return; + } + if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) + { + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + m_Status = eStreamStatusReset; + Close (); + return; + } - // collect packets to resend - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - std::vector packets; + // collect packets to resend + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + std::vector packets; + if (m_IsNAcked) + { + for (auto it : m_NACKedPackets) + { + if (ts >= it->sendTime + m_RTO) + { + if (ts < it->sendTime + m_RTO*2) + it->resent = true; + else + it->resent = false; + it->sendTime = ts; + packets.push_back (it); + if ((int)packets.size () >= m_NumPacketsToSend) break; + } + } + } + else + { for (auto it : m_SentPackets) { if (ts >= it->sendTime + m_RTO) { + if (ts < it->sendTime + m_RTO*2) + it->resent = true; + else + it->resent = false; it->sendTime = ts; packets.push_back (it); + if ((int)packets.size () >= m_NumPacketsToSend) break; } } - - // select tunnels if necessary and send - if (packets.size () > 0) - { - m_NumResendAttempts++; - m_RTO *= 2; - switch (m_NumResendAttempts) - { - case 1: // congesion avoidance - m_WindowSize >>= 1; // /2 - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - break; - case 2: - m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time -#if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; -#endif - // no break here - case 4: - if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); - UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); - break; - case 3: - // pick another outbound tunnel - if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); - LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); - break; - default: ; - } - SendPackets (packets); - } - ScheduleResend (); } + + // select tunnels if necessary and send + if (packets.size () > 0 && m_IsSendTime) + { + if (m_IsNAcked) m_NumResendAttempts = 1; + else if (m_IsTimeOutResend) m_NumResendAttempts++; + if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) + { + // loss-based CC + if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED && !m_IsClientChoked) + { + LogPrint (eLogDebug, "Streaming: Packet loss, reduce window size"); + ProcessWindowDrop (); + } + } + else if (m_IsTimeOutResend) + { + m_IsTimeOutResend = false; + m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change + m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsWinDropped = true; + m_IsFirstRttSample = true; + m_DropWindowDelaySequenceNumber = 0; + m_IsFirstACK = true; + m_LastACKRecieveTime = 0; + m_ACKRecieveInterval = m_AckDelay; + UpdatePacingTime (); + if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); + if (m_NumResendAttempts & 1) + { + // pick another outbound tunnel + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, + ", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); + } + else + { + CancelRemoteLeaseChange (); + UpdateCurrentRemoteLease (); // pick another lease + LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, + ", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + } + } + SendPackets (packets); + m_LastSendTime = ts; + m_IsSendTime = false; + if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) ScheduleSend (); + } + else if (!m_IsClientChoked) + SendBuffer (); + if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); + if (m_IsClientChoked) ScheduleSend (); } + void Stream::ScheduleAck (int timeout) + { + if (m_IsAckSendScheduled) + m_AckSendTimer.cancel (); + m_IsAckSendScheduled = true; + if (timeout < MIN_SEND_ACK_TIMEOUT) timeout = MIN_SEND_ACK_TIMEOUT; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(timeout)); + m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, + shared_from_this (), std::placeholders::_1)); + } + void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) @@ -990,9 +1553,13 @@ namespace stream { if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ()) { - // seems something went wrong and we should re-select tunnels - m_CurrentOutboundTunnel = nullptr; - m_CurrentRemoteLease = nullptr; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT) + { + // seems something went wrong and we should re-select tunnels + m_CurrentOutboundTunnel = nullptr; + m_CurrentRemoteLease = nullptr; + } } SendQuickAck (); } @@ -1002,23 +1569,33 @@ namespace stream void Stream::UpdateCurrentRemoteLease (bool expired) { + bool isLeaseChanged = true; if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!remoteLeaseSet) { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); - if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) - { - m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( - std::make_shared(m_RemoteIdentity)); - return; // we keep m_RemoteLeaseSet for possible next request + if (!m_IsIncoming) // outgoing + { + if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) + { + m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( + std::make_shared(m_RemoteIdentity)); + return; // we keep m_RemoteLeaseSet for possible next request + } + else + { + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + } } - else + else // incoming { - m_RemoteLeaseSet = nullptr; - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt - } + // just close the socket without sending FIN or RST + m_Status = eStreamStatusClosed; + AsyncClose (); + } } else { @@ -1059,10 +1636,15 @@ namespace stream } if (!updated) { - uint32_t i = rand () % leases.size (); + uint32_t i = m_LocalDestination.GetRandom () % leases.size (); if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) + { // make sure we don't select previous - i = (i + 1) % leases.size (); // if so, pick next + if (leases.size () > 1) + i = (i + 1) % leases.size (); // if so, pick next + else + isLeaseChanged = false; + } m_CurrentRemoteLease = leases[i]; } } @@ -1079,14 +1661,93 @@ namespace stream LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; } + if (isLeaseChanged && !m_IsRemoteLeaseChangeInProgress) + { + LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); + ResetWindowSize (); + } + } + + void Stream::ResetRoutingPath () + { + m_CurrentOutboundTunnel = nullptr; + m_CurrentRemoteLease = nullptr; + m_RTT = INITIAL_RTT; + m_RTO = INITIAL_RTO; + if (m_RoutingSession) + m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures + } + + void Stream::UpdatePacingTime () + { + if (m_WindowDropTargetSize) + m_PacingTime = std::round (m_RTT*1000/m_WindowDropTargetSize); + else + m_PacingTime = std::round (m_RTT*1000/m_WindowSize); + if (m_MinPacingTime && m_PacingTime < m_MinPacingTime) + m_PacingTime = m_MinPacingTime; + } + + void Stream::ProcessWindowDrop () + { + if (m_WindowDropTargetSize) + m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue + else + { + if (m_WindowSize < m_LastWindowDropSize) + { + m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize))); + if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE; + } + else + { + m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2)); + if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE; + } + m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue + } + if (m_WindowDropTargetSize < MIN_WINDOW_SIZE) + m_WindowDropTargetSize = MIN_WINDOW_SIZE; + m_WindowIncCounter = 0; // disable window growth + m_DropWindowDelaySequenceNumber = m_SequenceNumber + int(m_WindowDropTargetSize); + m_IsFirstACK = true; // ignore first RTT sample + m_IsWinDropped = true; // don't drop window twice + m_WindowSizeTail = 0; + UpdatePacingTime (); + } + + void Stream::ResetWindowSize () + { + m_RTO = INITIAL_RTO; + if (!m_IsClientChoked) + { + if (m_WindowSize > INITIAL_WINDOW_SIZE) + { + m_WindowDropTargetSize = (float)INITIAL_WINDOW_SIZE; + m_IsWinDropped = true; + } + else + m_WindowSize = INITIAL_WINDOW_SIZE; + } + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsFirstRttSample = true; + m_IsFirstACK = true; + m_WindowSizeTail = 0; + UpdatePacingTime (); + } + + void Stream::CancelRemoteLeaseChange () + { + m_RemoteLeaseChangeTime = 0; + m_IsRemoteLeaseChangeInProgress = false; } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), - m_PendingIncomingTimer (m_Owner->GetService ()) + m_PendingIncomingTimer (m_Owner->GetService ()), + m_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ()) { - if (m_Gzip) - m_Deflator.reset (new i2p::data::GzipDeflator); } StreamingDestination::~StreamingDestination () @@ -1163,12 +1824,24 @@ namespace stream { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); + it1->second->ResetRoutingPath (); // Ack was not delivered, changing path DeletePacket (packet); // drop it, because previous should be connected return; } + if (m_Owner->GetStreamingMaxConcurrentStreams () > 0 && (int)m_Streams.size () > m_Owner->GetStreamingMaxConcurrentStreams ()) + { + LogPrint(eLogWarning, "Streaming: Number of streams exceeds ", m_Owner->GetStreamingMaxConcurrentStreams ()); + DeletePacket (packet); + return; + } auto incomingStream = CreateNewIncomingStream (receiveStreamID); incomingStream->HandleNextPacket (packet); // SYN - auto ident = incomingStream->GetRemoteIdentity(); + if (!incomingStream->GetRemoteLeaseSet ()) + { + LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated"); + incomingStream->Terminate (); // can't send FIN anyway + return; + } // handle saved packets if any { @@ -1270,13 +1943,16 @@ namespace stream { std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); - m_IncomingStreams.erase (stream->GetSendStreamID ()); + if (stream->IsIncoming ()) + m_IncomingStreams.erase (stream->GetSendStreamID ()); if (m_LastStream == stream) m_LastStream = nullptr; } - if (m_Streams.empty ()) + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (m_Streams.empty () || ts > m_LastCleanupTime + STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL) { m_PacketsPool.CleanUp (); m_I2NPMsgsPool.CleanUp (); + m_LastCleanupTime = ts; } } @@ -1286,7 +1962,7 @@ namespace stream if (it == m_Streams.end ()) return false; auto s = it->second; - m_Owner->GetService ().post ([this, s] () + boost::asio::post (m_Owner->GetService (), [this, s] () { s->Close (); // try to send FIN s->Terminate (false); @@ -1299,7 +1975,7 @@ namespace stream { m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet auto s = shared_from_this (); - m_Owner->GetService ().post([s](void) + boost::asio::post (m_Owner->GetService (), [s](void) { // take care about incoming queue for (auto& it: s->m_PendingIncomingStreams) @@ -1318,7 +1994,7 @@ namespace stream void StreamingDestination::AcceptOnce (const Acceptor& acceptor) { - m_Owner->GetService ().post([acceptor, this](void) + boost::asio::post (m_Owner->GetService (), [acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) { @@ -1341,6 +2017,26 @@ namespace stream acceptor (stream); } + std::shared_ptr StreamingDestination::AcceptStream (int timeout) + { + std::shared_ptr stream; + std::condition_variable streamAccept; + std::mutex streamAcceptMutex; + std::unique_lock l(streamAcceptMutex); + AcceptOnce ( + [&streamAccept, &streamAcceptMutex, &stream](std::shared_ptr s) + { + stream = s; + std::unique_lock l(streamAcceptMutex); + streamAccept.notify_all (); + }); + if (timeout) + streamAccept.wait_for (l, std::chrono::seconds (timeout)); + else + streamAccept.wait (l); + return stream; + } + void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) @@ -1365,16 +2061,16 @@ namespace stream } std::shared_ptr StreamingDestination::CreateDataMessage ( - const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) + const uint8_t * payload, size_t len, uint16_t toPort, bool checksum, bool gzip) { size_t size; - auto msg = m_I2NPMsgsPool.AcquireShared (); + auto msg = (len <= STREAMING_MTU_RATCHETS) ? m_I2NPMsgsPool.AcquireShared () : NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; - if (m_Gzip && m_Deflator) - size = m_Deflator->Deflate (payload, len, buf, msg->maxLen - msg->len); + if (m_Gzip || gzip) + size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); else size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); @@ -1392,5 +2088,15 @@ namespace stream return msg; } + uint32_t StreamingDestination::GetRandom () + { + if (m_Owner) + { + auto pool = m_Owner->GetTunnelPool (); + if (pool) + return pool->GetRng ()(); + } + return rand (); + } } } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 088c2e11..570fdd1d 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,7 @@ #include #include #include "Base.h" +#include "Gzip.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" @@ -50,29 +51,47 @@ namespace stream const size_t STREAMING_MTU = 1730; const size_t STREAMING_MTU_RATCHETS = 1812; +#if OPENSSL_PQ + const size_t MAX_PACKET_SIZE = 8192; +#else const size_t MAX_PACKET_SIZE = 4096; +#endif const size_t COMPRESSION_THRESHOLD_SIZE = 66; - const int MAX_NUM_RESEND_ATTEMPTS = 6; - const int WINDOW_SIZE = 6; // in messages - const int MIN_WINDOW_SIZE = 1; - const int MAX_WINDOW_SIZE = 128; - const int INITIAL_RTT = 8000; // in milliseconds + const int MAX_NUM_RESEND_ATTEMPTS = 10; + const int INITIAL_WINDOW_SIZE = 10; + const int MIN_WINDOW_SIZE = 3; + const int MAX_WINDOW_SIZE = 512; + const int MAX_WINDOW_SIZE_INC_PER_RTT = 16; + const double RTT_EWMA_ALPHA = 0.25; + const double SLOWRTT_EWMA_ALPHA = 0.05; + const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer + const int MIN_RTO = 20; // in milliseconds + const int INITIAL_RTT = 1500; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds + const int INITIAL_PACING_TIME = 1000 * INITIAL_RTT / INITIAL_WINDOW_SIZE; // in microseconds const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds - const size_t MAX_PENDING_INCOMING_BACKLOG = 128; + const size_t MAX_PENDING_INCOMING_BACKLOG = 1024; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds const int MAX_RECEIVE_TIMEOUT = 20; // in seconds - + const uint16_t DELAY_CHOKING = 60000; // in milliseconds + const uint64_t SEND_INTERVAL = 10000; // in microseconds + const uint64_t SEND_INTERVAL_VARIANCE = 2000; // in microseconds + const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds + const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds + const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1 + const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds + struct Packet { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; uint64_t sendTime; + bool resent; - Packet (): len (0), offset (0), sendTime (0) {}; + Packet (): len (0), offset (0), sendTime (0), resent (false) {}; uint8_t * GetBuffer () { return buf + offset; }; - size_t GetLength () const { return len - offset; }; + size_t GetLength () const { return len > offset ? len - offset : 0; }; uint32_t GetSendStreamID () const { return bufbe32toh (buf); }; uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); }; @@ -80,6 +99,7 @@ namespace stream uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); }; uint8_t GetNACKCount () const { return buf[16]; }; uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); }; + const uint8_t * GetNACKs () const { return buf + 17; }; const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); }; uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); }; @@ -134,8 +154,7 @@ namespace stream SendBufferQueue (): m_Size (0) {}; ~SendBufferQueue () { CleanUp (); }; - void Add (const uint8_t * buf, size_t len, SendHandler handler); - void Add (std::shared_ptr buf); + void Add (std::shared_ptr&& buf); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; @@ -162,9 +181,9 @@ namespace stream { public: - Stream (boost::asio::io_service& service, StreamingDestination& local, + Stream (boost::asio::io_context& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing - Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming + Stream (boost::asio::io_context& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; @@ -173,8 +192,10 @@ namespace stream std::shared_ptr GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; + bool IsIncoming () const { return m_IsIncoming; }; StreamStatus GetStatus () const { return m_Status; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; + void ResetRoutingPath (); void HandleNextPacket (Packet * packet); void HandlePing (Packet * packet); @@ -185,8 +206,9 @@ namespace stream template 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())); }; + void AsyncClose() { boost::asio::post(m_Service, std::bind(&Stream::Close, shared_from_this())); }; /** only call close from destination thread, use Stream::AsyncClose for other threads */ void Close (); @@ -222,38 +244,69 @@ namespace stream void UpdateCurrentRemoteLease (bool expired = false); template - void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout); + void HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout); + void ScheduleSend (); + void HandleSendTimer (const boost::system::error_code& ecode); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); + void ResendPacket (); + void ScheduleAck (int timeout); void HandleAckSendTimer (const boost::system::error_code& ecode); + void UpdatePacingTime (); + void ProcessWindowDrop (); + void ResetWindowSize (); + void CancelRemoteLeaseChange (); + private: - boost::asio::io_service& m_Service; + boost::asio::io_context& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; + uint32_t m_DropWindowDelaySequenceNumber; + uint32_t m_TunnelsChangeSequenceNumber; int32_t m_LastReceivedSequenceNumber; + int32_t m_PreviousReceivedSequenceNumber; + int32_t m_LastConfirmedReceivedSequenceNumber; // for limit inbound speed StreamStatus m_Status; + bool m_IsIncoming; bool m_IsAckSendScheduled; + bool m_IsNAcked; + bool m_IsFirstACK; + bool m_IsResendNeeded; + bool m_IsFirstRttSample; + bool m_IsSendTime; + bool m_IsWinDropped; + bool m_IsClientChoked; + bool m_IsTimeOutResend; + bool m_IsImmediateAckRequested; + bool m_IsRemoteLeaseChangeInProgress; + bool m_DoubleWinIncCounter; StreamingDestination& m_LocalDestination; std::shared_ptr m_RemoteIdentity; std::shared_ptr m_TransientVerifier; // in case of offline key std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; std::shared_ptr m_CurrentRemoteLease; + std::shared_ptr m_NextRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; std::set m_SentPackets; - boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer; + std::set m_NACKedPackets; + boost::asio::deadline_timer m_ReceiveTimer, m_SendTimer, m_ResendTimer, m_AckSendTimer; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; - std::mutex m_SendBufferMutex; SendBufferQueue m_SendBuffer; - int m_WindowSize, m_RTT, m_RTO, m_AckDelay; - uint64_t m_LastWindowSizeIncreaseTime; - int m_NumResendAttempts; + double m_RTT, m_SlowRTT, m_SlowRTT2; + float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize; + int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail; + double m_Jitter; + uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds + m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime; // milliseconds + uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed + int m_NumResendAttempts, m_NumPacketsToSend; size_t m_MTU; }; @@ -273,21 +326,24 @@ namespace stream void SendPing (std::shared_ptr remote); void DeleteStream (std::shared_ptr stream); bool DeleteStream (uint32_t recvStreamID); + size_t GetNumStreams () const { return m_Streams.size (); }; void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; 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; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true, bool gzip = false); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } + uint32_t GetRandom (); private: @@ -310,12 +366,13 @@ namespace stream std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; - i2p::util::MemoryPool > m_I2NPMsgsPool; - + i2p::util::MemoryPool > m_I2NPMsgsPool; + uint64_t m_LastCleanupTime; // in seconds + public: i2p::data::GzipInflator m_Inflator; - std::unique_ptr m_Deflator; + i2p::data::GzipDeflator m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; @@ -327,7 +384,7 @@ namespace stream void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout) { auto s = shared_from_this(); - m_Service.post ([s, buffer, handler, timeout](void) + boost::asio::post (m_Service, [s, buffer, handler, timeout](void) { if (!s->m_ReceiveQueue.empty () || s->m_Status == eStreamStatusReset) s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0); @@ -336,20 +393,19 @@ namespace stream int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); int left = timeout - t; - auto self = s->shared_from_this(); - self->m_ReceiveTimer.async_wait ( - [self, buffer, handler, left](const boost::system::error_code & ec) + s->m_ReceiveTimer.async_wait ( + [s, buffer, handler, left](const boost::system::error_code & ec) { - self->HandleReceiveTimer(ec, buffer, handler, left); + s->HandleReceiveTimer(ec, buffer, handler, left); }); } }); } template - void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout) + void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { - size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); + size_t received = ConcatenatePackets ((uint8_t *)buffer.data (), buffer.size ()); if (received > 0) handler (boost::system::error_code (), received); else if (ecode == boost::asio::error::operation_aborted) diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index d898395f..30b7708d 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,10 +12,14 @@ #include #include #include +#include +#include #include "Base.h" -namespace i2p { -namespace data { +namespace i2p +{ +namespace data +{ template class Tag { @@ -58,28 +62,31 @@ namespace data { std::string ToBase64 (size_t len = sz) const { - char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2); - return std::string (str, str + l); + return i2p::data::ByteStreamToBase64 (m_Buf, len); } std::string ToBase32 (size_t len = sz) const { - char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2); - return std::string (str, str + l); + return i2p::data::ByteStreamToBase32 (m_Buf, len); } - size_t FromBase32 (const std::string& s) + size_t FromBase32 (std::string_view s) { - return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + return i2p::data::Base32ToByteStream (s, m_Buf, sz); } - size_t FromBase64 (const std::string& s) + size_t FromBase64 (std::string_view s) { - return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + return i2p::data::Base64ToByteStream (s, 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/Timestamp.cpp b/libi2pd/Timestamp.cpp index 99507398..a22e9bde 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -60,18 +60,16 @@ namespace util static void SyncTimeWithNTP (const std::string& address) { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); - boost::asio::io_service service; + boost::asio::io_context service; boost::system::error_code ec; - auto it = boost::asio::ip::udp::resolver (service).resolve ( - boost::asio::ip::udp::resolver::query (address, "ntp"), ec); + auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec); if (!ec) { bool found = false; - boost::asio::ip::udp::resolver::iterator end; boost::asio::ip::udp::endpoint ep; - while (it != end) + for (const auto& it: endpoints) { - ep = *it; + ep = it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) @@ -88,7 +86,6 @@ namespace util } } if (found) break; - it++; } if (!found) { @@ -154,7 +151,7 @@ namespace util { m_IsRunning = true; LogPrint(eLogInfo, "Timestamp: NTP time sync starting"); - m_Service.post (std::bind (&NTPTimeSync::Sync, this)); + boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this)); m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this))); } else @@ -232,11 +229,34 @@ namespace util return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; } + uint64_t GetMonotonicMicroseconds() + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } + + uint64_t GetMonotonicMilliseconds() + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } + + uint64_t GetMonotonicSeconds () + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } + void GetCurrentDate (char * date) { GetDateString (GetSecondsSinceEpoch (), date); } + void GetNextDayDate (char * date) + { + GetDateString (GetSecondsSinceEpoch () + 24*60*60, date); + } + void GetDateString (uint64_t timestamp, char * date) { using clock = std::chrono::system_clock; diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 995ea36f..00c60433 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,8 +24,13 @@ namespace util uint32_t GetMinutesSinceEpoch (); uint32_t GetHoursSinceEpoch (); - void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes - void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes + uint64_t GetMonotonicMicroseconds (); + uint64_t GetMonotonicMilliseconds (); + uint64_t GetMonotonicSeconds (); + + void GetCurrentDate (char * date); // returns UTC date as YYYYMMDD string, 9 bytes + void GetNextDayDate (char * date); // returns next UTC day as YYYYMMDD string, 9 bytes + void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes void AdjustTimeOffset (int64_t offset); // in seconds from current class NTPTimeSync @@ -47,7 +52,7 @@ namespace util bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_service m_Service; + boost::asio::io_context m_Service; boost::asio::deadline_timer m_Timer; int m_SyncInterval; std::vector m_NTPServersList; diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index c4f3fa19..b24c8ac5 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,9 +8,14 @@ #include #include "I2PEndian.h" +#include "Crypto.h" #include "Log.h" +#include "Identity.h" +#include "RouterInfo.h" #include "RouterContext.h" #include "I2NPProtocol.h" +#include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "Tunnel.h" #include "Transports.h" #include "TransitTunnel.h" @@ -20,19 +25,39 @@ 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); } + std::string TransitTunnel::GetNextPeerName () const + { + return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()); + } + + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) + { + LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); + } + + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + { + LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); + } + TransitTunnelParticipant::~TransitTunnelParticipant () { } @@ -54,48 +79,103 @@ namespace tunnel auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); - i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); - m_TunnelDataMsgs.clear (); + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear } } - void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) + std::string TransitTunnelParticipant::GetNextPeerName () const { - LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); - } - - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) - { - LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); - } - + if (m_Sender) + { + auto transport = m_Sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.SendBuffer (); } + std::string TransitTunnelGateway::GetNextPeerName () const + { + const auto& sender = m_Gateway.GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + std::lock_guard l(m_HandleMutex); + if (!m_Endpoint) m_Endpoint = std::make_unique(false); // transit endpoint is always outbound + m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg); } + void TransitTunnelEndpoint::FlushTunnelDataMsgs () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->FlushI2NPMsgs (); + } + } + + void TransitTunnelEndpoint::Cleanup () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->Cleanup (); + } + } + + std::string TransitTunnelEndpoint::GetNextPeerName () const + { + if (!m_Endpoint) return ""; + auto hash = m_Endpoint->GetCurrentHash (); + if (hash) + { + const auto& sender = m_Endpoint->GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return i2p::data::GetIdentHashAbbreviation (*hash) + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + else + return i2p::data::GetIdentHashAbbreviation (*hash); + } + } + return ""; + } + std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - const 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) @@ -114,5 +194,440 @@ namespace tunnel return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } + + TransitTunnels::TransitTunnels (): + m_IsRunning (false), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) + { + } + + TransitTunnels::~TransitTunnels () + { + Stop (); + } + + void TransitTunnels::Start () + { + m_IsRunning = true; + m_Thread.reset (new std::thread (std::bind (&TransitTunnels::Run, this))); + } + + void TransitTunnels::Stop () + { + m_IsRunning = false; + m_TunnelBuildMsgQueue.WakeUp (); + if (m_Thread) + { + m_Thread->join (); + m_Thread = nullptr; + } + m_TransitTunnels.clear (); + } + + void TransitTunnels::Run () + { + i2p::util::SetThreadName("TBM"); + uint64_t lastTs = 0; + std::list > msgs; + while (m_IsRunning) + { + try + { + if (m_TunnelBuildMsgQueue.Wait (TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL, 0)) + { + m_TunnelBuildMsgQueue.GetWholeQueue (msgs); + while (!msgs.empty ()) + { + auto msg = msgs.front (); msgs.pop_front (); + if (!msg) continue; + uint8_t typeID = msg->GetTypeID (); + switch (typeID) + { + case eI2NPShortTunnelBuild: + HandleShortTransitTunnelBuildMsg (std::move (msg)); + break; + case eI2NPVariableTunnelBuild: + HandleVariableTransitTunnelBuildMsg (std::move (msg)); + break; + default: + LogPrint (eLogWarning, "TransitTunnel: Unexpected message type ", (int) typeID); + } + if (!m_IsRunning) break; + } + } + if (m_IsRunning) + { + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts >= lastTs + TUNNEL_MANAGE_INTERVAL || ts + TUNNEL_MANAGE_INTERVAL < lastTs) + { + ManageTransitTunnels (ts); + lastTs = ts; + } + } + } + catch (std::exception& ex) + { + LogPrint (eLogError, "TransitTunnel: Runtime exception: ", ex.what ()); + } + } + } + + void TransitTunnels::PostTransitTunnelBuildMsg (std::shared_ptr&& msg) + { + if (msg) m_TunnelBuildMsgQueue.Put (msg); + } + + void TransitTunnels::HandleShortTransitTunnelBuildMsg (std::shared_ptr&& msg) + { + if (!msg) return; + uint8_t * buf = msg->GetPayload(); + size_t len = msg->GetPayloadLength(); + int num = buf[0]; + LogPrint (eLogDebug, "TransitTunnel: ShortTunnelBuild ", num, " records"); + if (num > i2p::tunnel::MAX_NUM_RECORDS) + { + LogPrint (eLogError, "TransitTunnel: Too many records in ShortTunnelBuild message ", num); + return; + } + if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "TransitTunnel: ShortTunnelBuild message of ", num, " records is too short ", len); + return; + } + const uint8_t * record = buf + 1; + for (int i = 0; i < num; i++) + { + if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "TransitTunnel: Short request record ", i, " is ours"); + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "TransitTunnel: Can't decrypt short request record ", i); + return; + } + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "TransitTunnel: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); + return; + } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + 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); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + { + if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours + { + LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in short request record"); + return; + } + memcpy (ivKey, noiseState.m_CK , 32); + } + + // check if we accept this tunnel + std::shared_ptr transitTunnel; + uint8_t retCode = 0; + if (i2p::context.AcceptsTunnels ()) + { + auto congestionLevel = i2p::context.GetCongestionLevel (false); + if (congestionLevel < CONGESTION_LEVEL_FULL) + { + if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) + { + // random reject depending on congestion level + int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; + if (congestionLevel > level) + retCode = 30; + } + } + else + retCode = 30; + } + else + retCode = 30; + + if (!retCode) + { + i2p::data::IdentHash nextIdent(clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) + { + // create new transit tunnel + transitTunnel = CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + nextIdent, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + isEndpoint); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + else + // decline tunnel going to duplicated router + retCode = 30; + } + + // encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "TransitTunnel: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // send reply + auto onDrop = [transitTunnel]() + { + if (transitTunnel) + { + LogPrint (eLogDebug, "TransitTunnel: Failed to send reply for transit tunnel ", transitTunnel->GetTunnelID ()); + auto t = transitTunnel->GetCreationTime (); + if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT) + // make transit tunnel expired + transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT); + } + }; + if (isEndpoint) + { + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (transitTunnel) replyMsg->onDrop = onDrop; + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + { + tunnel->SendTunnelDataMsg (replyMsg); + tunnel->FlushTunnelDataMsgs (); + } + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } + } + else + { + auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (transitTunnel) msg->onDrop = onDrop; + i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg); + } + return; + } + record += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + } + + bool TransitTunnels::HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + { + for (int i = 0; i < num; i++) + { + uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; + if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "TransitTunnel: Build request record ", i, " is ours"); + if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "TransitTunnel: Failed to decrypt tunnel build record"); + return false; + } + if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours + !(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint + { + LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in tunnel build record"); + return false; + } + uint8_t retCode = 0; + // decide if we should accept tunnel + bool accept = i2p::context.AcceptsTunnels (); + if (accept) + { + auto congestionLevel = i2p::context.GetCongestionLevel (false); + if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) + { + if (congestionLevel < CONGESTION_LEVEL_FULL) + { + // random reject depending on congestion level + int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; + if (congestionLevel > level) + accept = false; + } + else + accept = false; + } + } + + if (accept) + { + i2p::data::IdentHash nextIdent(clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET); + bool isEndpoint = clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) + { + auto transitTunnel = CreateTransitTunnel ( + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + nextIdent, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + isEndpoint); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + else + // decline tunnel going to duplicated router + retCode = 30; + } + else + retCode = 30; // always reject with bandwidth reason (30) + + // replace record to reply + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; + // encrypt reply + i2p::crypto::CBCEncryption encryption; + for (int j = 0; j < num; j++) + { + uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "TransitTunnel: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, reply); + } + } + return true; + } + } + return false; + } + + void TransitTunnels::HandleVariableTransitTunnelBuildMsg (std::shared_ptr&& msg) + { + if (!msg) return; + uint8_t * buf = msg->GetPayload(); + size_t len = msg->GetPayloadLength(); + int num = buf[0]; + LogPrint (eLogDebug, "TransitTunnel: VariableTunnelBuild ", num, " records"); + if (num > i2p::tunnel::MAX_NUM_RECORDS) + { + LogPrint (eLogError, "TransitTunnle: Too many records in VaribleTunnelBuild message ", num); + return; + } + if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "TransitTunnel: VaribleTunnelBuild message of ", num, " records is too short ", len); + return; + } + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) + { + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } + + bool TransitTunnels::AddTransitTunnel (std::shared_ptr tunnel) + { + if (tunnels.AddTunnel (tunnel)) + m_TransitTunnels.push_back (tunnel); + else + { + LogPrint (eLogError, "TransitTunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); + return false; + } + return true; + } + + void TransitTunnels::ManageTransitTunnels (uint64_t ts) + { + for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) + { + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) + { + LogPrint (eLogDebug, "TransitTunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); + tunnels.RemoveTunnel (tunnel->GetTunnelID ()); + it = m_TransitTunnels.erase (it); + } + else + { + tunnel->Cleanup (); + it++; + } + } + } + + int TransitTunnels::GetTransitTunnelsExpirationTimeout () + { + int timeout = 0; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + // TODO: possible race condition with I2PControl + for (const auto& it : m_TransitTunnels) + { + int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; + if (t > timeout) timeout = t; + } + return timeout; + } } } diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index bce90958..34bcc79f 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,10 +10,11 @@ #define TRANSIT_TUNNEL_H__ #include -#include +#include #include #include #include "Crypto.h" +#include "Queue.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" @@ -28,18 +29,21 @@ 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; }; + virtual std::string GetNextPeerName () const; // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg); - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); + void SendTunnelDataMsg (std::shared_ptr msg) override; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; + private: - i2p::crypto::TunnelEncryption m_Encryption; + i2p::crypto::AESKey m_LayerKey, m_IVKey; + std::unique_ptr m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel @@ -47,20 +51,22 @@ 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 (); - size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); - void FlushTunnelDataMsgs (); + size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; }; + std::string GetNextPeerName () const override; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + void FlushTunnelDataMsgs () override; private: size_t m_NumTransmittedBytes; - std::vector > m_TunnelDataMsgs; + std::list > m_TunnelDataMsgs; + std::unique_ptr m_Sender; }; class TransitTunnelGateway: public TransitTunnel @@ -68,15 +74,16 @@ 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) {}; - - void SendTunnelDataMsg (std::shared_ptr msg); - void FlushTunnelDataMsgs (); - size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; + layerKey, ivKey), m_Gateway(*this) {}; + void SendTunnelDataMsg (std::shared_ptr msg) override; + void FlushTunnelDataMsgs () override; + size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); }; + std::string GetNextPeerName () const override; + private: std::mutex m_SendMutex; @@ -88,25 +95,70 @@ namespace tunnel public: TransitTunnelEndpoint (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): - TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), - m_Endpoint (false) {}; // transit endpoint is always outbound - - void Cleanup () { m_Endpoint.Cleanup (); } - - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); - size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): + TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {}; + void Cleanup () override; + + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + void FlushTunnelDataMsgs () override; + size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; } + std::string GetNextPeerName () const override; + private: - TunnelEndpoint m_Endpoint; + std::mutex m_HandleMutex; + std::unique_ptr m_Endpoint; }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - 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); + + + const int TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL = 10; // in seconds + + class TransitTunnels + { + public: + + TransitTunnels (); + ~TransitTunnels (); + + void Start (); + void Stop (); + void PostTransitTunnelBuildMsg (std::shared_ptr&& msg); + + size_t GetNumTransitTunnels () const { return m_TransitTunnels.size (); } + int GetTransitTunnelsExpirationTimeout (); + + private: + + bool AddTransitTunnel (std::shared_ptr tunnel); + void ManageTransitTunnels (uint64_t ts); + + void HandleShortTransitTunnelBuildMsg (std::shared_ptr&& msg); + void HandleVariableTransitTunnelBuildMsg (std::shared_ptr&& msg); + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); + + void Run (); + + private: + + volatile bool m_IsRunning; + std::unique_ptr m_Thread; + std::list > m_TransitTunnels; + i2p::util::Queue > m_TunnelBuildMsgQueue; + std::mt19937 m_Rng; + + public: + + // for HTTP only + const auto& GetTransitTunnels () const { return m_TransitTunnels; }; + size_t GetTunnelBuildMsgQueueSize () const { return m_TunnelBuildMsgQueue.GetSize (); }; + }; } } diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 53192816..2cff0b1f 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,7 @@ #define TRANSPORT_SESSION_H__ #include -#include +#include #include #include #include @@ -27,59 +27,71 @@ namespace transport const size_t IPV4_HEADER_SIZE = 20; const size_t IPV6_HEADER_SIZE = 40; const size_t UDP_HEADER_SIZE = 8; - + + template class SignedData { public: - SignedData () {} + SignedData (): m_Size(0) {} SignedData (const SignedData& other) { - m_Stream << other.m_Stream.rdbuf (); + m_Size = other.m_Size; + memcpy (m_Buf, other.m_Buf, m_Size); } void Reset () { - m_Stream.str(""); + m_Size = 0; } - - void Insert (const uint8_t * buf, size_t len) + + size_t Insert (const uint8_t * buf, size_t len) { - m_Stream.write ((char *)buf, len); + if (m_Size + len > sz) len = sz - m_Size; + memcpy (m_Buf + m_Size, buf, len); + m_Size += len; + return len; } template void Insert (T t) { - m_Stream.write ((char *)&t, sizeof (T)); + Insert ((const uint8_t *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { - return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + return ident->Verify (m_Buf, m_Size, signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { - keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + keys.Sign (m_Buf, m_Size, signature); } private: - std::stringstream m_Stream; + uint8_t m_Buf[sz]; + size_t m_Size; }; - + + const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds + const int64_t TRANSPORT_SESSION_MAX_HANDSHAKE_INTERVAL = 10000; // in milliseconds + const uint64_t TRANSPORT_SESSION_BANDWIDTH_UPDATE_MIN_INTERVAL = 5; // in seconds class TransportSession { public: TransportSession (std::shared_ptr router, int terminationTimeout): - m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), - m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) + m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), m_HandshakeInterval (0), + m_SendQueueSize (0), m_NumSentBytes (0), m_NumReceivedBytes (0), + m_LastBandWidthUpdateNumSentBytes (0), m_LastBandWidthUpdateNumReceivedBytes (0), + m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()), + m_LastBandwidthUpdateTimestamp (m_LastActivityTimestamp), m_InBandwidth (0), m_OutBandwidth (0) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); - m_CreationTime = m_LastActivityTimestamp; + m_CreationTime = m_LastActivityTimestamp; } virtual ~TransportSession () {}; @@ -99,30 +111,92 @@ namespace transport } size_t GetNumSentBytes () const { return m_NumSentBytes; }; + void UpdateNumSentBytes (size_t len) + { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumSentBytes += len; + UpdateBandwidth (); + } size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + void UpdateNumReceivedBytes (size_t len) + { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumReceivedBytes += len; + UpdateBandwidth (); + } + size_t GetSendQueueSize () const { return m_SendQueueSize; }; + void SetSendQueueSize (size_t s) { m_SendQueueSize = s; }; bool IsOutgoing () const { return m_IsOutgoing; }; - + bool IsSlow () const { return m_HandshakeInterval > TRANSPORT_SESSION_SLOWNESS_THRESHOLD && + m_HandshakeInterval < TRANSPORT_SESSION_MAX_HANDSHAKE_INTERVAL; }; + bool IsBandwidthExceeded (bool isHighBandwidth) const + { + auto limit = isHighBandwidth ? i2p::data::HIGH_BANDWIDTH_LIMIT*1024 : i2p::data::LOW_BANDWIDTH_LIMIT*1024; // convert to bytes + return std::max (m_InBandwidth, m_OutBandwidth) > limit; + } + 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 + + uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; + void SetLastActivityTimestamp (uint64_t ts) { m_LastActivityTimestamp = ts; }; virtual uint32_t GetRelayTag () const { return 0; }; - virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; - virtual void SendI2NPMessages (const std::vector >& msgs) = 0; + virtual void SendLocalRouterInfo (bool update = false) + { + std::list > msgs{ CreateDatabaseStoreMsg () }; + SendI2NPMessages (msgs); + }; + virtual void SendI2NPMessages (std::list >& msgs) = 0; + virtual bool IsEstablished () const = 0; + virtual i2p::data::RouterInfo::SupportedTransports GetTransportType () const = 0; + private: + + void UpdateBandwidth () + { + int64_t interval = m_LastActivityTimestamp - m_LastBandwidthUpdateTimestamp; + if (interval < 0 || interval > 60*10) // 10 minutes + { + // clock was adjusted, copy new values + m_LastBandWidthUpdateNumSentBytes = m_NumSentBytes; + m_LastBandWidthUpdateNumReceivedBytes = m_NumReceivedBytes; + m_LastBandwidthUpdateTimestamp = m_LastActivityTimestamp; + return; + } + if ((uint64_t)interval > TRANSPORT_SESSION_BANDWIDTH_UPDATE_MIN_INTERVAL) + { + m_OutBandwidth = (m_NumSentBytes - m_LastBandWidthUpdateNumSentBytes)/interval; + m_LastBandWidthUpdateNumSentBytes = m_NumSentBytes; + m_InBandwidth = (m_NumReceivedBytes - m_LastBandWidthUpdateNumReceivedBytes)/interval; + m_LastBandWidthUpdateNumReceivedBytes = m_NumReceivedBytes; + m_LastBandwidthUpdateTimestamp = m_LastActivityTimestamp; + } + } + protected: std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; - size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; - uint64_t m_LastActivityTimestamp; uint32_t m_CreationTime; // seconds since epoch + int64_t m_HandshakeInterval; // in milliseconds between SessionRequest->SessionCreated or SessionCreated->SessionConfirmed + + private: + + size_t m_SendQueueSize, m_NumSentBytes, m_NumReceivedBytes, + m_LastBandWidthUpdateNumSentBytes, m_LastBandWidthUpdateNumReceivedBytes; + uint64_t m_LastActivityTimestamp, m_LastBandwidthUpdateTimestamp; + uint32_t m_InBandwidth, m_OutBandwidth; }; } } diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index b37ef7b2..98dbcd94 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,11 +1,12 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ +#include // for boost::to_lower #include "Log.h" #include "Crypto.h" #include "RouterContext.h" @@ -24,7 +25,7 @@ namespace transport { template EphemeralKeysSupplier::EphemeralKeysSupplier (int size): - m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) + m_QueueSize (size), m_IsRunning (false) { } @@ -38,7 +39,7 @@ namespace transport void EphemeralKeysSupplier::Start () { m_IsRunning = true; - m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); + m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier::Run, this))); } template @@ -52,9 +53,15 @@ namespace transport if (m_Thread) { m_Thread->join (); - delete m_Thread; - m_Thread = 0; + m_Thread = nullptr; } + if (!m_Queue.empty ()) + { + // clean up queue + std::queue > tmp; + std::swap (m_Queue, tmp); + } + m_KeysPool.CleanUpMt (); } template @@ -65,18 +72,19 @@ namespace transport while (m_IsRunning) { int num, total = 0; - while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10) + while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize) { CreateEphemeralKeys (num); total += num; } - if (total >= 10) + if (total > m_QueueSize) { LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else { + m_KeysPool.CleanUpMt (); std::unique_lock l(m_AcquiredMutex); if (!m_IsRunning) break; m_Acquired.wait (l); // wait for element gets acquired @@ -91,7 +99,7 @@ namespace transport { for (int i = 0; i < num; i++) { - auto pair = std::make_shared (); + auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); @@ -113,7 +121,7 @@ namespace transport } } // queue is empty, create new - auto pair = std::make_shared (); + auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); return pair; } @@ -123,25 +131,37 @@ namespace transport { if (pair) { - std::unique_lockl(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else - LogPrint(eLogError, "Transports: Return null DHKeys"); + LogPrint(eLogError, "Transports: Return null keys"); } + void Peer::UpdateParams (std::shared_ptr router) + { + if (router) + { + isHighBandwidth = router->IsHighBandwidth (); + isEligible =(bool)router->GetCompatibleTransports (true) && // reachable + router->GetCongestion () != i2p::data::RouterInfo::eRejectAll && // accepts tunnel + router->IsECIES () && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; // not too old + } + } + Transports transports; Transports::Transports (): m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), - m_SSUServer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), - m_X25519KeysPairSupplier (15), // 15 pre-generated keys - m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), - m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), - m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), - m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) + m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), + m_X25519KeysPairSupplier (NUM_X25519_PRE_GENERATED_KEYS), + m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), + m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), + m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), + m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) { } @@ -152,21 +172,25 @@ namespace transport { delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr; delete m_PeerTestTimer; m_PeerTestTimer = nullptr; + delete m_UpdateBandwidthTimer; m_UpdateBandwidthTimer = nullptr; delete m_Work; m_Work = nullptr; delete m_Service; m_Service = nullptr; } } - void Transports::Start (bool enableNTCP2, bool enableSSU, bool enableSSU2) + void Transports::Start (bool enableNTCP2, bool enableSSU2) { if (!m_Service) { - m_Service = new boost::asio::io_service (); - m_Work = new boost::asio::io_service::work (*m_Service); + m_Service = new boost::asio::io_context (); + m_Work = new boost::asio::executor_work_guard (m_Service->get_executor ()); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); + m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service); } + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); i2p::config::GetOption("nat", m_IsNAT); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; @@ -190,69 +214,93 @@ namespace transport m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); i2p::context.SetStatus (eRouterStatusProxy); + if (ipv6) + i2p::context.SetStatusV6 (eRouterStatusProxy); } else - LogPrint(eLogError, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogCritical, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); } else - LogPrint(eLogError, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogCritical, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); } else m_NTCP2Server = new NTCP2Server (); } - // create SSU server - int ssuPort = 0; - if (enableSSU) + // create SSU2 server + if (enableSSU2) { - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address: addresses) + m_SSU2Server = new SSU2Server (); + std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy); + if (!ssu2proxy.empty()) { - if (!address) continue; - if (address->transportStyle == RouterInfo::eTransportSSU) + if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks") { - ssuPort = address->port; - m_SSUServer = new SSUServer (address->port); - break; + if (m_SSU2Server->SetProxy (proxyurl.host, proxyurl.port)) + { + i2p::context.SetStatus (eRouterStatusProxy); + if (ipv6) + i2p::context.SetStatusV6 (eRouterStatusProxy); + } + else + LogPrint(eLogCritical, "Transports: Can't set SSU2 proxy ", ssu2proxy); } + else + LogPrint(eLogCritical, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); } } - // create SSU2 server - if (enableSSU2) m_SSU2Server = new SSU2Server (); // bind to interfaces - bool ipv4; i2p::config::GetOption("ipv4", ipv4); if (ipv4) { std::string address; i2p::config::GetOption("address4", address); if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (address, ec); + auto addr = boost::asio::ip::make_address (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); - if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr); } } + + if (enableSSU2) + { + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); + if (mtu) + { + if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; + if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; + i2p::context.SetMTU (mtu, true); + } + } } - bool ipv6; i2p::config::GetOption("ipv6", ipv6); if (ipv6) { std::string address; i2p::config::GetOption("address6", address); if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (address, ec); + auto addr = boost::asio::ip::make_address (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); - if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr); } } + + if (enableSSU2) + { + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); + if (mtu) + { + if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; + if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; + i2p::context.SetMTU (mtu, false); + } + } } bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); @@ -262,7 +310,7 @@ namespace transport if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (address, ec); + auto addr = boost::asio::ip::make_address (address, ec); if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr)) m_NTCP2Server->SetLocalAddress (addr); } @@ -271,29 +319,27 @@ namespace transport // start servers if (m_NTCP2Server) m_NTCP2Server->Start (); if (m_SSU2Server) m_SSU2Server->Start (); - if (m_SSUServer) - { - LogPrint (eLogInfo, "Transports: Start listening UDP port ", ssuPort); - try - { - m_SSUServer->Start (); - } - catch (std::exception& ex ) - { - LogPrint(eLogError, "Transports: Failed to bind to UDP port", ssuPort); - m_SSUServer->Stop (); - delete m_SSUServer; - m_SSUServer = nullptr; - } - } - if (m_SSUServer || m_SSU2Server) DetectExternalIP (); + if (m_SSU2Server) DetectExternalIP (); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5 * SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); + for (int i = 0; i < TRAFFIC_SAMPLE_COUNT; i++) + { + m_TrafficSamples[i].Timestamp = ts - (TRAFFIC_SAMPLE_COUNT - i - 1) * 1000; + m_TrafficSamples[i].TotalReceivedBytes = 0; + m_TrafficSamples[i].TotalSentBytes = 0; + m_TrafficSamples[i].TotalTransitTransmittedBytes = 0; + } + m_TrafficSamplePtr = TRAFFIC_SAMPLE_COUNT - 1; + + m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); + m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); + if (m_IsNAT) { - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } @@ -302,13 +348,6 @@ namespace transport { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); - m_Peers.clear (); - if (m_SSUServer) - { - m_SSUServer->Stop (); - delete m_SSUServer; - m_SSUServer = nullptr; - } if (m_SSU2Server) { @@ -333,6 +372,7 @@ namespace transport delete m_Thread; m_Thread = nullptr; } + m_Peers.clear (); } void Transports::Run () @@ -352,50 +392,85 @@ namespace transport } } - void Transports::UpdateBandwidth () + void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - if (m_LastBandwidthUpdateTime > 0) + TrafficSample& sample1 = m_TrafficSamples[m_TrafficSamplePtr]; + TrafficSample& sample2 = m_TrafficSamples[(TRAFFIC_SAMPLE_COUNT + m_TrafficSamplePtr - interval) % TRAFFIC_SAMPLE_COUNT]; + auto delta = (int64_t)sample1.Timestamp - (int64_t)sample2.Timestamp; + if (delta <= 0) { - auto delta = ts - m_LastBandwidthUpdateTime; - if (delta > 0) - { - m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second - m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second - m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta; - } + LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000); + return; } - m_LastBandwidthUpdateTime = ts; - m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; - m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; - m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; + in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta; + out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta; + transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta; } - bool Transports::IsBandwidthExceeded () const + void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) { - auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes - auto bw = std::max (m_InBandwidth, m_OutBandwidth); - return bw > limit; + if (ecode != boost::asio::error::operation_aborted) + { + m_TrafficSamplePtr++; + if (m_TrafficSamplePtr == TRAFFIC_SAMPLE_COUNT) + m_TrafficSamplePtr = 0; + + TrafficSample& sample = m_TrafficSamples[m_TrafficSamplePtr]; + sample.Timestamp = i2p::util::GetMillisecondsSinceEpoch(); + sample.TotalReceivedBytes = m_TotalReceivedBytes; + sample.TotalSentBytes = m_TotalSentBytes; + sample.TotalTransitTransmittedBytes = m_TotalTransitTransmittedBytes; + + UpdateBandwidthValues (1, m_InBandwidth, m_OutBandwidth, m_TransitBandwidth); + UpdateBandwidthValues (15, m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s); + UpdateBandwidthValues (300, m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m); + + m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); + m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); + } } - bool Transports::IsTransitBandwidthExceeded () const + int Transports::GetCongestionLevel (bool longTerm) const { - auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes - return m_TransitBandwidth > limit; + auto bwLimit = i2p::context.GetBandwidthLimit () * 1024; // convert to bytes + auto tbwLimit = i2p::context.GetTransitBandwidthLimit () * 1024; // convert to bytes + + if (tbwLimit == 0 || bwLimit == 0) + return CONGESTION_LEVEL_FULL; + + uint32_t bw; + uint32_t tbw; + if (longTerm) + { + bw = std::max (m_InBandwidth5m, m_OutBandwidth5m); + tbw = m_TransitBandwidth5m; + } + else + { + bw = std::max (m_InBandwidth15s, m_OutBandwidth15s); + tbw = m_TransitBandwidth; + } + auto bwCongestionLevel = CONGESTION_LEVEL_FULL * bw / bwLimit; + auto tbwCongestionLevel = CONGESTION_LEVEL_FULL * tbw / tbwLimit; + return std::max (bwCongestionLevel, tbwCongestionLevel); } - void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) + std::future > Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { if (m_IsOnline) - SendMessages (ident, std::vector > {msg }); + return SendMessages (ident, { msg }); + return {}; // invalid future } - void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) + std::future > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs) { - m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); - } - - void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) + return boost::asio::post (*m_Service, boost::asio::use_future ([this, ident, msgs = std::move(msgs)] () mutable + { + return PostMessages (ident, msgs); + })); + } + + std::shared_ptr Transports::PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { @@ -403,180 +478,171 @@ namespace transport for (auto& it: msgs) m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); - return; + return nullptr; } - if(RoutesRestricted() && !IsRestrictedPeer(ident)) return; - auto it = m_Peers.find (ident); - if (it == m_Peers.end ()) + if(RoutesRestricted() && !IsRestrictedPeer(ident)) return nullptr; + std::shared_ptr peer; { + std::lock_guard l(m_PeersMutex); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) + peer = it->second; + } + if (!peer) + { + // check if not banned + if (i2p::data::IsRouterBanned (ident)) return nullptr; // don't create peer to unreachable router + // try to connect bool connected = false; try { auto r = netdb.FindRouter (ident); - if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(m_PeersMutex); - it = m_Peers.insert (std::pair(ident, { 0, r, {}, - ts, ts + PEER_ROUTER_INFO_UPDATE_INTERVAL, {} })).first; + if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return nullptr; // router found but non-reachable + + peer = std::make_shared(r, i2p::util::GetSecondsSinceEpoch ()); + { + std::lock_guard l(m_PeersMutex); + peer = m_Peers.emplace (ident, peer).first->second; } - connected = ConnectToPeer (ident, it->second); + if (peer) + connected = ConnectToPeer (ident, peer); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } - if (!connected) return; + if (!connected) return nullptr; } - if (!it->second.sessions.empty ()) - it->second.sessions.front ()->SendI2NPMessages (msgs); + + if (!peer) return nullptr; + if (peer->IsConnected ()) + { + auto session = peer->sessions.front (); + if (session) session->SendI2NPMessages (msgs); + return session; + } else { - if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) + auto sz = peer->delayedMessages.size (); + if (sz < MAX_NUM_DELAYED_MESSAGES) { - for (auto& it1: msgs) - it->second.delayedMessages.push_back (it1); + if (sz < CHECK_PROFILE_NUM_DELAYED_MESSAGES && sz + msgs.size () >= CHECK_PROFILE_NUM_DELAYED_MESSAGES) + { + if (i2p::data::IsRouterBanned (ident)) + { + LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); + std::lock_guard l(m_PeersMutex); + m_Peers.erase (ident); + return nullptr; + } + } + if (sz > MAX_NUM_DELAYED_MESSAGES/2) + { + for (auto& it1: msgs) + if (it1->onDrop) + it1->Drop (); // drop earlier because we can handle it + else + peer->delayedMessages.push_back (it1); + } + else + peer->delayedMessages.splice (peer->delayedMessages.end (), msgs); } else { LogPrint (eLogWarning, "Transports: Delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); - std::unique_lock l(m_PeersMutex); - m_Peers.erase (it); + std::lock_guard l(m_PeersMutex); + m_Peers.erase (ident); } } + return nullptr; } - bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) + bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer) { - if (!peer.router) // reconnect - peer.router = netdb.FindRouter (ident); // try to get new one from netdb - if (peer.router) // we have RI already + if (!peer->router) // reconnect + { + auto r = netdb.FindRouter (ident); // try to get new one from netdb + if (r) + { + peer->SetRouter (r); + r->CancelBufferToDelete (); + } + } + if (peer->router) // we have RI already { - if (peer.numAttempts < 2) // NTCP2, 0 - ipv6, 1 - ipv4 + if (peer->priority.empty ()) + SetPriority (peer); + while (peer->numAttempts < (int)peer->priority.size ()) { - if (m_NTCP2Server) // we support NTCP2 + auto tr = peer->priority[peer->numAttempts]; + peer->numAttempts++; + switch (tr) { - std::shared_ptr address; - if (!peer.numAttempts) // NTCP2 ipv6 + case i2p::data::RouterInfo::eNTCP2V4: + case i2p::data::RouterInfo::eNTCP2V6: { - if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) + if (!m_NTCP2Server) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? + peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address (); + if (address && IsInReservedRange(address->host)) + address = nullptr; + if (address) { - address = peer.router->GetPublishedNTCP2V6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; + auto s = std::make_shared (*m_NTCP2Server, peer->router, address); + if( m_NTCP2Server->UsingProxy()) + m_NTCP2Server->ConnectWithProxy(s); + else + m_NTCP2Server->Connect (s); + return true; } - peer.numAttempts++; + break; } - if (!address && peer.numAttempts == 1) // NTCP2 ipv4 + case i2p::data::RouterInfo::eSSU2V4: + case i2p::data::RouterInfo::eSSU2V6: { - if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) + if (!m_SSU2Server) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSU2V6) ? + peer->router->GetSSU2V6Address () : peer->router->GetSSU2V4Address (); + if (address && IsInReservedRange(address->host)) + address = nullptr; + if (address && address->IsReachableSSU ()) { - address = peer.router->GetPublishedNTCP2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; + if (m_SSU2Server->CreateSession (peer->router, address)) + return true; } - peer.numAttempts++; + break; } - if (address) + case i2p::data::RouterInfo::eNTCP2V6Mesh: { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - if( m_NTCP2Server->UsingProxy()) - m_NTCP2Server->ConnectWithProxy(s); - else + if (!m_NTCP2Server) continue; + auto address = peer->router->GetYggdrasilAddress (); + if (address) + { + auto s = std::make_shared (*m_NTCP2Server, peer->router, address); m_NTCP2Server->Connect (s); - return true; - } - } - else - peer.numAttempts = 2; // switch to SSU - } - if (peer.numAttempts == 2 || peer.numAttempts == 3) // SSU2, 2 - ipv6, 3 - ipv4 - { - if (m_SSU2Server) - { - std::shared_ptr address; - if (peer.numAttempts == 2) // SSU2 ipv6 - { - if (context.GetRouterInfo ().IsSSU2V6 () && peer.router->IsReachableBy (RouterInfo::eSSU2V6)) - { - address = peer.router->GetSSU2V6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (!address && peer.numAttempts == 3) // SSU2 ipv4 - { - if (context.GetRouterInfo ().IsSSU2V4 () && peer.router->IsReachableBy (RouterInfo::eSSU2V4)) - { - address = peer.router->GetSSU2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (address && address->IsReachableSSU ()) - { - if (m_SSU2Server->CreateSession (peer.router, address)) return true; - } - } - else - peer.numAttempts += 2; // switch to mesh - } - if (peer.numAttempts == 4) // Mesh - { - peer.numAttempts++; - if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) - { - auto address = peer.router->GetYggdrasilAddress (); - if (address) - { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - m_NTCP2Server->Connect (s); - return true; - } - } - } - if (peer.numAttempts == 5 || peer.numAttempts == 6) // SSU, 5 - ipv6, 6 - ipv4 - { - if (m_SSUServer) - { - std::shared_ptr address; - if (peer.numAttempts == 5) // SSU ipv6 - { - if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) - { - address = peer.router->GetSSUV6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; } - peer.numAttempts++; - } - if (!address && peer.numAttempts == 6) // SSU ipv4 - { - if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) - { - address = peer.router->GetSSUAddress (true); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (address && address->IsReachableSSU ()) - { - if (m_SSUServer->CreateSession (peer.router, address)) - return true; + break; } + default: + LogPrint (eLogError, "Transports: Unknown transport ", (int)tr); } - else - peer.numAttempts += 2; } - LogPrint (eLogInfo, "Transports: No compatble NTCP2 or SSU addresses available"); - i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed - peer.Done (); - std::unique_lock l(m_PeersMutex); + + LogPrint (eLogInfo, "Transports: No compatible addresses available"); + if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) + i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them + peer->Done (); + std::lock_guard l(m_PeersMutex); + m_Peers.erase (ident); + return false; + } + else if (i2p::data::IsRouterBanned (ident)) + { + LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); + peer->Done (); + std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -589,29 +655,126 @@ namespace transport return true; } + void Transports::SetPriority (std::shared_ptr peer) + { + static const std::vector + ntcp2Priority = + { + i2p::data::RouterInfo::eNTCP2V6, + i2p::data::RouterInfo::eNTCP2V4, + i2p::data::RouterInfo::eSSU2V6, + i2p::data::RouterInfo::eSSU2V4, + i2p::data::RouterInfo::eNTCP2V6Mesh + }, + ssu2Priority = + { + i2p::data::RouterInfo::eSSU2V6, + i2p::data::RouterInfo::eSSU2V4, + i2p::data::RouterInfo::eNTCP2V6, + i2p::data::RouterInfo::eNTCP2V4, + i2p::data::RouterInfo::eNTCP2V6Mesh + }; + if (!peer || !peer->router) return; + auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & + peer->router->GetCompatibleTransports (true); + auto directTransports = compatibleTransports & peer->router->GetPublishedTransports (); + peer->numAttempts = 0; + peer->priority.clear (); + + std::shared_ptr profile; + if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory + bool ssu2 = false; // NTCP2 by default + bool isReal = profile ? profile->IsReal () : true; + if (isReal) + { + ssu2 = m_Rng () & 1; // 1/2 + if (ssu2 && !profile) + { + profile = peer->router->GetProfile (); // load profile if necessary + isReal = profile->IsReal (); + if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real + } + } + const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; + if (directTransports) + { + // direct connections have higher priority + if (!isReal && (directTransports & (i2p::data::RouterInfo::eNTCP2V4 | i2p::data::RouterInfo::eNTCP2V6))) + { + // Non-confirmed router and a NTCP2 direct connection is presented + compatibleTransports &= ~directTransports; // exclude SSU2 direct connections + directTransports &= ~(i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6); + } + for (auto transport: priority) + if (transport & directTransports) + peer->priority.push_back (transport); + compatibleTransports &= ~directTransports; + } + if (compatibleTransports) + { + // then remaining + for (auto transport: priority) + if (transport & compatibleTransports) + peer->priority.push_back (transport); + } + if (peer->priority.empty ()) + { + // try recently connected SSU2 if any + auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & + peer->router->GetCompatibleTransports (false); + if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && + peer->router->HasProfile ()) + { + auto ep = peer->router->GetProfile ()->GetLastEndpoint (); + if (!ep.address ().is_unspecified () && ep.port ()) + { + if (ep.address ().is_v4 ()) + { + if ((supportedTransports & i2p::data::RouterInfo::eSSU2V4) && + m_SSU2Server->IsConnectedRecently (ep, false)) + peer->priority.push_back (i2p::data::RouterInfo::eSSU2V4); + } + else if (ep.address ().is_v6 ()) + { + if ((supportedTransports & i2p::data::RouterInfo::eSSU2V6) && + m_SSU2Server->IsConnectedRecently (ep)) + peer->priority.push_back (i2p::data::RouterInfo::eSSU2V6); + } + } + } + } + } + void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { - m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); + boost::asio::post (*m_Service, std::bind (&Transports::HandleRequestComplete, this, r, ident)); } void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { - auto it = m_Peers.find (ident); - if (it != m_Peers.end ()) + std::shared_ptr peer; { - if (r) + std::lock_guard l(m_PeersMutex); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) { - LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); - it->second.router = r; - ConnectToPeer (ident, it->second); - } - else - { - LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages"); - std::unique_lock l(m_PeersMutex); - m_Peers.erase (it); - } + if (r) + peer = it->second; + else + m_Peers.erase (it); + } } + + if (peer && !peer->router && r) + { + LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); + peer->SetRouter (r); + if (!peer->IsConnected ()) + ConnectToPeer (ident, peer); + } + else if (!r) + LogPrint (eLogInfo, "Transports: RouterInfo not found, failed to send messages"); + } void Transports::DetectExternalIP () @@ -622,7 +785,7 @@ namespace transport i2p::context.SetStatus (eRouterStatusOK); return; } - if (m_SSUServer || m_SSU2Server) + if (m_SSU2Server) PeerTest (); else LogPrint (eLogWarning, "Transports: Can't detect external IP. SSU or SSU2 is not available"); @@ -630,101 +793,82 @@ namespace transport void Transports::PeerTest (bool ipv4, bool ipv6) { - if (RoutesRestricted() || (!m_SSUServer && !m_SSU2Server)) return; + if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return; if (ipv4 && i2p::context.SupportsV4 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv4"); - std::set excluded; + std::unordered_set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - if (m_SSUServer) + int testDelay = 0; + for (int i = 0; i < 5; i++) { - bool statusChanged = false; - for (int i = 0; i < 5; i++) + auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 + if (router) { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4 - if (router) - { - auto addr = router->GetSSUAddress (true); // ipv4 - if (addr && !i2p::util::net::IsInReservedRange(addr->host)) - { - if (!statusChanged) - { - statusChanged = true; - i2p::context.SetStatus (eRouterStatusTesting); // first time only - } - m_SSUServer->CreateSession (router, addr, true); // peer test v4 - } - excluded.insert (router->GetIdentHash ()); - } - } - if (!statusChanged) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); - } - // SSU2 - if (m_SSU2Server) - { - excluded.clear (); - excluded.insert (i2p::context.GetIdentHash ()); - for (int i = 0; i < 3; i++) - { - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 - if (router) - { - if (i2p::context.GetStatus () != eRouterStatusTesting) - i2p::context.SetStatusSSU2 (eRouterStatusTesting); + if (!i2p::context.GetTesting ()) + { + i2p::context.SetTesting (true); + // send first peer test immediately m_SSU2Server->StartPeerTest (router, true); - excluded.insert (router->GetIdentHash ()); - } + } + else + { + testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; + if (m_Service) + { + auto delayTimer = std::make_shared(*m_Service); + delayTimer->expires_from_now (boost::posix_time::milliseconds (testDelay)); + delayTimer->async_wait ( + [this, router, delayTimer](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + m_SSU2Server->StartPeerTest (router, true); + }); + } + } + excluded.insert (router->GetIdentHash ()); } } + if (excluded.size () <= 1) + LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); } if (ipv6 && i2p::context.SupportsV6 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv6"); - std::set excluded; + std::unordered_set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - if (m_SSUServer) + int testDelay = 0; + for (int i = 0; i < 5; i++) { - bool statusChanged = false; - for (int i = 0; i < 5; i++) + auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 + if (router) { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6 - if (router) - { - auto addr = router->GetSSUV6Address (); - if (addr && !i2p::util::net::IsInReservedRange(addr->host)) - { - if (!statusChanged) - { - statusChanged = true; - i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only - } - m_SSUServer->CreateSession (router, addr, true); // peer test v6 - } - excluded.insert (router->GetIdentHash ()); - } - } - if (!statusChanged) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); - } - - // SSU2 - if (m_SSU2Server) - { - excluded.clear (); - excluded.insert (i2p::context.GetIdentHash ()); - for (int i = 0; i < 3; i++) - { - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 - if (router) - { - if (i2p::context.GetStatusV6 () != eRouterStatusTesting) - i2p::context.SetStatusV6SSU2 (eRouterStatusTesting); + if (!i2p::context.GetTestingV6 ()) + { + i2p::context.SetTestingV6 (true); + // send first peer test immediately m_SSU2Server->StartPeerTest (router, false); - excluded.insert (router->GetIdentHash ()); + } + else + { + testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; + if (m_Service) + { + auto delayTimer = std::make_shared(*m_Service); + delayTimer->expires_from_now (boost::posix_time::milliseconds (testDelay)); + delayTimer->async_wait ( + [this, router, delayTimer](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + m_SSU2Server->StartPeerTest (router, false); + }); + } } + excluded.insert (router->GetIdentHash ()); } } + if (excluded.size () <= 1) + LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); } } @@ -740,7 +884,7 @@ namespace transport void Transports::PeerConnected (std::shared_ptr session) { - m_Service->post([session, this]() + boost::asio::post (*m_Service, [session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; @@ -748,12 +892,36 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - it->second.router = nullptr; // we don't need RouterInfo after successive connect + auto peer = it->second; + if (peer->numAttempts > 1) + { + // exclude failed transports + i2p::data::RouterInfo::CompatibleTransports transports = 0; + int numExcluded = peer->numAttempts - 1; + if (numExcluded > (int)peer->priority.size ()) numExcluded = peer->priority.size (); + for (int i = 0; i < numExcluded; i++) + transports |= peer->priority[i]; + i2p::data::netdb.ExcludeReachableTransports (ident, transports); + } + if (peer->router && peer->numAttempts) + { + auto transport = peer->priority[peer->numAttempts-1]; + if (transport == i2p::data::RouterInfo::eNTCP2V4 || + transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh) + i2p::data::UpdateRouterProfile (ident, + [](std::shared_ptr profile) + { + if (profile) profile->Connected (); // outgoing NTCP2 connection if always real + }); + i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable + } + peer->numAttempts = 0; + peer->router = nullptr; // we don't need RouterInfo after successive connect bool sendDatabaseStore = true; - if (it->second.delayedMessages.size () > 0) + if (it->second->delayedMessages.size () > 0) { // check if first message is our DatabaseStore (publishing) - auto firstMsg = it->second.delayedMessages[0]; + auto firstMsg = peer->delayedMessages.front (); if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already @@ -762,11 +930,10 @@ namespace transport session->SendLocalRouterInfo (); else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds - it->second.sessions.push_back (session); - session->SendI2NPMessages (it->second.delayedMessages); - it->second.delayedMessages.clear (); + peer->sessions.push_back (session); + session->SendI2NPMessages (peer->delayedMessages); // send and clear } - else // incoming connection + else // incoming connection or peer test { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted @@ -774,18 +941,30 @@ namespace transport session->Done(); return; } - session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore + if (!session->IsOutgoing ()) // incoming + { + std::list > msgs{ CreateDatabaseStoreMsg () }; + session->SendI2NPMessages (msgs); // send DatabaseStore + } + auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed + i2p::data::UpdateRouterProfile (ident, + [](std::shared_ptr profile) + { + if (profile) profile->Connected (); + }); auto ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(m_PeersMutex); - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, - ts, ts + PEER_ROUTER_INFO_UPDATE_INTERVAL, {} })); + auto peer = std::make_shared(r, ts); + peer->sessions.push_back (session); + peer->router = nullptr; + std::lock_guard l(m_PeersMutex); + m_Peers.emplace (ident, peer); } }); } void Transports::PeerDisconnected (std::shared_ptr session) { - m_Service->post([session, this]() + boost::asio::post (*m_Service, [session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; @@ -793,20 +972,26 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - auto before = it->second.sessions.size (); - it->second.sessions.remove (session); - if (it->second.sessions.empty ()) + auto peer = it->second; + bool wasConnected = peer->IsConnected (); + peer->sessions.remove (session); + if (!peer->IsConnected ()) { - if (it->second.delayedMessages.size () > 0) + if (peer->delayedMessages.size () > 0) { - if (before > 0) // we had an active session before - it->second.numAttempts = 0; // start over - ConnectToPeer (ident, it->second); + if (wasConnected) // we had an active session before + peer->numAttempts = 0; // start over + ConnectToPeer (ident, peer); } else { - std::unique_lock l(m_PeersMutex); - m_Peers.erase (it); + { + std::lock_guard l(m_PeersMutex); + m_Peers.erase (it); + } + // delete buffer of just disconnected router + auto r = i2p::data::netdb.FindRouter (ident); + if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete (); } } } @@ -815,9 +1000,13 @@ namespace transport bool Transports::IsConnected (const i2p::data::IdentHash& ident) const { - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); +#if __cplusplus >= 202002L // C++20 + return m_Peers.contains (ident); +#else auto it = m_Peers.find (ident); return it != m_Peers.end (); +#endif } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) @@ -827,37 +1016,46 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + it->second->sessions.remove_if ( + [](std::shared_ptr session)->bool + { + return !session || !session->IsEstablished (); + }); + if (!it->second->IsConnected () && ts > it->second->creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); - auto profile = i2p::data::GetRouterProfile(it->first); - if (profile) - { - profile->TunnelNonReplied(); - } - std::unique_lock l(m_PeersMutex); + /* if (!it->second.router) + { + // if router for ident not found mark it unreachable + auto profile = i2p::data::GetRouterProfile (it->first); + if (profile) profile->Unreachable (); + } */ + std::lock_guard l(m_PeersMutex); it = m_Peers.erase (it); } else { - if (ts > it->second.nextRouterInfoUpdateTime) + if (ts > it->second->nextRouterInfoUpdateTime) { - auto session = it->second.sessions.front (); + auto session = it->second->sessions.front (); if (session) session->SendLocalRouterInfo (true); - it->second.nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + - rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; + it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + + m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; } ++it; } } - UpdateBandwidth (); // TODO: use separate timer(s) for it - bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting; - bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting; - // if still testing, repeat peer test + bool ipv4Testing = i2p::context.GetTesting (); + if (!ipv4Testing) + ipv4Testing = i2p::context.GetRouterInfo ().IsSSU2V4 () && (i2p::context.GetStatus() == eRouterStatusUnknown); + bool ipv6Testing = i2p::context.GetTestingV6 (); + if (!ipv6Testing) + ipv6Testing = i2p::context.GetRouterInfo ().IsSSU2V6 () && (i2p::context.GetStatusV6() == eRouterStatusUnknown); + // if still testing or unknown, repeat peer test if (ipv4Testing || ipv6Testing) PeerTest (ipv4Testing, ipv6Testing); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3*SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } @@ -867,23 +1065,116 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { PeerTest (); - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } - 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; + auto ts = i2p::util::GetSecondsSinceEpoch (); + bool found = false; i2p::data::IdentHash ident; { - std::unique_lock l(m_PeersMutex); + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); + std::lock_guard l(m_PeersMutex); + auto count = m_Peers.size (); + if(count == 0) return nullptr; + inds[0] %= count; 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 (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) + { + ident = it->first; + it->second->lastSelectionTime = ts; + 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 (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) + { + ident = it->first; + it->second->lastSelectionTime = ts; + found = true; + break; + } + it++; + } + if (!found) + { + // still not found, try to the beginning + it = it2; + while (it != m_Peers.end ()) + { + if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) + { + ident = it->first; + it->second->lastSelectionTime = ts; + 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](std::shared_ptr peer)->bool + { + // connected, not overloaded and not slow + return !peer->router && peer->IsConnected () && peer->isEligible && + peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && + !peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) && + (!isHighBandwidth || peer->isHighBandwidth); + }); } void Transports::RestrictRoutesToFamilies(const std::set& families) @@ -899,22 +1190,29 @@ namespace transport } } - void Transports::RestrictRoutesToRouters(std::set routers) + void Transports::RestrictRoutesToRouters(const std::set& routers) { - std::unique_lock lock(m_TrustedRoutersMutex); + std::lock_guard lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) - m_TrustedRouters.push_back(ri); + m_TrustedRouters.insert(ri); } - bool Transports::RoutesRestricted() const { - std::unique_lock famlock(m_FamilyMutex); - std::unique_lock routerslock(m_TrustedRoutersMutex); - return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0; + bool Transports::RoutesRestricted() const + { + { + std::lock_guard routerslock(m_TrustedRoutersMutex); + if (!m_TrustedRouters.empty ()) return true; + } + { + std::lock_guard famlock(m_FamilyMutex); + if (!m_TrustedFamilies.empty ()) return true; + } + return false; } /** XXX: if routes are not restricted this dies */ - std::shared_ptr Transports::GetRestrictedPeer() const + std::shared_ptr Transports::GetRestrictedPeer() { { std::lock_guard l(m_FamilyMutex); @@ -923,7 +1221,7 @@ namespace transport if(sz > 1) { auto it = m_TrustedFamilies.begin (); - std::advance(it, rand() % sz); + std::advance(it, m_Rng() % sz); fam = *it; } else if (sz == 1) @@ -934,29 +1232,38 @@ namespace transport return i2p::data::netdb.GetRandomRouterInFamily(fam); } { - std::unique_lock l(m_TrustedRoutersMutex); + std::lock_guard l(m_TrustedRoutersMutex); auto sz = m_TrustedRouters.size(); if (sz) { - if(sz == 1) - return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); auto it = m_TrustedRouters.begin(); - std::advance(it, rand() % sz); + if(sz > 1) + std::advance(it, m_Rng() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } - bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const + bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const { + if (m_TrustedRouters.empty ()) return false; + std::lock_guard l(m_TrustedRoutersMutex); +#if __cplusplus >= 202002L // C++20 + if (m_TrustedRouters.contains (ih)) +#else + if (m_TrustedRouters.count (ih) > 0) +#endif + return true; + return false; + } + + bool Transports::IsRestrictedPeer(const i2p::data::IdentHash& ih) const + { + if (IsTrustedRouter (ih)) return true; + { - std::unique_lock l(m_TrustedRoutersMutex); - for (const auto & r : m_TrustedRouters ) - if ( r == ih ) return true; - } - { - std::unique_lock l(m_FamilyMutex); + std::lock_guard l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); for (const auto & fam : m_TrustedFamilies) if(ri->IsFamily(fam)) return true; @@ -975,5 +1282,123 @@ namespace transport i2p::context.SetError (eRouterErrorOffline); } } + + bool Transports::IsInReservedRange (const boost::asio::ip::address& host) const + { + return IsCheckReserved () && i2p::util::net::IsInReservedRange (host); + } + + void InitAddressFromIface () + { + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + + // ifname -> address + std::string ifname; i2p::config::GetOption("ifname", ifname); + if (ipv4 && i2p::config::IsDefault ("address4")) + { + std::string ifname4; i2p::config::GetOption("ifname4", ifname4); + if (!ifname4.empty ()) + i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 + else if (!ifname.empty ()) + i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 + } + if (ipv6 && i2p::config::IsDefault ("address6")) + { + std::string ifname6; i2p::config::GetOption("ifname6", ifname6); + if (!ifname6.empty ()) + i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 + else if (!ifname.empty ()) + i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 + } + } + + void InitTransports () + { + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + uint16_t port; i2p::config::GetOption("port", port); + + boost::asio::ip::address_v6 yggaddr; + if (ygg) + { + std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); + if (!yggaddress.empty ()) + { + yggaddr = boost::asio::ip::make_address (yggaddress).to_v6 (); + if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || + !i2p::util::net::IsLocalAddress (yggaddr)) + { + LogPrint(eLogWarning, "Transports: Can't find Yggdrasil address ", yggaddress); + ygg = false; + } + } + else + { + yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (yggaddr.is_unspecified ()) + { + LogPrint(eLogWarning, "Transports: Yggdrasil is not running. Disabled"); + ygg = false; + } + } + } + + if (!i2p::config::IsDefault("port")) + { + LogPrint(eLogInfo, "Transports: Accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); + i2p::context.SetSupportsMesh (ygg, yggaddr); + + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) + { + std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); + if (!ntcp2proxy.empty ()) published = false; + } + if (published) + { + uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); + if (!ntcp2port) ntcp2port = port; // use standard port + i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish + if (ipv6) + { + std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); + auto addr = boost::asio::ip::make_address (ipv6Addr).to_v6 (); + if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) + i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured + } + } + else + i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish + } + if (ygg) + { + i2p::context.PublishNTCP2Address (port, true, false, false, true); + i2p::context.UpdateNTCP2V6Address (yggaddr); + if (!ipv4 && !ipv6) + i2p::context.SetStatus (eRouterStatusMesh); + } + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2 && i2p::config::IsDefault ("ssu2.enabled") && !ipv4 && !ipv6) + ssu2 = false; // don't enable ssu2 for yggdrasil only router + if (ssu2) + { + uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); + if (!ssu2port && port) ssu2port = port; + bool published; i2p::config::GetOption("ssu2.published", published); + if (published) + i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish + else + i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish + } + } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 78088a8d..fcd2cfc6 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,22 +11,25 @@ #include #include +#include #include #include #include +#include #include #include #include #include #include +#include #include #include "TransportSession.h" -#include "SSU.h" #include "SSU2.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" +#include "util.h" namespace i2p { @@ -53,35 +56,76 @@ namespace transport private: const int m_QueueSize; + i2p::util::MemoryPoolMt m_KeysPool; std::queue > m_Queue; bool m_IsRunning; - std::thread * m_Thread; + std::unique_ptr m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; typedef EphemeralKeysSupplier X25519KeysPairSupplier; - const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds - const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds + 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; + const int PEER_SELECTION_MIN_INTERVAL = 20; // in seconds struct Peer { int numAttempts; std::shared_ptr router; std::list > sessions; - uint64_t creationTime, nextRouterInfoUpdateTime; - std::vector > delayedMessages; + uint64_t creationTime, nextRouterInfoUpdateTime, lastSelectionTime; + std::list > delayedMessages; + std::vector priority; + bool isHighBandwidth, isEligible; + Peer (std::shared_ptr r, uint64_t ts): + numAttempts (0), router (r), creationTime (ts), + nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL), + lastSelectionTime (0), isHighBandwidth (false), isEligible (false) + { + UpdateParams (router); + } + void Done () { for (auto& it: sessions) it->Done (); + // drop not sent delayed messages + for (auto& it: delayedMessages) + it->Drop (); } + + void SetRouter (std::shared_ptr r) + { + router = r; + UpdateParams (router); + } + + bool IsConnected () const { return !sessions.empty (); } + void UpdateParams (std::shared_ptr router); }; const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds - const int PEER_TEST_INTERVAL = 71; // in minutes + const int PEER_TEST_INTERVAL = 68*60; // in seconds + const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds + const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds + const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds const int MAX_NUM_DELAYED_MESSAGES = 150; + const int CHECK_PROFILE_NUM_DELAYED_MESSAGES = 15; // check profile after + const int NUM_X25519_PRE_GENERATED_KEYS = 25; // pre-generated x25519 keys pairs + + const int TRAFFIC_SAMPLE_COUNT = 301; // seconds + + struct TrafficSample + { + uint64_t Timestamp; + uint64_t TotalReceivedBytes; + uint64_t TotalSentBytes; + uint64_t TotalTransitTransmittedBytes; + }; + class Transports { public: @@ -89,22 +133,22 @@ namespace transport Transports (); ~Transports (); - void Start (bool enableNTCP2=true, bool enableSSU=true, bool enableSSU2=false); + void Start (bool enableNTCP2=true, bool enableSSU2=true); void Stop (); + bool IsRunning () const { return m_IsRunning; } - bool IsBoundSSU() const { return m_SSUServer != nullptr; } bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; void SetOnline (bool online); - boost::asio::io_service& GetService () { return *m_Service; }; + auto& GetService () { return *m_Service; }; std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); - void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); - void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); + std::future > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); + std::future > SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); @@ -119,82 +163,100 @@ namespace transport uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; - bool IsBandwidthExceeded () const; - bool IsTransitBandwidthExceeded () const; + uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; }; + uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; }; + uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; }; + int GetCongestionLevel (bool longTerm) 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; + std::shared_ptr GetRestrictedPeer(); /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ void RestrictRoutesToFamilies(const std::set& families); /** restrict routes to use only these routers for first hops */ - void RestrictRoutesToRouters(std::set routers); + void RestrictRoutesToRouters(const std::set& routers); - bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; + bool IsTrustedRouter (const i2p::data::IdentHash& ih) const; + bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const; void PeerTest (bool ipv4 = true, bool ipv6 = true); void SetCheckReserved (bool check) { m_CheckReserved = check; }; - bool IsCheckReserved () { return m_CheckReserved; }; + bool IsCheckReserved () const { return m_CheckReserved; }; + bool IsInReservedRange (const boost::asio::ip::address& host) const; private: void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); - void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); - bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); + std::shared_ptr PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs); + bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer); + void SetPriority (std::shared_ptr peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); + void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); + void UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit); - void UpdateBandwidth (); void DetectExternalIP (); + template + std::shared_ptr GetRandomPeer (Filter filter) const; + private: volatile bool m_IsOnline; bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; - boost::asio::io_service * m_Service; - boost::asio::io_service::work * m_Work; - boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; + boost::asio::io_context * m_Service; + boost::asio::executor_work_guard * m_Work; + boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer; - SSUServer * m_SSUServer; SSU2Server * m_SSU2Server; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; - std::unordered_map m_Peers; + std::unordered_map > m_Peers; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; - uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second - uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; - uint64_t m_LastBandwidthUpdateTime; + + TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT]; + int m_TrafficSamplePtr; + + // Bandwidth per second + uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; + // Bandwidth during last 15 seconds + uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s; + // Bandwidth during last 5 minutes + uint32_t m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m; /** which router families to trust for first hops */ std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ - std::vector m_TrustedRouters; + std::unordered_set m_TrustedRouters; mutable std::mutex m_TrustedRoutersMutex; i2p::I2NPMessagesHandler m_LoopbackHandler; + std::mt19937 m_Rng; public: // for HTTP only - const SSUServer * GetSSUServer () const { return m_SSUServer; }; const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; const SSU2Server * GetSSU2Server () const { return m_SSU2Server; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; + + void InitAddressFromIface (); + void InitTransports (); } } diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index b578f6c1..1b317121 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,7 +33,7 @@ namespace tunnel TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr), m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports), - m_IsRecreated (false), m_Latency (0) + m_IsRecreated (false), m_Latency (UNKNOWN_LATENCY) { } @@ -52,7 +52,7 @@ namespace tunnel // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); - std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()())); + std::shuffle (recordIndicies.begin(), recordIndicies.end(), m_Pool ? m_Pool->GetRng () : std::mt19937(std::random_device()())); // create real records uint8_t * records = msg->GetPayload () + 1; @@ -90,20 +90,26 @@ namespace tunnel hop = hop->prev; } msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); - + auto s = shared_from_this (); + msg->onDrop = [s]() + { + LogPrint (eLogInfo, "I2NP: Tunnel ", s->GetTunnelID (), " request was not sent"); + s->SetState (i2p::tunnel::eTunnelStateBuildFailed); + }; + // send message if (outboundTunnel) { if (m_Config->IsShort ()) { auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; - if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP + if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); if (msg1) msg = msg1; } } - outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + outboundTunnel->SendTunnelDataMsgTo (GetNextIdentHash (), 0, msg); } else { @@ -116,7 +122,7 @@ namespace tunnel if (m_Pool && m_Pool->GetLocalDestination ()) m_Pool->GetLocalDestination ()->SubmitECIESx25519Key (key, tag); else - i2p::context.AddECIESx25519Key (key, tag); + i2p::context.SubmitECIESx25519Key (key, tag); } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } @@ -124,8 +130,19 @@ namespace tunnel bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { - LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - + int num = msg[0]; + LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", num, " records."); + if (num > MAX_NUM_RECORDS) + { + LogPrint (eLogError, "Tunnel: Too many records in TunnelBuildResponse", num); + return false; + } + if (len < num*m_Config->GetRecordSize () + 1) + { + LogPrint (eLogError, "Tunnel: TunnelBuildResponse of ", num, " records is too short ", len); + return false; + } + TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { @@ -146,7 +163,7 @@ namespace tunnel while (hop1) { auto idx = hop1->recordIndex; - if (idx >= 0 && idx < msg[0]) + if (idx >= 0 && idx < num) hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range"); @@ -162,9 +179,12 @@ namespace tunnel { uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); - auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); - if (profile) - profile->TunnelBuildResponse (ret); + if (hop->ident) + i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), + [ret](std::shared_ptr profile) + { + if (profile) profile->TunnelBuildResponse (ret); + }); if (ret) // if any of participants declined the tunnel is not established established = false; @@ -192,10 +212,10 @@ namespace tunnel return established; } - bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const + bool Tunnel::LatencyFitsRange(int lowerbound, int upperbound) const { auto latency = GetMeanLatency(); - return latency >= lower && latency <= upper; + return latency >= lowerbound && latency <= upperbound; } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) @@ -244,12 +264,38 @@ namespace tunnel void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& msg) { - if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive + if (!IsEstablished () && GetState () != eTunnelStateExpiring) + { + // incoming messages means a tunnel is alive + SetState (eTunnelStateEstablished); + auto pool = GetTunnelPool (); + if (pool) + { + // update LeaseSet + auto dest = pool->GetLocalDestination (); + if (dest) dest->SetLeaseSetUpdated (true); + } + } EncryptTunnelMsg (msg, msg); - msg->from = shared_from_this (); + msg->from = GetSharedFromThis (); m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } + bool InboundTunnel::Recreate () + { + if (!IsRecreated ()) + { + auto pool = GetTunnelPool (); + if (pool) + { + SetRecreated (true); + pool->RecreateInboundTunnel (std::static_pointer_cast(shared_from_this ())); + return true; + } + } + return false; + } + ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) @@ -261,33 +307,39 @@ namespace tunnel if (msg) { m_NumReceivedBytes += msg->GetLength (); - msg->from = shared_from_this (); + msg->from = GetSharedFromThis (); HandleI2NPMessage (msg); } } - void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) + void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; + block.tunnelID = 0; // Initialize tunnelID to a default value + if (gwHash) { block.hash = gwHash; if (gwTunnel) { block.deliveryType = eDeliveryTypeTunnel; - block.tunnelID = gwTunnel; + block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero } else + { block.deliveryType = eDeliveryTypeRouter; + } } else + { block.deliveryType = eDeliveryTypeLocal; + } + block.data = msg; - - SendTunnelDataMsg ({block}); + SendTunnelDataMsgs({block}); } - void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) + void OutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) @@ -300,13 +352,28 @@ namespace tunnel LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); } + bool OutboundTunnel::Recreate () + { + if (!IsRecreated ()) + { + auto pool = GetTunnelPool (); + if (pool) + { + SetRecreated (true); + pool->RecreateOutboundTunnel (std::static_pointer_cast(shared_from_this ())); + return true; + } + } + return false; + } + ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) { } - void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) + void ZeroHopsOutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) { for (auto& msg : msgs) { @@ -331,23 +398,40 @@ namespace tunnel Tunnels tunnels; - Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), - m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) + Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_MaxNumTransitTunnels (DEFAULT_MAX_NUM_TRANSIT_TUNNELS), + m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal average + m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0), + m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) { } Tunnels::~Tunnels () { + DeleteTunnelPool(m_ExploratoryPool); } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { + std::lock_guard l(m_TunnelsMutex); auto it = m_Tunnels.find(tunnelID); if (it != m_Tunnels.end ()) return it->second; return nullptr; } + bool Tunnels::AddTunnel (std::shared_ptr tunnel) + { + if (!tunnel) return false; + std::lock_guard l(m_TunnelsMutex); + return m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second; + } + + void Tunnels::RemoveTunnel (uint32_t tunnelID) + { + std::lock_guard l(m_TunnelsMutex); + m_Tunnels.erase (tunnelID); + } + std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); @@ -389,7 +473,7 @@ namespace tunnel std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; - uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; + uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { @@ -403,10 +487,12 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, - int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance) + std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, + int numOutboundHops, int numInboundTunnels, int numOutboundTunnels, + int inboundVariance, int outboundVariance, bool isHighBandwidth) { - auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance); + auto pool = std::make_shared (numInboundHops, numOutboundHops, + numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -433,22 +519,16 @@ namespace tunnel } } - void 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"); - } - void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); + m_TransitTunnels.Start (); } void Tunnels::Stop () { + m_TransitTunnels.Stop (); m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) @@ -465,17 +545,21 @@ namespace tunnel std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0; + std::list > msgs; while (m_IsRunning) { try { - auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec - if (msg) + if (m_Queue.Wait (1,0)) // 1 sec { + m_Queue.GetWholeQueue (msgs); + int numMsgs = 0; uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; - do + while (!msgs.empty ()) { + auto msg = msgs.front (); msgs.pop_front (); + if (!msg) continue; std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) @@ -503,44 +587,57 @@ namespace tunnel break; } - case eI2NPVariableTunnelBuild: - case eI2NPVariableTunnelBuildReply: case eI2NPShortTunnelBuild: + HandleShortTunnelBuildMsg (msg); + break; + case eI2NPVariableTunnelBuild: + HandleVariableTunnelBuildMsg (msg); + break; case eI2NPShortTunnelBuildReply: + HandleTunnelBuildReplyMsg (msg, true); + break; + case eI2NPVariableTunnelBuildReply: + HandleTunnelBuildReplyMsg (msg, false); + break; case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); - break; + LogPrint (eLogWarning, "Tunnel: TunnelBuild is too old for ECIES router"); + break; default: LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } - msg = m_Queue.Get (); - if (msg) - { - prevTunnelID = tunnelID; - prevTunnel = tunnel; - } - else if (tunnel) - tunnel->FlushTunnelDataMsgs (); + prevTunnelID = tunnelID; + prevTunnel = tunnel; + numMsgs++; + + if (msgs.empty ()) + { + if (numMsgs < MAX_TUNNEL_MSGS_BATCH_SIZE && !m_Queue.IsEmpty ()) + m_Queue.GetWholeQueue (msgs); // try more + else if (tunnel) + tunnel->FlushTunnelDataMsgs (); // otherwise flush last + } } - while (msg); } 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 seconds + 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 (); @@ -575,39 +672,115 @@ namespace tunnel auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "Tunnel: Gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); - if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) - // transit DatabaseStore my contain new/updated RI - // or DatabaseSearchReply with new routers - i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg)); tunnel->SendTunnelDataMsg (msg); } - void Tunnels::ManageTunnels () + void Tunnels::HandleShortTunnelBuildMsg (std::shared_ptr msg) { - ManagePendingTunnels (); - ManageInboundTunnels (); - ManageOutboundTunnels (); - ManageTransitTunnels (); + if (!msg) return; + auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + return; + } + else + m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); + } + + void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr msg) + { + auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + } + else + m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); + } + + void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort) + { + auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // reply for outbound tunnel + LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddOutboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + } + else + LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found"); + + } + + void Tunnels::ManageTunnels (uint64_t ts) + { + ManagePendingTunnels (ts); + std::vector > tunnelsToRecreate; + ManageInboundTunnels (ts, tunnelsToRecreate); + ManageOutboundTunnels (ts, tunnelsToRecreate); + // rec-create in random order + if (!tunnelsToRecreate.empty ()) + { + if (tunnelsToRecreate.size () > 1) + std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng); + for (auto& it: tunnelsToRecreate) + it->Recreate (); + } } - void Tunnels::ManagePendingTunnels () + 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 @@ -618,17 +791,17 @@ namespace tunnel while (hop) { if (hop->ident) - { - auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); - if (profile) - profile->TunnelNonReplied (); - } + i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->TunnelNonReplied (); + }); hop = hop->next; } } // delete it = pendingTunnels.erase (it); - m_NumFailedTunnelCreations++; + FailedTunnelCreation(); } else ++it; @@ -636,7 +809,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,56 +818,51 @@ namespace tunnel default: // success it = pendingTunnels.erase (it); - m_NumSuccesiveTunnelCreations++; + SuccesiveTunnelCreation(); } } } - void Tunnels::ManageOutboundTunnels () + void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate) { - 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 (tunnel->IsFailed () || 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 or failed"); + 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()) - { - tunnel->SetRecreated (true); - pool->RecreateOutboundTunnel (tunnel); - } - } - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); + auto pool = tunnel->GetTunnelPool (); + // let it die if the tunnel pool has been reconfigured and this is old + if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) + toRecreate.push_back (tunnel); } - ++it; + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); } + ++it; } } if (m_OutboundTunnels.size () < 3) { - // trying to create one more oubound tunnel + // trying to create one more outbound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); // reachable by us + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); CreateTunnel ( @@ -704,44 +872,39 @@ namespace tunnel } } - void Tunnels::ManageInboundTunnels () + void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate) { - 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 (tunnel->IsFailed () || 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 or failed"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + RemoveTunnel (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()) - { - 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 (); + 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()) + toRecreate.push_back (tunnel); } - 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++; } } @@ -756,7 +919,7 @@ namespace tunnel int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen); int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum); int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum); - m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0); + m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0, false); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; @@ -768,7 +931,7 @@ namespace tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : // should be reachable by us because we send build request directly - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); if (!router) { LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); return; @@ -780,26 +943,6 @@ namespace tunnel } } - void Tunnels::ManageTransitTunnels () - { - 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) - { - LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); - m_Tunnels.erase (tunnel->GetTunnelID ()); - it = m_TransitTunnels.erase (it); - } - else - { - tunnel->Cleanup (); - it++; - } - } - } - void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); @@ -815,7 +958,7 @@ namespace tunnel if (msg) m_Queue.Put (msg); } - void Tunnels::PostTunnelData (const std::vector >& msgs) + void Tunnels::PostTunnelData (std::list >& msgs) { m_Queue.Put (msgs); } @@ -873,7 +1016,7 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { - if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) + if (AddTunnel (newTunnel)) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); @@ -903,7 +1046,7 @@ namespace tunnel inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); - m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; + AddTunnel (inboundTunnel); return inboundTunnel; } @@ -937,21 +1080,12 @@ namespace tunnel int Tunnels::GetTransitTunnelsExpirationTimeout () { - int timeout = 0; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - // TODO: possible race condition with I2PControl - for (const auto& it : m_TransitTunnels) - { - int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; - if (t > timeout) timeout = t; - } - return timeout; + return m_TransitTunnels.GetTransitTunnelsExpirationTimeout (); } size_t Tunnels::CountTransitTunnels() const { - // TODO: locking - return m_TransitTunnels.size(); + return m_TransitTunnels.GetNumTransitTunnels (); } size_t Tunnels::CountInboundTunnels() const @@ -965,5 +1099,14 @@ namespace tunnel // TODO: locking return m_OutboundTunnels.size(); } + + void Tunnels::SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels) + { + if (maxNumTransitTunnels > 0 && m_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "Tunnel: Max number of transit tunnels set to ", maxNumTransitTunnels); + m_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 503b7f9c..5d21cd8b 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include +#include #include "util.h" #include "Queue.h" #include "Crypto.h" @@ -39,11 +40,20 @@ namespace tunnel const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds 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 UNKNOWN_LATENCY = -1; + const int HIGH_LATENCY_PER_HOP = 250000; // in microseconds + const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt + const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000; + 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, @@ -57,7 +67,8 @@ namespace tunnel class OutboundTunnel; class InboundTunnel; - class Tunnel: public TunnelBase + class Tunnel: public TunnelBase, + public std::enable_shared_from_this { struct TunnelHop { @@ -82,12 +93,13 @@ namespace tunnel i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; }; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); - bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; + bool IsEstablished () const { return m_State == eTunnelStateEstablished || m_State == eTunnelStateTestFailed; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; + virtual bool Recreate () = 0; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; @@ -95,18 +107,18 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg); - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); + void SendTunnelDataMsg (std::shared_ptr msg) override; + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; /** @brief add latency sample */ - void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; } + void AddLatencySample(const int us) { m_Latency = LatencyIsKnown() ? (m_Latency + us) >> 1 : us; } /** @brief get this tunnel's estimated latency */ - uint64_t GetMeanLatency() const { return m_Latency; } + int GetMeanLatency() const { return (m_Latency + 500) / 1000; } /** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */ - bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; + bool LatencyFitsRange(int lowerbound, int upperbound) const; - bool LatencyIsKnown() const { return m_Latency > 0; } - bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } + bool LatencyIsKnown() const { return m_Latency != UNKNOWN_LATENCY; } + bool IsSlow () const { return LatencyIsKnown() && m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } /** visit all hops we currently store */ void VisitTunnelHops(TunnelHopVisitor v); @@ -120,7 +132,7 @@ namespace tunnel TunnelState m_State; i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace - uint64_t m_Latency; // in milliseconds + int m_Latency; // in microseconds }; class OutboundTunnel: public Tunnel @@ -128,17 +140,18 @@ namespace tunnel public: OutboundTunnel (std::shared_ptr config): - Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; + Tunnel (config), m_Gateway (*this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; - void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); - virtual void SendTunnelDataMsg (const std::vector& msgs); // multiple messages + void SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); + virtual void SendTunnelDataMsgs (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; - bool IsInbound() const { return false; } + bool IsInbound() const override { return false; } + bool Recreate () override; private: @@ -147,18 +160,26 @@ namespace tunnel i2p::data::IdentHash m_EndpointIdentHash; }; - class InboundTunnel: public Tunnel, public std::enable_shared_from_this + class InboundTunnel: public Tunnel { public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr&& msg); + void HandleTunnelDataMsg (std::shared_ptr&& msg) override; virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - bool IsInbound() const { return true; } + bool IsInbound() const override { return true; } + bool Recreate () override; // override TunnelBase - void Cleanup () { m_Endpoint.Cleanup (); }; + void Cleanup () override { m_Endpoint.Cleanup (); }; + protected: + + std::shared_ptr GetSharedFromThis () + { + return std::static_pointer_cast(shared_from_this ()); + } + private: TunnelEndpoint m_Endpoint; @@ -169,8 +190,8 @@ namespace tunnel public: ZeroHopsInboundTunnel (); - void SendTunnelDataMsg (std::shared_ptr msg); - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + void SendTunnelDataMsg (std::shared_ptr msg) override; + size_t GetNumReceivedBytes () const override { return m_NumReceivedBytes; }; private: @@ -182,8 +203,8 @@ namespace tunnel public: ZeroHopsOutboundTunnel (); - void SendTunnelDataMsg (const std::vector& msgs); - size_t GetNumSentBytes () const { return m_NumSentBytes; }; + void SendTunnelDataMsgs (const std::vector& msgs) override; + size_t GetNumSentBytes () const override { return m_NumSentBytes; }; private: @@ -205,23 +226,29 @@ namespace tunnel std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); + bool AddTunnel (std::shared_ptr tunnel); + void RemoveTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); - void 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); std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); - void PostTunnelData (const std::vector >& msgs); + void PostTunnelData (std::list >& msgs); // and cleanup msgs void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); - std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, - int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance); + std::shared_ptr CreateTunnelPool (int numInboundHops, + int numOuboundHops, int numInboundTunnels, int numOutboundTunnels, + int inboundVariance, int outboundVariance, bool isHighBandwidth); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); std::shared_ptr NewI2NPTunnelMessage (bool endpoint); + void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels); + uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; + int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } + private: template @@ -232,56 +259,83 @@ namespace tunnel std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); - + void HandleShortTunnelBuildMsg (std::shared_ptr msg); + void HandleVariableTunnelBuildMsg (std::shared_ptr msg); + void HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort); + void Run (); - void ManageTunnels (); - void ManageOutboundTunnels (); - void ManageInboundTunnels (); - void ManageTransitTunnels (); - void ManagePendingTunnels (); + void ManageTunnels (uint64_t ts); + void ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate); + void ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate); + void ManagePendingTunnels (uint64_t ts); template - void ManagePendingTunnels (PendingTunnels& pendingTunnels); + 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; std::thread * m_Thread; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; - std::list > m_TransitTunnels; + mutable std::mutex m_TunnelsMutex; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id - std::mutex m_PoolsMutex; + mutable std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; - i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; - - // some stats - int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; - + uint32_t m_MaxNumTransitTunnels; + // count of tunnels for total TCSR algorithm + int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations; + double m_TunnelCreationSuccessRate; + int m_TunnelCreationAttemptsNum; + std::mt19937 m_Rng; + TransitTunnels m_TransitTunnels; + public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; - const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; + const auto& GetTransitTunnels () const { return m_TransitTunnels.GetTransitTunnels (); }; size_t CountTransitTunnels() const; size_t CountInboundTunnels() const; size_t CountOutboundTunnels() const; - int GetQueueSize () { return m_Queue.GetSize (); }; - int GetTunnelCreationSuccessRate () const // in percents + size_t GetQueueSize () const { return m_Queue.GetSize (); }; + size_t GetTBMQueueSize () const { return m_TransitTunnels.GetTunnelBuildMsgQueueSize (); }; + int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents + double GetPreciseTunnelCreationSuccessRate () const { return 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.cpp b/libi2pd/TunnelBase.cpp new file mode 100644 index 00000000..b5a4a0b3 --- /dev/null +++ b/libi2pd/TunnelBase.cpp @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include "Transports.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >&& msgs) + { + if (msgs.empty ()) return; + auto currentTransport = m_CurrentTransport.lock (); + if (!currentTransport) + { + // try to obtain transport from pending request or send thought transport is not complete + if (m_PendingTransport.valid ()) // pending request? + { + if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + { + // pending request complete + currentTransport = m_PendingTransport.get (); // take transports used in pending request + if (currentTransport) + { + if (currentTransport->IsEstablished ()) + m_CurrentTransport = currentTransport; + else + currentTransport = nullptr; + } + } + else // still pending + { + // send through transports, but don't update pending transport + i2p::transport::transports.SendMessages (to, std::move (msgs)); + return; + } + } + } + if (currentTransport) // session is good + // send to session directly + currentTransport->SendI2NPMessages (msgs); + else // no session yet + // send through transports + m_PendingTransport = i2p::transport::transports.SendMessages (to, std::move (msgs)); + + } + + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >& msgs) + { + std::list > msgs1; + msgs.swap (msgs1); + SendMessagesTo (to, std::move (msgs1)); + } + + void TunnelTransportSender::Reset () + { + m_CurrentTransport.reset (); + if (m_PendingTransport.valid ()) + m_PendingTransport = std::future >(); + } +} +} diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index 8d0edff1..39d6e780 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,12 +11,19 @@ #include #include +#include +#include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { +namespace transport +{ + class TransportSession; +} + namespace tunnel { const size_t TUNNEL_DATA_MSG_SIZE = 1028; @@ -41,7 +48,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 () {}; @@ -76,6 +83,25 @@ namespace tunnel return t1 < t2; } }; + + class TunnelTransportSender final + { + public: + + TunnelTransportSender () = default; + ~TunnelTransportSender () = default; + + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >&& msgs); + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >& msgs); // send and clear + + std::shared_ptr GetCurrentTransport () const { return m_CurrentTransport.lock (); } + void Reset (); + + private: + + std::weak_ptr m_CurrentTransport; + std::future > m_PendingTransport; + }; } } diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index e19b515d..fe0e8573 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -79,8 +79,7 @@ namespace tunnel uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); - decryption.SetIV (replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record); } void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 9dcf2c02..718a6fdb 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -181,6 +181,8 @@ namespace tunnel return peers; } + size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; }; + protected: // this constructor can't be called from outside diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 9c294374..66b7effa 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,10 +21,7 @@ namespace i2p { namespace tunnel { - TunnelEndpoint::~TunnelEndpoint () - { - } - + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; @@ -208,7 +205,7 @@ namespace tunnel if (msg.data->len + size > msg.data->maxLen) { // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); + auto newMsg = NewI2NPMessage (msg.data->len + size); *newMsg = *(msg.data); msg.data = newMsg; } @@ -261,9 +258,8 @@ namespace tunnel void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size) { - std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); - memcpy (f->data.data (), fragment, size); - if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) + if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, + isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second) LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -293,17 +289,17 @@ namespace tunnel if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); - size_t size = it->second->data.size (); + size_t size = it->second.data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); + auto newMsg = NewI2NPMessage (msg.data->len + size); *newMsg = *(msg.data); msg.data = newMsg; } - if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment + if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); - if (it->second->isLastFragment) + if (it->second.isLastFragment) // message complete msg.nextFragmentNum = 0; else @@ -323,11 +319,7 @@ namespace tunnel } uint8_t typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); - // catch RI or reply with new list of routers - if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && - !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) - i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data)); - + switch (msg.deliveryType) { case eDeliveryTypeLocal: @@ -335,13 +327,13 @@ namespace tunnel break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel - i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); + SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel - i2p::transport::transports.SendMessage (msg.hash, msg.data); + i2p::transport::transports.SendMessage (msg.hash, msg.data); // send right away, because most likely it's single message else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; @@ -356,7 +348,7 @@ namespace tunnel // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { - if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) + if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; @@ -370,5 +362,35 @@ namespace tunnel ++it; } } + + void TunnelEndpoint::SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr msg) + { + if (msg) + { + if (!m_Sender && m_I2NPMsgs.empty ()) // first message + m_CurrentHash = to; + else if (m_CurrentHash != to) // new target router + { + FlushI2NPMsgs (); // flush message to previous + if (m_Sender) m_Sender->Reset (); // reset sender + m_CurrentHash = to; // set new target router + } // otherwise add msg to the list for current target router + m_I2NPMsgs.push_back (msg); + } + } + + void TunnelEndpoint::FlushI2NPMsgs () + { + if (!m_I2NPMsgs.empty ()) + { + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (m_CurrentHash, m_I2NPMsgs); // send and clear + } + } + + const i2p::data::IdentHash * TunnelEndpoint::GetCurrentHash () const + { + return (m_Sender || !m_I2NPMsgs.empty ()) ? &m_CurrentHash : nullptr; + } } } diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 17590a5f..1e81c445 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -20,7 +22,7 @@ namespace i2p { namespace tunnel { - class TunnelEndpoint + class TunnelEndpoint final { struct TunnelMessageBlockEx: public TunnelMessageBlock { @@ -30,7 +32,8 @@ namespace tunnel struct Fragment { - Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; + Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): + isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); }; bool isLastFragment; uint64_t receiveTime; // milliseconds since epoch std::vector data; @@ -39,18 +42,23 @@ namespace tunnel public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; - ~TunnelEndpoint (); + ~TunnelEndpoint () = default; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); + void FlushI2NPMsgs (); + const i2p::data::IdentHash * GetCurrentHash () const; // return null if not available + const std::unique_ptr& GetSender () const { return m_Sender; }; + private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success void HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); + void SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added @@ -60,11 +68,15 @@ namespace tunnel private: std::unordered_map m_IncompleteMessages; - std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; uint32_t m_CurrentMsgID; + // I2NP messages to send + std::list > m_I2NPMsgs; // to send + i2p::data::IdentHash m_CurrentHash; // send msgs to + std::unique_ptr m_Sender; }; } } diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 12e7652f..9e27d207 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -35,6 +35,13 @@ namespace tunnel if (!m_CurrentTunnelDataMsg) { CreateCurrentTunnelDataMessage (); + if (block.data && block.data->onDrop) + { + // onDrop is called for the first fragment in tunnel message + // that's usually true for short TBMs or lookups + m_CurrentTunnelDataMsg->onDrop = block.data->onDrop; + block.data->onDrop = nullptr; + } messageCreated = true; } @@ -155,7 +162,6 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { - m_CurrentTunnelDataMsg = nullptr; m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; @@ -214,20 +220,24 @@ namespace tunnel void TunnelGateway::SendBuffer () { + // create list or tunnel messages m_Buffer.CompleteCurrentTunnelDataMessage (); - std::vector > newTunnelMsgs; + std::list > newTunnelMsgs; const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { auto newMsg = CreateEmptyTunnelDataMsg (false); - m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); - htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); + m_Tunnel.EncryptTunnelMsg (tunnelMsg, newMsg); + htobe32buf (newMsg->GetPayload (), m_Tunnel.GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); + if (tunnelMsg->onDrop) newMsg->onDrop = tunnelMsg->onDrop; newTunnelMsgs.push_back (newMsg); m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } m_Buffer.ClearTunnelDataMsgs (); - i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs); + // send + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs)); } } } diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h index 741bbe84..75f27581 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,18 +45,20 @@ namespace tunnel { public: - TunnelGateway (TunnelBase * tunnel): + TunnelGateway (TunnelBase& tunnel): m_Tunnel (tunnel), m_NumSentBytes (0) {}; void SendTunnelDataMsg (const TunnelMessageBlock& block); void PutTunnelDataMsg (const TunnelMessageBlock& block); void SendBuffer (); size_t GetNumSentBytes () const { return m_NumSentBytes; }; + const std::unique_ptr& GetSender () const { return m_Sender; }; private: - TunnelBase * m_Tunnel; + TunnelBase& m_Tunnel; TunnelGatewayBuffer m_Buffer; size_t m_NumSentBytes; + std::unique_ptr m_Sender; }; } } diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 204ac294..26367aa6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,13 +7,13 @@ */ #include -#include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" #include "NetDb.hpp" #include "Timestamp.h" #include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "Transports.h" #include "Log.h" #include "Tunnel.h" @@ -41,11 +41,12 @@ namespace tunnel } TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance): + int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance), - m_IsActive (true), m_CustomPeerSelector(nullptr) + m_IsActive (true), m_IsHighBandwidth (isHighBandwidth), m_CustomPeerSelector(nullptr), + m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) { if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY; @@ -59,7 +60,7 @@ namespace tunnel m_InboundVariance = (m_NumInboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumInboundHops : 0; if (m_OutboundVariance > 0 && m_NumOutboundHops + m_OutboundVariance > STANDARD_NUM_RECORDS) m_OutboundVariance = (m_NumOutboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumOutboundHops : 0; - m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL; + m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL; } TunnelPool::~TunnelPool () @@ -76,12 +77,12 @@ namespace tunnel if (m_NumInboundHops > size) { m_NumInboundHops = size; - LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has been adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; - LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has been adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; @@ -102,7 +103,10 @@ namespace tunnel it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } - m_Tests.clear (); + { + std::unique_lock l(m_TestsMutex); + m_Tests.clear (); + } } bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) @@ -137,7 +141,7 @@ namespace tunnel m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) - m_LocalDestination->SetLeaseSetUpdated (); + m_LocalDestination->SetLeaseSetUpdated (true); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -145,8 +149,11 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - for (auto& it: m_Tests) - if (it.second.second == expiredTunnel) it.second.second = nullptr; + { + std::unique_lock l(m_TestsMutex); + for (auto& it: m_Tests) + if (it.second.second == expiredTunnel) it.second.second = nullptr; + } std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); @@ -167,8 +174,11 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - for (auto& it: m_Tests) - if (it.second.first == expiredTunnel) it.second.first = nullptr; + { + std::unique_lock l(m_TestsMutex); + for (auto& it: m_Tests) + if (it.second.first == expiredTunnel) it.second.first = nullptr; + } std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); @@ -201,14 +211,14 @@ namespace tunnel } std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, - i2p::data::RouterInfo::CompatibleTransports compatible) const + i2p::data::RouterInfo::CompatibleTransports compatible) { std::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, excluded, compatible); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, - i2p::data::RouterInfo::CompatibleTransports compatible) const + i2p::data::RouterInfo::CompatibleTransports compatible) { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded, compatible); @@ -216,10 +226,10 @@ namespace tunnel template typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) { if (tunnels.empty ()) return nullptr; - uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; + uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; bool skipped = false; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) @@ -239,7 +249,7 @@ namespace tunnel } if (!tunnel && skipped) { - ind = rand () % (tunnels.size ()/2 + 1), i = 0; + ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) @@ -254,10 +264,11 @@ namespace tunnel return tunnel; } - std::shared_ptr TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) const + std::pair, bool> TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) { - if (old && old->IsEstablished ()) return old; + if (old && old->IsEstablished ()) return std::make_pair(old, false); std::shared_ptr tunnel; + bool freshTunnel = false; if (old) { std::unique_lock l(m_OutboundTunnelsMutex); @@ -270,8 +281,11 @@ namespace tunnel } if (!tunnel) + { tunnel = GetNextOutboundTunnel (); - return tunnel; + freshTunnel = true; + } + return std::make_pair(tunnel, freshTunnel); } void TunnelPool::CreateTunnels () @@ -282,8 +296,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; { @@ -291,20 +310,27 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) + if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0 && + m_NumInboundHops == m_NumOutboundHops) { for (auto it: m_OutboundTunnels) { + // try to create inbound tunnel through the same path as successive outbound CreatePairedInboundTunnel (it); num++; 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 + m_LocalDestination->SetLeaseSetUpdated (true); // update LeaseSet immediately } void TunnelPool::TestTunnels () @@ -325,9 +351,15 @@ namespace tunnel { it.second.first->SetState (eTunnelStateFailed); std::unique_lock l(m_OutboundTunnelsMutex); - m_OutboundTunnels.erase (it.second.first); + if (m_OutboundTunnels.size () > 1) // don't fail last tunnel + m_OutboundTunnels.erase (it.second.first); + else + { + it.second.first->SetState (eTunnelStateTestFailed); + CreateOutboundTunnel (); // create new tunnel immediately because last one failed + } } - else + else if (it.second.first->GetState () != eTunnelStateExpiring) it.second.first->SetState (eTunnelStateTestFailed); } if (it.second.second) @@ -336,48 +368,100 @@ namespace tunnel { it.second.second->SetState (eTunnelStateFailed); { - std::unique_lock l(m_InboundTunnelsMutex); - m_InboundTunnels.erase (it.second.second); + bool failed = false; + { + std::unique_lock l(m_InboundTunnelsMutex); + if (m_InboundTunnels.size () > 1) // don't fail last tunnel + { + m_InboundTunnels.erase (it.second.second); + failed = true; + } + else + { + it.second.second->SetState (eTunnelStateTestFailed); + CreateInboundTunnel (); // create new tunnel immediately because last one failed + } + } + if (failed && m_LocalDestination) + m_LocalDestination->SetLeaseSetUpdated (true); } if (m_LocalDestination) - m_LocalDestination->SetLeaseSetUpdated (); + m_LocalDestination->SetLeaseSetUpdated (true); } - else + else if (it.second.second->GetState () != eTunnelStateExpiring) it.second.second->SetState (eTunnelStateTestFailed); } } // new tests - std::unique_lock l1(m_OutboundTunnelsMutex); - auto it1 = m_OutboundTunnels.begin (); - std::unique_lock l2(m_InboundTunnelsMutex); - auto it2 = m_InboundTunnels.begin (); - while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) + if (!m_LocalDestination) return; + std::vector, std::shared_ptr > > newTests; + std::vector > outboundTunnels; { - bool failed = false; - if ((*it1)->IsFailed ()) - { - failed = true; - ++it1; - } - if ((*it2)->IsFailed ()) - { - failed = true; - ++it2; - } - if (!failed) - { - uint32_t msgID; - RAND_bytes ((uint8_t *)&msgID, 4); - { - std::unique_lock l(m_TestsMutex); - m_Tests[msgID] = std::make_pair (*it1, *it2); - } - (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), - CreateDeliveryStatusMsg (msgID)); - ++it1; ++it2; - } + std::unique_lock l(m_OutboundTunnelsMutex); + for (auto& it: m_OutboundTunnels) + if (it->IsEstablished ()) + outboundTunnels.push_back (it); } + std::shuffle (outboundTunnels.begin(), outboundTunnels.end(), m_Rng); + std::vector > inboundTunnels; + { + std::unique_lock l(m_InboundTunnelsMutex); + for (auto& it: m_InboundTunnels) + if (it->IsEstablished ()) + inboundTunnels.push_back (it); + } + std::shuffle (inboundTunnels.begin(), inboundTunnels.end(), m_Rng); + auto it1 = outboundTunnels.begin (); + auto it2 = inboundTunnels.begin (); + while (it1 != outboundTunnels.end () && it2 != inboundTunnels.end ()) + { + newTests.push_back(std::make_pair (*it1, *it2)); + ++it1; ++it2; + } + bool isECIES = m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + for (auto& it: newTests) + { + uint32_t msgID; + RAND_bytes ((uint8_t *)&msgID, 4); + { + std::unique_lock l(m_TestsMutex); + m_Tests[msgID] = it; + } + auto msg = CreateTunnelTestMsg (msgID); + auto outbound = it.first; + auto s = shared_from_this (); + msg->onDrop = [msgID, outbound, s]() + { + // if test msg dropped locally it's outbound tunnel to blame + outbound->SetState (eTunnelStateFailed); + { + std::unique_lock l(s->m_TestsMutex); + s->m_Tests.erase (msgID); + } + { + std::unique_lock l(s->m_OutboundTunnelsMutex); + s->m_OutboundTunnels.erase (outbound); + } + }; + // encrypt + if (isECIES) + { + uint8_t key[32]; RAND_bytes (key, 32); + uint64_t tag; RAND_bytes ((uint8_t *)&tag, 8); + m_LocalDestination->SubmitECIESx25519Key (key, tag); + msg = i2p::garlic::WrapECIESX25519Message (msg, key, tag); + } + else + { + uint8_t key[32], tag[32]; + RAND_bytes (key, 32); RAND_bytes (tag, 32); + m_LocalDestination->SubmitSessionKey (key, tag); + i2p::garlic::ElGamalAESSession garlic (key, tag); + msg = garlic.WrapSingleMessage (msg); + } + outbound->SendTunnelDataMsgTo (it.second->GetNextIdentHash (), it.second->GetNextTunnelID (), msg); + } } void TunnelPool::ManageTunnels (uint64_t ts) @@ -386,7 +470,7 @@ namespace tunnel { CreateTunnels (); TestTunnels (); - m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2; + m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL)/2; } } @@ -399,12 +483,25 @@ namespace tunnel } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) + { + if (m_LocalDestination) + m_LocalDestination->ProcessDeliveryStatusMessage (msg); + else + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); + } + + void TunnelPool::ProcessTunnelTest (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); uint32_t msgID = bufbe32toh (buf); buf += 4; uint64_t timestamp = bufbe64toh (buf); + ProcessTunnelTest (msgID, timestamp); + } + + bool TunnelPool::ProcessTunnelTest (uint32_t msgID, uint64_t timestamp) + { decltype(m_Tests)::mapped_type test; bool found = false; { @@ -419,58 +516,68 @@ namespace tunnel } if (found) { - uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; - LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " milliseconds"); + int dlt = (uint64_t)i2p::util::GetMonotonicMicroseconds () - (int64_t)timestamp; + LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " microseconds"); + if (dlt < 0) dlt = 0; // should not happen int numHops = 0; if (test.first) numHops += test.first->GetNumHops (); if (test.second) numHops += test.second->GetNumHops (); // restore from test failed state if any if (test.first) { - if (test.first->GetState () == eTunnelStateTestFailed) + if (test.first->GetState () != eTunnelStateExpiring) test.first->SetState (eTunnelStateEstablished); // update latency - uint64_t latency = 0; + int latency = 0; if (numHops) latency = dlt*test.first->GetNumHops ()/numHops; if (!latency) latency = dlt/2; - test.first->AddLatencySample(latency); + test.first->AddLatencySample (latency); } if (test.second) { - if (test.second->GetState () == eTunnelStateTestFailed) + if (test.second->GetState () != eTunnelStateExpiring) test.second->SetState (eTunnelStateEstablished); // update latency - uint64_t latency = 0; + int latency = 0; if (numHops) latency = dlt*test.second->GetNumHops ()/numHops; if (!latency) latency = dlt/2; - test.second->AddLatencySample(latency); + test.second->AddLatencySample (latency); } } - else - { - if (m_LocalDestination) - m_LocalDestination->ProcessDeliveryStatusMessage (msg); - else - LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); - } - } - + return found; + } + bool TunnelPool::IsExploratory () const { return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); } - std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, bool reverse) const + std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, + bool reverse, bool endpoint) const { - auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): - i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse); - - if (!hop || hop->GetProfile ()->IsBad ()) - hop = i2p::data::netdb.GetRandomRouter (prevHop, reverse); + bool tryClient = !IsExploratory () && !i2p::context.IsLimitedConnectivity (); + std::shared_ptr hop; + for (int i = 0; i < TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS; i++) + { + hop = tryClient ? + (m_IsHighBandwidth ? + i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) : + i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, true)): + i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); + if (hop) + { + if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ()) + break; + } + else if (tryClient) + tryClient = false; + else + return nullptr; + } 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,9 +593,9 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - auto r = i2p::transport::transports.GetRandomPeer (); - if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && - (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable + auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); + if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) && + (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 { prevHop = r; path.Add (r); @@ -498,11 +605,11 @@ namespace tunnel for(int i = start; i < numHops; i++ ) { - auto hop = nextHop (prevHop, inbound); + auto hop = nextHop (prevHop, inbound, i == numHops - 1); 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) @@ -510,12 +617,6 @@ namespace tunnel LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } - if ((i == numHops - 1) && (!hop->IsV4 () || // doesn't support ipv4 - (inbound && !hop->IsReachable ()))) // IBGW is not reachable - { - auto hop1 = nextHop (prevHop, true); - if (hop1) hop = hop1; - } prevHop = hop; path.Add (hop); } @@ -534,7 +635,7 @@ namespace tunnel numHops = m_NumInboundHops; if (m_InboundVariance) { - int offset = rand () % (std::abs (m_InboundVariance) + 1); + int offset = m_Rng () % (std::abs (m_InboundVariance) + 1); if (m_InboundVariance < 0) offset = -offset; numHops += offset; } @@ -544,7 +645,7 @@ namespace tunnel numHops = m_NumOutboundHops; if (m_OutboundVariance) { - int offset = rand () % (std::abs (m_OutboundVariance) + 1); + int offset = m_Rng () % (std::abs (m_OutboundVariance) + 1); if (m_OutboundVariance < 0) offset = -offset; numHops += offset; } @@ -557,14 +658,15 @@ namespace tunnel if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } - return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); + return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { + if (!m_ExplicitPeers->size ()) return false; int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); - if (!numHops) return false; for (int i = 0; i < numHops; i++) { auto& ident = (*m_ExplicitPeers)[i]; @@ -628,8 +730,13 @@ namespace tunnel outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; - if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) - config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); + if (m_NumInboundHops > 0) + { + auto peers = tunnel->GetPeers(); + if (peers.size ()&& ValidatePeers (peers)) + config = std::make_shared(tunnel->GetPeers (), + tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); + } if (!m_NumInboundHops || config) { auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); @@ -693,10 +800,12 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); std::shared_ptr config; - if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) + if (m_NumOutboundHops > 0) { - config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); + auto peers = tunnel->GetPeers(); + if (peers.size () && ValidatePeers (peers)) + config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); } if (!m_NumOutboundHops || config) { @@ -737,11 +846,26 @@ namespace tunnel return m_CustomPeerSelector != nullptr; } + bool TunnelPool::ValidatePeers (std::vector >& peers) const + { + bool highBandwidth = !IsExploratory (); + for (auto it: peers) + { + auto r = i2p::data::netdb.FindRouter (it->GetIdentHash ()); + if (r) + { + if (r->IsHighCongestion (highBandwidth)) return false; + it = r->GetIdentity (); // use identity from updated RouterInfo + } + } + return true; + } + std::shared_ptr TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_InboundTunnelsMutex); - uint64_t min = 1000000; + int min = 1000000; for (const auto & itr : m_InboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); @@ -757,7 +881,7 @@ namespace tunnel { std::shared_ptr tun = nullptr; std::unique_lock lock(m_OutboundTunnelsMutex); - uint64_t min = 1000000; + int min = 1000000; for (const auto & itr : m_OutboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index d8c60d69..0ebfd1ac 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ #include #include #include +#include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" @@ -30,6 +31,8 @@ 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 = 3; + const int TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS = 3; class Tunnel; class InboundTunnel; @@ -53,16 +56,13 @@ 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, bool)> SelectHopFunc; public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance); + int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -78,13 +78,15 @@ namespace tunnel void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr, - i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports); std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr, - i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; - std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports); + std::pair, bool> GetNewOutboundTunnel (std::shared_ptr old); void ManageTunnels (uint64_t ts); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); + void ProcessTunnelTest (std::shared_ptr msg); + bool ProcessTunnelTest (uint32_t msgID, uint64_t timestamp); bool IsExploratory () const; bool IsActive () const { return m_IsActive; }; @@ -104,7 +106,7 @@ namespace tunnel bool HasCustomPeerSelector(); /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ - void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } + void RequireLatency(int min, int max) { m_MinLatency = min; m_MaxLatency = max; } /** @brief return true if this tunnel pool has a latency requirement */ bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } @@ -114,8 +116,11 @@ namespace tunnel std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude = nullptr) const; // for overriding tunnel peer selection - std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse) const; + std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse, bool endpoint) const; + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); + std::mt19937& GetRng () { return m_Rng; } + private: void TestTunnels (); @@ -124,9 +129,10 @@ namespace tunnel void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const; + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible); bool SelectPeers (Path& path, bool isInbound); bool SelectExplicitPeers (Path& path, bool isInbound); + bool ValidatePeers (std::vector >& peers) const; private: @@ -140,14 +146,16 @@ namespace tunnel std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; - bool m_IsActive; + bool m_IsActive, m_IsHighBandwidth; uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; - uint64_t m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms - uint64_t m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms + int m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms + int m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms + std::mt19937 m_Rng; + public: // for HTTP only diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 905fab75..7dc11157 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -37,14 +37,14 @@ namespace api i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); - bool avx; i2p::config::GetOption("cpuext.avx", avx); - bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); - i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); + i2p::crypto::InitCrypto (precomputation); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); + bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); + i2p::transport::transports.SetCheckReserved(checkReserved); + i2p::context.Init (); } @@ -60,17 +60,22 @@ namespace api else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); + i2p::transport::InitTransports (); LogPrint(eLogInfo, "API: Starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: Starting Transports"); i2p::transport::transports.Start(); LogPrint(eLogInfo, "API: Starting Tunnels"); i2p::tunnel::tunnels.Start(); + LogPrint(eLogInfo, "API: Starting Router context"); + i2p::context.Start(); } void StopI2P () { LogPrint(eLogInfo, "API: Shutting down"); + LogPrint(eLogInfo, "API: Stopping Router context"); + i2p::context.Stop(); LogPrint(eLogInfo, "API: Stopping Tunnels"); i2p::tunnel::tunnels.Stop(); LogPrint(eLogInfo, "API: Stopping Transports"); diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 10de346f..925cf629 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,13 +8,15 @@ #include #include +#include +#include #include #include "util.h" #include "Log.h" #include "I2PEndian.h" -#if not defined (__FreeBSD__) +#if !defined (__FreeBSD__) && !defined(_MSC_VER) #include #endif @@ -22,6 +24,20 @@ #include #endif +#if defined(__APPLE__) +# include +#endif + +#if defined(__HAIKU__) +#include +#include +#include +#include +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#include +#endif +#endif #ifdef _WIN32 #include @@ -33,6 +49,19 @@ #include #include +#if defined(_MSC_VER) +const DWORD MS_VC_EXCEPTION = 0x406D1388; +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; +} THREADNAME_INFO; +#pragma pack(pop) +#endif + #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) @@ -71,7 +100,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) ZeroMemory(&ss, sizeof(ss)); ss.ss_family = af; - switch(af) { + switch (af) + { case AF_INET: ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; break; @@ -81,7 +111,7 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) default: return NULL; } - /* cannot direclty use &size because of strict aliasing rules */ + /* cannot directly use &size because of strict aliasing rules */ return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)? dst : NULL; } @@ -94,8 +124,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) #endif #endif -#define address_pair_v4(a,b) { boost::asio::ip::address_v4::from_string (a).to_ulong (), boost::asio::ip::address_v4::from_string (b).to_ulong () } -#define address_pair_v6(a,b) { boost::asio::ip::address_v6::from_string (a).to_bytes (), boost::asio::ip::address_v6::from_string (b).to_bytes () } +#define address_pair_v4(a,b) std::pair{ boost::asio::ip::make_address (a).to_v4 ().to_uint (), boost::asio::ip::make_address(b).to_v4 ().to_uint () } +#define address_pair_v6(a,b) std::pair{ boost::asio::ip::make_address (a).to_v6 ().to_bytes (), boost::asio::ip::make_address(b).to_v6 ().to_bytes () } namespace i2p { @@ -142,15 +172,46 @@ namespace util } } + void RunnableService::SetName (std::string_view name) + { + if (name.length() < 16) + m_Name = name; + else + m_Name = name.substr(0,15); + } + void SetThreadName (const char *name) { -#if defined(__APPLE__) && !defined(__powerpc__) +#if defined(__APPLE__) +# if (!defined(MAC_OS_X_VERSION_10_6) || \ + (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) || \ + defined(__POWERPC__)) + /* pthread_setname_np is not there on <10.6 and all PPC. + So do nothing. */ +# else pthread_setname_np((char*)name); +# endif #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); #elif !defined(__gnu_hurd__) + #if defined(_MSC_VER) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = -1; + info.dwFlags = 0; + #pragma warning(push) + #pragma warning(disable: 6320 6322) + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + } + #pragma warning(pop) + #else pthread_setname_np(pthread_self(), name); + #endif #endif } @@ -160,7 +221,7 @@ namespace net int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); - IPN inetntop = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); + IPN inetntop = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); if (!inetntop) inetntop = inet_ntop_xp; // use own implementation if not found ULONG outBufLen = 0; @@ -168,7 +229,7 @@ namespace net PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; - if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + if (GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); @@ -179,7 +240,7 @@ namespace net AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if(dwRetVal != NO_ERROR) + if (dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); @@ -187,19 +248,17 @@ namespace net } pCurrAddresses = pAddresses; - while(pCurrAddresses) + while (pCurrAddresses) { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - pUnicast = pCurrAddresses->FirstUnicastAddress; - if(pUnicast == nullptr) + if (pUnicast == nullptr) LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); - for(int i = 0; pUnicast != nullptr; ++i) + while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; - if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) + if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { char addr[INET_ADDRSTRLEN]; inetntop(AF_INET, &(((struct sockaddr_in *)localInterfaceAddress)->sin_addr), addr, INET_ADDRSTRLEN); @@ -223,7 +282,7 @@ namespace net int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); - IPN inetntop = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); + IPN inetntop = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); if (!inetntop) inetntop = inet_ntop_xp; // use own implementation if not found ULONG outBufLen = 0; @@ -253,12 +312,11 @@ namespace net pCurrAddresses = pAddresses; while (pCurrAddresses) { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if (pUnicast == nullptr) LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv6 address, this is not supported"); - for (int i = 0; pUnicast != nullptr; ++i) + while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -303,16 +361,16 @@ namespace net #endif typedef int (* IPN)(int af, const char *src, void *dst); - IPN inetpton = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); + IPN inetpton = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found - if(localAddress.is_v4()) + if (localAddress.is_v4()) { sockaddr_in inputAddress; inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } - else if(localAddress.is_v6()) + else if (localAddress.is_v6()) { sockaddr_in6 inputAddress; inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); @@ -328,7 +386,7 @@ namespace net int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; - if(getifaddrs(&ifaddr) == -1) + if (getifaddrs(&ifaddr) == -1) { LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); return fallback; @@ -336,34 +394,34 @@ namespace net int family = 0; // look for interface matching local address - for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if(!ifa->ifa_addr) + if (!ifa->ifa_addr) continue; family = ifa->ifa_addr->sa_family; - if(family == AF_INET && localAddress.is_v4()) + if (family == AF_INET && localAddress.is_v4()) { sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; - if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) + if (!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) break; // address matches } - else if(family == AF_INET6 && localAddress.is_v6()) + else if (family == AF_INET6 && localAddress.is_v6()) { sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; - if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) + if (!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) break; // address matches } } int mtu = fallback; - if(ifa && family) + if (ifa && family) { // interface found? int fd = socket(family, SOCK_DGRAM, 0); - if(fd > 0) + if (fd > 0) { ifreq ifr; strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ-1); // set interface for query - if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) + if (ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); @@ -396,10 +454,10 @@ namespace net { #ifdef _WIN32 LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32"); - if(ipv6) - return boost::asio::ip::address::from_string("::1"); + if (ipv6) + return boost::asio::ip::make_address("::1"); else - return boost::asio::ip::address::from_string("127.0.0.1"); + return boost::asio::ip::make_address("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs *addrs; @@ -415,13 +473,13 @@ namespace net // match char addr[INET6_ADDRSTRLEN]; memset (addr, 0, INET6_ADDRSTRLEN); - if(af == AF_INET) + if (af == AF_INET) inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN); else inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN); freeifaddrs(addrs); std::string cur_ifaddr(addr); - return boost::asio::ip::address::from_string(cur_ifaddr); + return boost::asio::ip::make_address(cur_ifaddr); } } } @@ -431,9 +489,9 @@ namespace net LogPrint(eLogError, "NetIface: Exception while searching address using ifaddr: ", ex.what()); } - if(addrs) freeifaddrs(addrs); + if (addrs) freeifaddrs(addrs); std::string fallback; - if(ipv6) + if (ipv6) { fallback = "::1"; LogPrint(eLogWarning, "NetIface: Cannot find IPv6 address for interface ", ifname); @@ -441,7 +499,7 @@ namespace net fallback = "127.0.0.1"; LogPrint(eLogWarning, "NetIface: Cannot find IPv4 address for interface ", ifname); } - return boost::asio::ip::address::from_string(fallback); + return boost::asio::ip::make_address(fallback); #endif } @@ -477,6 +535,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) @@ -485,7 +559,7 @@ namespace net PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; - if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); @@ -496,7 +570,7 @@ namespace net AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if(dwRetVal != NO_ERROR) + if (dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetYggdrasilAddress(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); @@ -504,12 +578,11 @@ namespace net } pCurrAddresses = pAddresses; - while(pCurrAddresses) + while (pCurrAddresses) { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; - for(int i = 0; pUnicast != nullptr; ++i) + while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -553,7 +626,7 @@ namespace net LogPrint(eLogError, "NetIface: Exception while searching Yggdrasill address using ifaddr: ", ex.what()); } LogPrint(eLogWarning, "NetIface: Interface with Yggdrasil network address not found"); - if(addrs) freeifaddrs(addrs); + if (addrs) freeifaddrs(addrs); return boost::asio::ip::address_v6 (); #endif } @@ -573,9 +646,10 @@ namespace net { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if (host.is_unspecified ()) return false; - if(host.is_v4()) + if (host.is_v4()) { - static const std::vector< std::pair > reservedIPv4Ranges { + static const std::array, 14> reservedIPv4Ranges + { address_pair_v4("0.0.0.0", "0.255.255.255"), address_pair_v4("10.0.0.0", "10.255.255.255"), address_pair_v4("100.64.0.0", "100.127.255.255"), @@ -592,15 +666,17 @@ namespace net address_pair_v4("224.0.0.0", "255.255.255.255") }; - uint32_t ipv4_address = host.to_v4 ().to_ulong (); - for(const auto& it : reservedIPv4Ranges) { + uint32_t ipv4_address = host.to_v4 ().to_uint (); + for (const auto& it : reservedIPv4Ranges) { if (ipv4_address >= it.first && ipv4_address <= it.second) return true; } } - if(host.is_v6()) + if (host.is_v6()) { - static const std::vector< std::pair > reservedIPv6Ranges { + static const std::array, 7> reservedIPv6Ranges + { + address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64 address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), @@ -610,7 +686,7 @@ namespace net }; boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); - for(const auto& it : reservedIPv6Ranges) { + for (const auto& it : reservedIPv6Ranges) { if (ipv6_address >= it.first && ipv6_address <= it.second) return true; } diff --git a/libi2pd/util.h b/libi2pd/util.h index 9420060b..7bd35e67 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -112,7 +112,7 @@ namespace util }; template - class MemoryPoolMt: public MemoryPool + class MemoryPoolMt: private MemoryPool { public: @@ -131,6 +131,14 @@ namespace util this->Release (t); } + void ReleaseMt (T * * arr, size_t num) + { + if (!arr || !num) return; + std::lock_guard l(m_Mutex); + for (size_t i = 0; i < num; i++) + this->Release (arr[i]); + } + templateclass C, typename... R> void ReleaseMt(const C& c) { @@ -169,12 +177,14 @@ namespace util RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {} virtual ~RunnableService () {} - boost::asio::io_service& GetIOService () { return m_Service; } + auto& GetIOService () { return m_Service; } bool IsRunning () const { return m_IsRunning; }; void StartIOService (); void StopIOService (); + void SetName (std::string_view name); + private: void Run (); @@ -184,7 +194,7 @@ namespace util std::string m_Name; volatile bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_service m_Service; + boost::asio::io_context m_Service; }; class RunnableServiceWithWork: public RunnableService @@ -192,11 +202,11 @@ namespace util protected: RunnableServiceWithWork (const std::string& name): - RunnableService (name), m_Work (GetIOService ()) {} + RunnableService (name), m_Work (GetIOService ().get_executor ()) {} private: - boost::asio::io_service::work m_Work; + boost::asio::executor_work_guard m_Work; }; void SetThreadName (const char *name); @@ -224,6 +234,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 5b6925cc..1e63ae08 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,16 +11,18 @@ #define CODENAME "Purple" +#define XSTRINGIZE(x) STRINGIZE(x) #define STRINGIZE(x) #x + #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 43 +#define I2PD_VERSION_MINOR 56 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #ifdef GITVER - #define I2PD_VERSION GITVER + #define I2PD_VERSION XSTRINGIZE(GITVER) #else #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #endif @@ -31,7 +33,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 55 +#define I2P_VERSION_MICRO 65 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index e67e3f06..8333e87d 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,15 +9,15 @@ #include #include #include -#include +#include #include #include #include #include #include -#include #include "Base.h" #include "util.h" +#include "Timestamp.h" #include "Identity.h" #include "FS.h" #include "Log.h" @@ -27,6 +27,14 @@ #include "AddressBook.h" #include "Config.h" +#if STD_FILESYSTEM +#include +namespace fs_lib = std::filesystem; +#else +#include +namespace fs_lib = boost::filesystem; +#endif + namespace i2p { namespace client @@ -42,22 +50,23 @@ namespace client if (m_IsPersist) i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); } - std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; - void AddAddress (std::shared_ptr address); - void RemoveAddress (const i2p::data::IdentHash& ident); + std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) override; + void AddAddress (std::shared_ptr address) override; + void RemoveAddress (const i2p::data::IdentHash& ident) override; + void CleanUpCache () override; - bool Init (); - int Load (std::map > & addresses); - int LoadLocal (std::map >& addresses); - int Save (const std::map >& addresses); - - void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); - bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); - void ResetEtags (); + bool Init () override; + int Load (Addresses& addresses) override; + int LoadLocal (Addresses& addresses) override; + int Save (const Addresses& addresses) override; + void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified) override; + bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) override; + void ResetEtags () override; + private: - int LoadFromFile (const std::string& filename, std::map >& addresses); // returns -1 if can't open file, otherwise number of records + int LoadFromFile (const std::string& filename, Addresses& addresses); // returns -1 if can't open file, otherwise number of records private: @@ -65,6 +74,8 @@ namespace client std::string etagsPath, indexPath, localPath; bool m_IsPersist; std::string m_HostsFile; // file to dump hosts.txt, empty if not used + std::unordered_map, uint64_t> > m_FullAddressCache; // ident hash -> (full ident buffer, last access timestamp) + std::mutex m_FullAddressCacheMutex; }; bool AddressBookFilesystemStorage::Init() @@ -85,8 +96,19 @@ namespace client return false; } - std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) { + auto ts = i2p::util::GetMonotonicSeconds (); + { + std::lock_guard l(m_FullAddressCacheMutex); + auto it = m_FullAddressCache.find (ident); + if (it != m_FullAddressCache.end ()) + { + it->second.second = ts; + return std::make_shared(it->second.first.data (), it->second.first.size ()); + } + } + if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); @@ -94,48 +116,72 @@ namespace client } std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); - if (!f.is_open ()) { + if (!f.is_open ()) + { LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); return nullptr; } f.seekg (0,std::ios::end); size_t len = f.tellg (); - if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { + if (len < i2p::data::DEFAULT_IDENTITY_SIZE) + { LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); return nullptr; } f.seekg(0, std::ios::beg); - uint8_t * buf = new uint8_t[len]; - f.read((char *)buf, len); - auto address = std::make_shared(buf, len); - delete[] buf; - return address; + std::vector buf(len); + f.read((char *)buf.data (), len); + if (!f) + { + LogPrint (eLogError, "Addressbook: Couldn't read ", filename); + return nullptr; + } + { + std::lock_guard l(m_FullAddressCacheMutex); + m_FullAddressCache.try_emplace (ident, buf, ts); + } + return std::make_shared(buf.data (), len); } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - if (!m_IsPersist) return; - std::string path = storage.Path( address->GetIdentHash().ToBase32() ); - std::ofstream f (path, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) { - LogPrint (eLogError, "Addressbook: Can't open file ", path); - return; - } + if (!address) return; size_t len = address->GetFullLen (); - uint8_t * buf = new uint8_t[len]; - address->ToBuffer (buf, len); - f.write ((char *)buf, len); - delete[] buf; + std::vector buf; + if (!len) return; // invalid address + { + std::lock_guard l(m_FullAddressCacheMutex); + auto [it, inserted] = m_FullAddressCache.try_emplace (address->GetIdentHash(), len, i2p::util::GetMonotonicSeconds ()); + if (inserted) + address->ToBuffer (it->second.first.data (), len); + if (m_IsPersist) + buf = it->second.first; + } + if (m_IsPersist && !buf.empty ()) + { + std::string path = storage.Path(address->GetIdentHash().ToBase32()); + std::ofstream f (path, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) + { + LogPrint (eLogError, "Addressbook: Can't open file ", path); + return; + } + f.write ((const char *)buf.data (), len); + } } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { + { + std::lock_guard l(m_FullAddressCacheMutex); + m_FullAddressCache.erase (ident); + } if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } - int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map >& addresses) + int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode @@ -161,7 +207,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Load (std::map >& addresses) + int AddressBookFilesystemStorage::Load (Addresses& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) @@ -175,7 +221,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::LoadLocal (std::map >& addresses) + int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; @@ -183,7 +229,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Save (const std::map >& addresses) + int AddressBookFilesystemStorage::Save (const Addresses& addresses) { if (addresses.empty()) { @@ -266,17 +312,30 @@ namespace client void AddressBookFilesystemStorage::ResetEtags () { LogPrint (eLogError, "Addressbook: Resetting eTags"); - for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it) + for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) { - if (!boost::filesystem::is_regular_file (it->status ())) + if (!fs_lib::is_regular_file (it->status ())) continue; - boost::filesystem::remove (it->path ()); + fs_lib::remove (it->path ()); } } + void AddressBookFilesystemStorage::CleanUpCache () + { + auto ts = i2p::util::GetMonotonicSeconds (); + std::lock_guard l(m_FullAddressCacheMutex); + for (auto it = m_FullAddressCache.begin (); it != m_FullAddressCache.end ();) + { + if (ts > it->second.second + ADDRESS_CACHE_EXPIRATION_TIMEOUT) + it = m_FullAddressCache.erase (it); + else + it++; + } + } + //--------------------------------------------------------------------- - Address::Address (const std::string& b32): + Address::Address (std::string_view b32): addressType (eAddressInvalid) { if (b32.length () <= B33_ADDRESS_THRESHOLD) @@ -298,7 +357,7 @@ namespace client identHash = hash; } - AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), + AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr), m_IsEnabled (true) { @@ -313,14 +372,15 @@ namespace client { i2p::config::GetOption("addressbook.enabled", m_IsEnabled); if (m_IsEnabled) - { + { if (!m_Storage) m_Storage = new AddressBookFilesystemStorage; m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); StartLookups (); - } + ScheduleCacheUpdate (); + } } void AddressBook::StartResolvers () @@ -334,23 +394,36 @@ namespace client StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { - delete m_SubscriptionsUpdateTimer; + m_SubscriptionsUpdateTimer->cancel (); m_SubscriptionsUpdateTimer = nullptr; } - if (m_IsDownloading) + if (m_AddressCacheUpdateTimer) { - LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); - for (int i = 0; i < 30; i++) - { - if (!m_IsDownloading) + m_AddressCacheUpdateTimer->cancel (); + m_AddressCacheUpdateTimer = nullptr; + } + bool isDownloading = m_Downloading.valid (); + if (isDownloading) + { + if (m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + isDownloading = false; + else + { + LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); + for (int i = 0; i < 30; i++) { - LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); - break; + if (m_Downloading.wait_for(std::chrono::seconds(1)) == std::future_status::ready) // wait for 1 seconds + { + isDownloading = false; + LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); + break; + } } - std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds - } - LogPrint (eLogError, "Addressbook: Subscription download timeout"); - m_IsDownloading = false; + } + if (!isDownloading) + m_Downloading.get (); + else + LogPrint (eLogError, "Addressbook: Subscription download timeout"); } if (m_Storage) { @@ -362,7 +435,7 @@ namespace client m_Subscriptions.clear (); } - std::shared_ptr AddressBook::GetAddress (const std::string& address) + std::shared_ptr AddressBook::GetAddress (std::string_view address) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) @@ -370,17 +443,18 @@ namespace client auto addr = std::make_shared(address.substr (0, pos)); return addr->IsValid () ? addr : nullptr; } - else + else +#if __cplusplus >= 202002L // C++20 + if (address.ends_with (".i2p")) +#else + if (address.find (".i2p") != std::string::npos) +#endif { - pos = address.find (".i2p"); - if (pos != std::string::npos) - { - if (!m_IsEnabled) return nullptr; - auto addr = FindAddress (address); - if (!addr) - LookupAddress (address); // TODO: - return addr; - } + if (!m_IsEnabled) return nullptr; + auto addr = FindAddress (address); + if (!addr) + LookupAddress (address); // TODO: + return addr; } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; @@ -389,7 +463,7 @@ namespace client return std::make_shared(dest.GetIdentHash ()); } - std::shared_ptr AddressBook::FindAddress (const std::string& address) + std::shared_ptr AddressBook::FindAddress (std::string_view address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) @@ -397,6 +471,29 @@ namespace client return nullptr; } + bool AddressBook::RecordExists (const std::string& address, const std::string& jump) + { + auto addr = FindAddress(address); + if (!addr) + return false; + + auto pos = jump.find(".b32.i2p"); + if (pos != std::string::npos) + { + i2p::data::IdentHash identHash; + if (identHash.FromBase32(jump.substr (0, pos)) && identHash == addr->identHash) + return true; + } + else + { + i2p::data::IdentityEx ident; + if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) + return true; + } + + return false; + } + void AddressBook::InsertAddress (const std::string& address, const std::string& jump) { auto pos = jump.find(".b32.i2p"); @@ -411,7 +508,7 @@ namespace client auto ident = std::make_shared(); if (ident->FromBase64 (jump)) { - m_Storage->AddAddress (ident); + if (m_Storage) m_Storage->AddAddress (ident); m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); } @@ -422,18 +519,19 @@ namespace client void AddressBook::InsertFullAddress (std::shared_ptr address) { - m_Storage->AddAddress (address); + if (m_Storage) m_Storage->AddAddress (address); } std::shared_ptr AddressBook::GetFullAddress (const std::string& address) { auto addr = GetAddress (address); if (!addr || !addr->IsIdentHash ()) return nullptr; - return m_Storage->GetAddress (addr->identHash); + return m_Storage ? m_Storage->GetAddress (addr->identHash) : nullptr; } void AddressBook::LoadHosts () { + if (!m_Storage) return; if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; @@ -469,29 +567,35 @@ namespace client if (pos != std::string::npos) { - std::string name = s.substr(0, pos++); - std::string addr = s.substr(pos); + std::string_view name = std::string_view(s).substr(0, pos++); + std::string_view addr = std::string_view(s).substr(pos); size_t pos = addr.find('#'); - if (pos != std::string::npos) + if (pos != addr.npos) addr = addr.substr(0, pos); // remove comments - - pos = name.find(".b32.i2p"); - if (pos != std::string::npos) +#if __cplusplus >= 202002L // C++20 + if (name.ends_with (".b32.i2p")) +#else + if (name.find(".b32.i2p") != name.npos) +#endif { LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); continue; } - pos = name.find(".i2p"); - if (pos == std::string::npos) +#if __cplusplus >= 202002L // C++20 + if (!name.ends_with (".i2p")) +#else + if (name.find(".i2p") == name.npos) +#endif { LogPrint (eLogError, "Addressbook: Malformed domain: ", name); continue; } auto ident = std::make_shared (); - if (!ident->FromBase64(addr)) { + if (!ident->FromBase64(addr)) + { LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); incomplete = f.eof (); continue; @@ -504,15 +608,18 @@ namespace client ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); - m_Storage->AddAddress (ident); - m_Storage->RemoveAddress (it->second->identHash); + if (m_Storage) + { + m_Storage->AddAddress (ident); + m_Storage->RemoveAddress (it->second->identHash); + } LogPrint (eLogInfo, "Addressbook: Updated host: ", name); } } else { m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); - m_Storage->AddAddress (ident); + if (m_Storage) m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: Added new host: ", name); } @@ -524,7 +631,7 @@ namespace client if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; - m_Storage->Save (m_Addresses); + if (m_Storage) m_Storage->Save (m_Addresses); } return !incomplete; } @@ -548,16 +655,15 @@ namespace client } else { - LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config file"); + LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config"); // using config file items std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); for (const auto& s: subsList) - { - m_Subscriptions.push_back (std::make_shared (*this, s)); - } + if (!s.empty ()) + m_Subscriptions.push_back (std::make_shared (*this, s)); LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } } @@ -567,7 +673,8 @@ namespace client void AddressBook::LoadLocal () { - std::map> localAddresses; + if (!m_Storage) return; + AddressBookStorage::Addresses localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { @@ -610,7 +717,6 @@ namespace client void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { - m_IsDownloading = false; m_NumRetries++; int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT) @@ -641,13 +747,13 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ()); + m_SubscriptionsUpdateTimer = std::make_unique(dest->GetService ()); m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } else - LogPrint (eLogError, "Addressbook: Can't start subscriptions: missing shared local destination"); + LogPrint (eLogCritical, "Addressbook: Can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () @@ -665,7 +771,13 @@ namespace client LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); return; } - if (!m_IsDownloading && dest->IsReady ()) + bool isDownloading = m_Downloading.valid (); + if (isDownloading && m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + { + m_Downloading.get (); + isDownloading = false; + } + if (!isDownloading && dest->IsReady ()) { if (!m_IsLoaded) { @@ -674,17 +786,15 @@ namespace client std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); - m_IsDownloading = true; - std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); - load_hosts.detach(); // TODO: use join + m_Downloading = std::async (std::launch::async, + std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); - m_IsDownloading = true; - std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); - load_hosts.detach(); // TODO: use join + m_Downloading = std::async (std::launch::async, + std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); } } else @@ -721,7 +831,7 @@ namespace client } } - void AddressBook::LookupAddress (const std::string& address) + void AddressBook::LookupAddress (std::string_view address) { std::shared_ptr addr; auto dot = address.find ('.'); @@ -751,7 +861,7 @@ namespace client memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); - memcpy (buf + 9, address.c_str (), address.length ()); + memcpy (buf + 9, address.data (), address.length ()); datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } @@ -788,7 +898,30 @@ namespace client } } - AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): + void AddressBook::ScheduleCacheUpdate () + { + if (!m_AddressCacheUpdateTimer) + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + if(dest) + m_AddressCacheUpdateTimer = std::make_unique(dest->GetService ()); + } + if (m_AddressCacheUpdateTimer) + { + m_AddressCacheUpdateTimer->expires_from_now (boost::posix_time::seconds(ADDRESS_CACHE_UPDATE_INTERVAL )); + m_AddressCacheUpdateTimer->async_wait ( + [this](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_Storage) m_Storage->CleanUpCache (); + ScheduleCacheUpdate (); + } + }); + } + } + + AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link): m_Book (book), m_Link (link) { } @@ -819,40 +952,22 @@ namespace client } else m_Ident = addr->identHash; - /* this code block still needs some love */ - std::condition_variable newDataReceived; - std::mutex newDataReceivedMutex; - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); - if (!leaseSet) + // 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 + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (m_Ident, dest_port); + if (!stream) { - std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, - [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) - { - leaseSet = ls; - std::unique_lock l1(newDataReceivedMutex); - newDataReceived.notify_all (); - }); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - { - LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); - i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already - return false; - } - } - if (!leaseSet) { - /* still no leaseset found */ 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); } - /* save url parts for later use */ - std::string dest_host = url.host; - int dest_port = url.port ? url.port : 80; - /* 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"); @@ -863,34 +978,29 @@ 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(); req.version = "HTTP/1.1"; - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); - /* read response */ + // read response std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { - stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), - [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (bytes_transferred) - response.append ((char *)recv_buf, bytes_transferred); - if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) - end = true; - newDataReceived.notify_all (); - }, - SUBSCRIPTION_REQUEST_TIMEOUT); - std::unique_lock l(newDataReceivedMutex); - // wait 1 more second - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT + 1)) == std::cv_status::timeout) + size_t received = stream->Receive (recv_buf, 4096, SUBSCRIPTION_REQUEST_TIMEOUT); + if (received) + { + response.append ((char *)recv_buf, received); + if (!stream->IsOpen ()) end = true; + } + else if (!stream->IsOpen ()) + end = true; + else { LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); numAttempts++; @@ -900,7 +1010,7 @@ namespace client // 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) @@ -913,7 +1023,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) { @@ -936,7 +1046,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/AddressBook.h b/libi2pd_client/AddressBook.h index 192c4ebb..8b32aa93 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,10 +11,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include "Base.h" @@ -32,7 +34,9 @@ namespace client const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES = 10; // then update timeout - const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in second + const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in seconds + const int ADDRESS_CACHE_EXPIRATION_TIMEOUT = 710; // in seconds + const int ADDRESS_CACHE_UPDATE_INTERVAL = 76; // in seconds const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; @@ -45,7 +49,7 @@ namespace client i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; - Address (const std::string& b32); + Address (std::string_view b32); Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; bool IsValid () const { return addressType != eAddressInvalid; }; @@ -57,15 +61,18 @@ namespace client { public: + typedef std::map, std::less<> > Addresses; + virtual ~AddressBookStorage () {}; - virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 0; + virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) = 0; virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; + virtual void CleanUpCache () = 0; virtual bool Init () = 0; - virtual int Load (std::map >& addresses) = 0; - virtual int LoadLocal (std::map >& addresses) = 0; - virtual int Save (const std::map >& addresses) = 0; + virtual int Load (Addresses& addresses) = 0; + virtual int LoadLocal (Addresses& addresses) = 0; + virtual int Save (const Addresses& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0; @@ -77,19 +84,21 @@ namespace client class AddressBook { public: - + AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); - std::shared_ptr GetAddress (const std::string& address); + std::shared_ptr GetAddress (std::string_view address); std::shared_ptr GetFullAddress (const std::string& address); - std::shared_ptr FindAddress (const std::string& address); - void LookupAddress (const std::string& address); + std::shared_ptr FindAddress (std::string_view address); + void LookupAddress (std::string_view address); void InsertAddress (const std::string& address, const std::string& jump); // for jump links void InsertFullAddress (std::shared_ptr address); + bool RecordExists (const std::string& address, const std::string& jump); + bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address @@ -97,7 +106,8 @@ namespace client std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); - + bool IsEnabled () const { return m_IsEnabled; } + private: void StartSubscriptions (); @@ -113,19 +123,22 @@ namespace client void StopLookups (); void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void ScheduleCacheUpdate (); + private: std::mutex m_AddressBookMutex; - std::map > m_Addresses; + AddressBookStorage::Addresses m_Addresses; std::map > m_Resolvers; // local destination->resolver std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; - volatile bool m_IsLoaded, m_IsDownloading; + volatile bool m_IsLoaded; + std::future m_Downloading; int m_NumRetries; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet - boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; + std::unique_ptr m_SubscriptionsUpdateTimer, m_AddressCacheUpdateTimer; bool m_IsEnabled; }; @@ -133,7 +146,7 @@ namespace client { public: - AddressBookSubscription (AddressBook& book, const std::string& link); + AddressBookSubscription (AddressBook& book, std::string_view link); void CheckUpdates (); private: diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index a8520e9b..8d94e94b 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,24 @@ namespace i2p { namespace client { + void BOBI2PTunnelIncomingConnection::Established () + { + if (m_IsQuiet) + StreamReceive (); + else + { + // send destination first like received from I2P + std::string dest = GetStream ()->GetRemoteIdentity ()->ToBase64 (); + dest += "\n"; + if (dest.size() <= I2P_TUNNEL_CONNECTION_BUFFER_SIZE) + memcpy (GetStreamBuffer (), dest.c_str (), dest.size ()); + else + memset (GetStreamBuffer (), 0, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + HandleStreamReceive (boost::system::error_code (), dest.size ()); + } + Receive (); + } + BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) { @@ -127,9 +145,9 @@ namespace client connection->I2PConnect (receiver->data, receiver->dataLen); } - BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port, + BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), - m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet) + m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet) { } @@ -156,7 +174,7 @@ namespace client { if (stream) { - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); + auto conn = std::make_shared (this, stream, m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } @@ -164,7 +182,7 @@ namespace client BOBDestination::BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const int inport, const int outport, const bool quiet): + const uint16_t inport, const uint16_t outport, const bool quiet): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), @@ -209,7 +227,7 @@ namespace client } } - void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost) + void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) { if (!m_InboundTunnel) { @@ -220,7 +238,7 @@ namespace client if (!inhost.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (inhost, ec); + auto addr = boost::asio::ip::make_address (inhost, ec); if (!ec) ep.address (addr); else @@ -230,7 +248,7 @@ namespace client } } - void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet) + void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet) { if (!m_OutboundTunnel) { @@ -357,13 +375,13 @@ namespace client os << data << std::endl; } - void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out) + void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr dest, std::string &out) { // helper lambdas const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; - const auto destReady = [](const BOBDestination * const dest) { return dest->IsRunning(); }; + const auto destReady = [](const BOBDestination * const dest) { return dest && dest->IsRunning(); }; const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str // tunnel info @@ -373,9 +391,9 @@ namespace client const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost()); const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort()); const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort()); - const bool keys = destExists(dest); // key must exist when destination is created - const bool starting = destExists(dest) && !destReady(dest); - const bool running = destExists(dest) && destReady(dest); + const bool keys = destExists(dest.get ()); // key must exist when destination is created + const bool starting = destExists(dest.get ()) && !destReady(dest.get ()); + const bool running = destExists(dest.get ()) && destReady(dest.get ()); const bool stopping = false; // build line @@ -425,7 +443,7 @@ namespace client { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; - boost::asio::ip::address::from_string(m_InHost, ec); + boost::asio::ip::make_address(m_InHost, ec); if (ec) { SendReplyError("inhost must be a valid IPv4 address."); @@ -436,7 +454,7 @@ namespace client { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; - boost::asio::ip::address::from_string(m_OutHost, ec); + boost::asio::ip::make_address(m_OutHost, ec); if (ec) { SendReplyError("outhost must be a IPv4 address."); @@ -446,7 +464,7 @@ namespace client if (!m_CurrentDestination) { - m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command + m_CurrentDestination = std::make_shared (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } @@ -547,7 +565,7 @@ namespace client } - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } @@ -595,9 +613,12 @@ namespace client LogPrint (eLogDebug, "BOB: outport ", operand); if (*operand) { - m_OutPort = std::stoi(operand); - if (m_OutPort >= 0) + int port = std::stoi(operand); + if (port >= 0 && port < 65536) + { + m_OutPort = port; SendReplyOK ("outbound port set"); + } else SendReplyError ("port out of range"); } @@ -622,9 +643,12 @@ namespace client LogPrint (eLogDebug, "BOB: inport ", operand); if (*operand) { - m_InPort = std::stoi(operand); - if (m_InPort >= 0) + int port = std::stoi(operand); + if (port >= 0 && port < 65536) + { + m_InPort = port; SendReplyOK ("inbound port set"); + } else SendReplyError ("port out of range"); } @@ -660,7 +684,13 @@ namespace client SendReplyError ("Address Not found"); return; } - auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? + m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + if (!localDestination) + { + SendReplyError ("No local destination"); + return; + } if (addr->IsIdentHash ()) { // we might have leaseset already @@ -814,9 +844,9 @@ namespace client } } - BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): + BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port): RunnableService ("BOB"), - m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; @@ -869,8 +899,6 @@ namespace client { if (IsRunning ()) Stop (); - for (const auto& it: m_Destinations) - delete it.second; } void BOBCommandChannel::Start () @@ -887,9 +915,9 @@ namespace client StopIOService (); } - void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) + void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr dest) { - m_Destinations[name] = dest; + m_Destinations.emplace (name, dest); } void BOBCommandChannel::DeleteDestination (const std::string& name) @@ -898,12 +926,11 @@ namespace client if (it != m_Destinations.end ()) { it->second->Stop (); - delete it->second; m_Destinations.erase (it); } } - BOBDestination * BOBCommandChannel::FindDestination (const std::string& name) + std::shared_ptr BOBCommandChannel::FindDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 2e6314e2..f5aefd0a 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -71,6 +71,23 @@ namespace client const char BOB_HELP_STATUS[] = "status - Display status of a nicknamed tunnel."; const char BOB_HELP_HELP [] = "help - Get help on a command."; + class BOBI2PTunnelIncomingConnection: public I2PTunnelConnection + { + public: + + BOBI2PTunnelIncomingConnection (I2PService * owner, std::shared_ptr stream, + const boost::asio::ip::tcp::endpoint& target, bool quiet): + I2PTunnelConnection (owner, stream, target), m_IsQuiet (quiet) {}; + + protected: + + void Established () override; + + private: + + bool m_IsQuiet; // don't send destination + }; + class BOBI2PTunnel: public I2PService { public: @@ -124,7 +141,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -149,19 +166,19 @@ namespace client BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const int inport, const int outport, const bool quiet); + const uint16_t inport, const uint16_t outport, const bool quiet); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); - void CreateInboundTunnel (int port, const std::string& inhost); - void CreateOutboundTunnel (const std::string& outhost, int port, bool quiet); + void CreateInboundTunnel (uint16_t port, const std::string& inhost); + void CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet); const std::string& GetNickname() const { return m_Nickname; } const std::string& GetInHost() const { return m_InHost; } const std::string& GetOutHost() const { return m_OutHost; } - int GetInPort() const { return m_InPort; } - int GetOutPort() const { return m_OutPort; } + uint16_t GetInPort() const { return m_InPort; } + uint16_t GetOutPort() const { return m_OutPort; } bool GetQuiet() const { return m_Quiet; } bool IsRunning() const { return m_IsRunning; } const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; @@ -175,7 +192,7 @@ namespace client std::string m_Nickname; std::string m_InHost, m_OutHost; - int m_InPort, m_OutPort; + uint16_t m_InPort, m_OutPort; bool m_Quiet; bool m_IsRunning; }; @@ -228,7 +245,7 @@ namespace client void SendReplyError (const char * msg); void SendRaw (const char * data); - void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); + void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); private: @@ -237,10 +254,10 @@ namespace client boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; - int m_InPort, m_OutPort; + uint16_t m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; - BOBDestination * m_CurrentDestination; + std::shared_ptr m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); @@ -248,16 +265,16 @@ namespace client { public: - BOBCommandChannel (const std::string& address, int port); + BOBCommandChannel (const std::string& address, uint16_t port); ~BOBCommandChannel (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return GetIOService (); }; - void AddDestination (const std::string& name, BOBDestination * dest); + auto& GetService () { return GetIOService (); }; + void AddDestination (const std::string& name, std::shared_ptr dest); void DeleteDestination (const std::string& name); - BOBDestination * FindDestination (const std::string& name); + std::shared_ptr FindDestination (const std::string& name); private: @@ -267,7 +284,7 @@ namespace client private: boost::asio::ip::tcp::acceptor m_Acceptor; - std::map m_Destinations; + std::map > m_Destinations; std::map m_CommandHandlers; std::map m_HelpStrings; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 3f670f06..d26e33ab 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include "Identity.h" #include "util.h" #include "ClientContext.h" +#include "HTTPProxy.h" #include "SOCKS.h" #include "MatchedDestination.h" @@ -63,18 +64,19 @@ namespace client if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); - uint16_t samPort; i2p::config::GetOption("sam.port", samPort); + uint16_t samPortTCP; i2p::config::GetOption("sam.port", samPortTCP); + uint16_t samPortUDP; i2p::config::GetOption("sam.portudp", samPortUDP); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":", samPort); + LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP, "]"); try { - m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); + m_SamBridge = new SAMBridge (samAddr, samPortTCP, samPortUDP, singleThread); m_SamBridge->Start (); } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); - ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); + LogPrint(eLogCritical, "Clients: Exception in SAM bridge: ", e.what()); + ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP,"]: ", e.what ()); } } @@ -91,7 +93,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in BOB bridge: ", e.what()); ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); } } @@ -111,7 +113,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in I2CP: ", e.what()); ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); } } @@ -185,22 +187,30 @@ namespace client LogPrint(eLogInfo, "Clients: Stopping AddressBook"); m_AddressBook.Stop (); + LogPrint(eLogInfo, "Clients: Stopping UDP Tunnels"); { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); } + LogPrint(eLogInfo, "Clients: Stopping UDP Tunnels timers"); if (m_CleanupUDPTimer) { m_CleanupUDPTimer->cancel (); m_CleanupUDPTimer = nullptr; } - for (auto& it: m_Destinations) - it.second->Stop (); - m_Destinations.clear (); + { + LogPrint(eLogInfo, "Clients: Stopping Destinations"); + std::lock_guard lock(m_DestinationsMutex); + for (auto& it: m_Destinations) + it.second->Stop (); + LogPrint(eLogInfo, "Clients: Stopping Destinations - Clear"); + m_Destinations.clear (); + } + LogPrint(eLogInfo, "Clients: Stopping SharedLocalDestination"); m_SharedLocalDestination->Release (); m_SharedLocalDestination = nullptr; } @@ -255,13 +265,17 @@ namespace client } } - bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { - static const std::string transient("transient"); +#if __cplusplus >= 202002L // C++20 + if (filename.starts_with ("transient")) +#else + std::string_view transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient +#endif { - keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } @@ -278,7 +292,7 @@ namespace client s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { - LogPrint (eLogError, "Clients: Failed to load keyfile ", filename); + LogPrint (eLogCritical, "Clients: Failed to load keyfile ", filename); success = false; } else @@ -287,8 +301,8 @@ namespace client } else { - LogPrint (eLogError, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); - keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + LogPrint (eLogInfo, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; @@ -328,18 +342,18 @@ namespace client i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); auto localDestination = std::make_shared (keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewLocalDestination ( - boost::asio::io_service& service, bool isPublic, + boost::asio::io_context& service, bool isPublic, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); auto localDestination = std::make_shared (service, keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; @@ -389,7 +403,7 @@ namespace client return localDestination; } - std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_service& service, + std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); @@ -406,13 +420,10 @@ namespace client void ClientContext::CreateNewSharedLocalDestination () { - std::map params - { - { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, - { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, - { I2CP_PARAM_LEASESET_TYPE, "3" }, - { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" } - }; + std::map params; + ReadI2CPOptionsFromConfig ("shareddest.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SharedDest"; + m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA m_SharedLocalDestination->Acquire (); @@ -461,9 +472,13 @@ namespace client options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); + options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED); + options[I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, DEFAULT_MAX_INBOUND_SPEED); + options[I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); + options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; @@ -507,6 +522,8 @@ namespace client options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ()) options[I2CP_PARAM_LEASESET_PRIV_KEY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_STREAMING_PROFILE, value)) + options[I2CP_PARAM_STREAMING_PROFILE] = value; } void ClientContext::ReadTunnels () @@ -530,7 +547,11 @@ namespace client { for (auto& it: files) { +#if __cplusplus >= 202002L // C++20 + if (!it.ends_with (".conf")) continue; +#else if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" +#endif LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } @@ -568,20 +589,27 @@ namespace client std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); + uint16_t port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params - bool matchTunnels = section.second.get(I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); - std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); - int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); + bool matchTunnels = section.second.get (I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); + std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); + uint16_t destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP std::map options; ReadI2CPOptions (section, false, options); + // Set I2CP name if not set + auto itopt = options.find (I2CP_PARAM_OUTBOUND_NICKNAME); + if (itopt == options.end ()) + options[I2CP_PARAM_OUTBOUND_NICKNAME] = name; + std::shared_ptr localDestination = nullptr; - if (keys.length () > 0) + if (keys == "shareddest") + localDestination = m_SharedLocalDestination; + else if (keys.length () > 0) { auto it = destinations.find (keys); if (it != destinations.end ()) @@ -608,7 +636,7 @@ namespace client if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // udp client // TODO: hostnames - boost::asio::ip::udp::endpoint end (boost::asio::ip::address::from_string(address), port); + boost::asio::ip::udp::endpoint end (boost::asio::ip::make_address(address), port); if (!localDestination) localDestination = m_SharedLocalDestination; @@ -651,7 +679,9 @@ namespace client // http proxy std::string outproxy = section.second.get("outproxy", ""); bool addresshelper = section.second.get("addresshelper", true); - auto tun = std::make_shared(name, address, port, outproxy, addresshelper, localDestination); + bool senduseragent = section.second.get("senduseragent", false); + auto tun = std::make_shared(name, address, port, + outproxy, addresshelper, senduseragent, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } @@ -711,52 +741,63 @@ namespace client { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); - int port = section.second.get (I2P_SERVER_TUNNEL_PORT); + uint16_t port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params - int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + uint16_t inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, port); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); if(accessList == "") - accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); - std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); + accessList = section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); + std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, false); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, ""); - bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); + bool isUniqueLocal = section.second.get (I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); + bool ssl = section.second.get (I2P_SERVER_TUNNEL_SSL, false); // I2CP std::map options; ReadI2CPOptions (section, true, options); + // Set I2CP name if not set + auto itopt = options.find (I2CP_PARAM_INBOUND_NICKNAME); + if (itopt == options.end ()) + options[I2CP_PARAM_INBOUND_NICKNAME] = name; + std::shared_ptr localDestination = nullptr; - auto it = destinations.find (keys); - if (it != destinations.end ()) - { - localDestination = it->second; - localDestination->SetPublic (true); - } + if (keys == "shareddest") + localDestination = m_SharedLocalDestination; else - { - i2p::data::PrivateKeys k; - if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) - continue; - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) + { + auto it = destinations.find (keys); + if (it != destinations.end ()) { - localDestination = CreateNewLocalDestination (k, true, &options); - destinations[keys] = localDestination; + localDestination = it->second; + localDestination->SetPublic (true); } else - localDestination->SetPublic (true); - } + { + i2p::data::PrivateKeys k; + if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) + continue; + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + { + localDestination = CreateNewLocalDestination (k, true, &options); + destinations[keys] = localDestination; + } + else + localDestination->SetPublic (true); + } + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: hostnames - boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::make_address(host), port); if (address.empty ()) { if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) @@ -764,8 +805,8 @@ namespace client else address = "127.0.0.1"; } - auto localAddress = boost::asio::ip::address::from_string(address); - auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); + auto localAddress = boost::asio::ip::make_address(address); + auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, inPort, gzip); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); @@ -799,11 +840,13 @@ namespace client if (!address.empty ()) serverTunnel->SetLocalAddress (address); - if(!isUniqueLocal) + if (!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } + if (ssl) + serverTunnel->SetSSL (true); if (accessList.length () > 0) { std::set idents; @@ -843,11 +886,11 @@ namespace client } else - LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); + LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { - LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); + LogPrint (eLogCritical, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); } } @@ -864,31 +907,39 @@ namespace client uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + bool httpSendUserAgent; i2p::config::GetOption("httpproxy.senduseragent", httpSendUserAgent); if (httpAddresshelper) - i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book + i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); - if (httpProxyKeys.length () > 0) + if (httpProxyKeys == "shareddest") + { + localDestination = m_SharedLocalDestination; + localDestination->Acquire (); + } + else if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("httpproxy.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "HTTPProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else - LogPrint(eLogError, "Clients: Failed to load HTTP Proxy key"); + LogPrint(eLogCritical, "Clients: Failed to load HTTP Proxy key"); } try { - m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); + m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, + httpOutProxyURL, httpAddresshelper, httpSendUserAgent, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what()); ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); } } @@ -910,7 +961,12 @@ namespace client uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - if (httpProxyKeys == socksProxyKeys && m_HttpProxy) + if (socksProxyKeys == "shareddest") + { + localDestination = m_SharedLocalDestination; + localDestination->Acquire (); + } + else if (httpProxyKeys == socksProxyKeys && m_HttpProxy) { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); @@ -922,11 +978,12 @@ namespace client { std::map params; ReadI2CPOptionsFromConfig ("socksproxy.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SOCKSProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else - LogPrint(eLogError, "Clients: Failed to load SOCKS Proxy key"); + LogPrint(eLogCritical, "Clients: Failed to load SOCKS Proxy key"); } try { @@ -936,7 +993,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index d512a55b..3f7eaf9a 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,9 +15,8 @@ #include #include "Destination.h" #include "I2PService.h" -#include "HTTPProxy.h" -#include "SOCKS.h" #include "I2PTunnel.h" +#include "UDPTunnel.h" #include "SAM.h" #include "BOB.h" #include "I2CP.h" @@ -61,7 +60,7 @@ namespace client const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; - + const char I2P_SERVER_TUNNEL_SSL[] = "ssl"; class ClientContext { @@ -80,20 +79,20 @@ namespace client i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // used by SAM only - std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, + std::shared_ptr CreateNewLocalDestination (boost::asio::io_context& service, bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); - std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, + std::shared_ptr CreateNewLocalDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; - bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); @@ -140,8 +139,7 @@ namespace client AddressBook m_AddressBook; - i2p::proxy::HTTPProxy * m_HttpProxy; - i2p::proxy::SOCKSProxy * m_SocksProxy; + I2PService * m_HttpProxy, * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint -> tunnel std::map, std::shared_ptr > m_ServerTunnels; // -> tunnel @@ -166,8 +164,8 @@ namespace client const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } - const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; } - const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; } + const I2PService * GetHttpProxy () const { return m_HttpProxy; } + const I2PService * GetSocksProxy () const { return m_SocksProxy; } }; extern ClientContext context; diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 73677836..4c2771b5 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-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include "Config.h" #include "HTTP.h" #include "I18N.h" +#include "Socks5.h" namespace i2p { namespace proxy { @@ -36,12 +38,14 @@ namespace proxy { "reg.i2p", "stats.i2p", "identiguy.i2p", + "notbob.i2p" }; static const std::map jumpservices = { { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + { "notbob.i2p", "http://nytzrhrjjfsutowojvxi7hphesskpqqr65wpistz6wa7cpajhp7a.b32.i2p/cgi-bin/jump.cgi?q=" } }; static const char *pageHead = @@ -56,7 +60,8 @@ namespace proxy { "\r\n" ; - bool str_rmatch(std::string & str, const char *suffix) { + static bool str_rmatch(std::string_view str, const char *suffix) + { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ @@ -73,28 +78,27 @@ namespace proxy { void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm); - void SanitizeHTTPRequest(i2p::http::HTTPReq & req); + static bool ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm); + static bool VerifyAddressHelper (std::string_view jump); + void SanitizeHTTPRequest(i2p::http::HTTPReq& req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ - void GenericProxyError(const std::string& title, const std::string& description); - void GenericProxyInfo(const std::string& title, const std::string& description); - void HostNotFound(std::string & host); - void SendProxyError(std::string & content); + void GenericProxyError(std::string_view title, std::string_view description); + void GenericProxyInfo(std::string_view title, std::string_view description); + void HostNotFound(std::string_view host); + void SendProxyError(std::string_view content); + void SendRedirect(const std::string& address); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); - void HTTPConnect(const std::string & host, uint16_t port); + void HTTPConnect(std::string_view host, uint16_t port); void HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); - void HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transfered); - void HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transfered); - typedef std::function ProxyResolvedHandler; - void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler); + void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler); void SocksProxySuccess(); void HandoverToUpstreamProxy(); @@ -105,12 +109,11 @@ namespace proxy { std::shared_ptr m_sock; std::shared_ptr m_proxysock; boost::asio::ip::tcp::resolver m_proxy_resolver; - std::string m_OutproxyUrl; - bool m_Addresshelper; + std::string m_OutproxyUrl, m_Response; + bool m_Addresshelper, m_SendUserAgent; i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; - uint8_t m_socks_buf[255+8]; // for socks request/response - ssize_t m_req_len; + int m_req_len; i2p::http::URL m_ClientRequestURL; i2p::http::HTTPReq m_ClientRequest; i2p::http::HTTPRes m_ClientResponse; @@ -123,7 +126,8 @@ namespace proxy { m_proxysock(std::make_shared(parent->GetService())), m_proxy_resolver(parent->GetService()), m_OutproxyUrl(parent->GetOutproxyURL()), - m_Addresshelper(parent->GetHelperSupport()) {} + m_Addresshelper(parent->GetHelperSupport()), + m_SendUserAgent (parent->GetSendUserAgent ()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; @@ -158,23 +162,24 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { + void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) + { std::stringstream ss; ss << "

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

\r\n"; ss << "

" << description << "

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

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

\r\n"; ss << "

" << description << "

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

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

\r\n" << "

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

\r\n" @@ -187,11 +192,10 @@ namespace proxy { ss << "
  • second << host << "\">" << js->first << "
  • \r\n"; } ss << "\r\n"; - std::string content = ss.str(); - SendProxyError(content); + SendProxyError(ss.str ()); } - void HTTPReqHandler::SendProxyError(std::string & content) + void HTTPReqHandler::SendProxyError(std::string_view content) { i2p::http::HTTPRes res; res.code = 500; @@ -202,12 +206,23 @@ namespace proxy { << "" << content << "\r\n" << "\r\n"; res.body = ss.str(); - std::string response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), + m_Response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) + void HTTPReqHandler::SendRedirect(const std::string& address) + { + i2p::http::HTTPRes res; + res.code = 302; + res.add_header("Location", address); + res.add_header("Connection", "close"); + m_Response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + } + + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm) { confirm = false; const char *param = "i2paddresshelper="; @@ -223,26 +238,97 @@ namespace proxy { std::string value = params["i2paddresshelper"]; len += value.length(); - b64 = i2p::http::UrlDecode(value); + jump = i2p::http::UrlDecode(value); + if (!VerifyAddressHelper (jump)) + { + LogPrint (eLogError, "HTTPProxy: Malformed jump link ", jump); + return false; + } + // 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; } - void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) + bool HTTPReqHandler::VerifyAddressHelper (std::string_view jump) + { + auto pos = jump.find(".b32.i2p"); + if (pos != std::string::npos) + { + auto b32 = jump.substr (0, pos); + for (auto& ch: b32) + if (!i2p::data::IsBase32(ch)) return false; + return true; + } + else + { + bool padding = false; + for (auto& ch: jump) + { + if (ch == '=') + padding = true; + else + { + if (padding) return false; // other chars after padding + if (!i2p::data::IsBase64(ch)) return false; + } + } + return true; + } + return false; + } + + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq& req) { /* drop common headers */ req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); + req.RemoveHeader("DNT"); // Useless DoNotTrack flag req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ - req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + if (!m_SendUserAgent) + req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + + /** + * i2pd PR #1816: + * 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: @@ -291,31 +377,66 @@ namespace proxy { std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { - if (!m_Addresshelper) + if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); - GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); + GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); return true; } - if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) + + if (i2p::client::context.GetAddressBook ().RecordExists (m_RequestURL.host, jump)) { + std::string full_url = m_RequestURL.to_string(); + SendRedirect(full_url); + return true; + } + 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:" ) << " " << 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 */ } } @@ -324,11 +445,11 @@ namespace proxy { bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { - std::string uri(m_ClientRequest.uri); + const std::string& uri = m_ClientRequest.uri; auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError(tr("Invalid request"), tr("invalid request uri")); + GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); return true; } else @@ -351,7 +472,7 @@ namespace proxy { if (dest_host != "") { /* absolute url, replace 'Host' header */ - std::string h = dest_host; + std::string h (dest_host); if (dest_port != 0 && dest_port != 80) h += ":" + std::to_string(dest_port); m_ClientRequest.UpdateHeader("Host", h); @@ -388,10 +509,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; @@ -431,9 +552,9 @@ namespace proxy { std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? m_ClientRequest.uri = m_ClientRequestURL.to_string(); - /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections */ - if(m_ClientRequest.method != "CONNECT") - m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0"); + /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 13, for non-HTTPS connections */ + if(m_ClientRequest.method != "CONNECT" && !m_SendUserAgent) + m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0"); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); @@ -462,79 +583,68 @@ namespace proxy { } else { - boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); - m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { - m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); - })); + m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, + std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) + { + m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); + })); } } else if (m_ProxyURL.schema == "socks") { /* handle upstream socks proxy */ if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified - boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); - m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { - m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); - })); + m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, + std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) + { + m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); + })); } 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) + void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler) { - if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); - else handler(*it); + if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); + else handler(*endpoints.begin ()); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { - if(!ec) { - if(m_RequestURL.host.size() > 255) { - GenericProxyError(tr("hostname too long"), m_RequestURL.host); + if(!ec) + { + if(m_RequestURL.host.size() > 255) + { + GenericProxyError(tr("Hostname is too long"), m_RequestURL.host); return; } uint16_t port = m_RequestURL.port; if(!port) port = 80; LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream"); - std::string host = m_RequestURL.host; - std::size_t reqsize = 0; - m_socks_buf[0] = '\x04'; - m_socks_buf[1] = 1; - htobe16buf(m_socks_buf+2, port); - m_socks_buf[4] = 0; - m_socks_buf[5] = 0; - m_socks_buf[6] = 0; - m_socks_buf[7] = 1; - // user id - m_socks_buf[8] = 'i'; - m_socks_buf[9] = '2'; - m_socks_buf[10] = 'p'; - m_socks_buf[11] = 'd'; - m_socks_buf[12] = 0; - reqsize += 13; - memcpy(m_socks_buf+ reqsize, host.c_str(), host.size()); - 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()); - } - - 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()); - else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); + auto s = shared_from_this (); + i2p::transport::Socks5Handshake (*m_proxysock, std::make_pair(host, port), + [s](const boost::system::error_code& ec) + { + if (!ec) + s->SocksProxySuccess(); + else + s->GenericProxyError(tr("SOCKS proxy error"), ec.message ()); + }); + + } + else + GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandoverToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy"); - auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); + auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); @@ -542,11 +652,10 @@ namespace proxy { Terminate(); } - void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port) + void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); - std::string hostname(host); - if(str_rmatch(hostname, ".i2p")) + if(str_rmatch(host, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else @@ -569,7 +678,7 @@ namespace proxy { } else { - GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); + GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); } } @@ -580,7 +689,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 { @@ -588,36 +697,18 @@ 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(); }); } } - void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred) - { - if(!ec) - { - if(m_socks_buf[1] == 90) { - // success - SocksProxySuccess(); - } else { - std::stringstream ss; - ss << "error code: "; - ss << (int) m_socks_buf[1]; - std::string msg = ss.str(); - GenericProxyError(tr("socks proxy error"), msg); - } - } - 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 */ @@ -662,9 +753,10 @@ namespace proxy { Done (shared_from_this()); } - HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, + const std::string & outproxy, bool addresshelper, bool senduseragent, std::shared_ptr localDestination): TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), - m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper) + m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent) { } diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 69ed4cef..507a87e2 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,13 +15,15 @@ namespace proxy { { public: - HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination); - HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : - HTTPProxy(name, address, port, "", true, localDestination) {} ; + HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, + bool addresshelper, bool senduseragent, std::shared_ptr localDestination); + HTTPProxy(const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination = nullptr) : + HTTPProxy(name, address, port, "", true, false, localDestination) {} ; ~HTTPProxy() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } - bool GetHelperSupport() { return m_Addresshelper; } + bool GetHelperSupport() const { return m_Addresshelper; } + bool GetSendUserAgent () const { return m_SendUserAgent; } protected: @@ -33,7 +35,7 @@ namespace proxy { std::string m_Name; std::string m_OutproxyUrl; - bool m_Addresshelper; + bool m_Addresshelper, m_SendUserAgent; }; } // http } // i2p diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index cc0837b7..11278e7a 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include "ClientContext.h" #include "Transports.h" #include "Signature.h" +#include "Config.h" #include "I2CP.h" namespace i2p @@ -23,19 +24,22 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, const std::map& params): + I2CPDestination::I2CPDestination (boost::asio::io_context& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, bool isSameThread, + const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), - m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) + m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), + m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service) { } void I2CPDestination::Stop () { + m_LeaseSetCreationTimer.cancel (); + m_ReadinessCheckTimer.cancel (); LeaseSetDestination::Stop (); m_Owner = nullptr; - m_LeaseSetCreationTimer.cancel (); } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) @@ -75,7 +79,13 @@ namespace client return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; } - + i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const + { + if (m_ECIESx25519Decryptor) + return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; + return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; + } + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); @@ -86,7 +96,7 @@ namespace client void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { - GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); + boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels)); } void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) @@ -96,19 +106,33 @@ namespace client LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); return; } + m_ReadinessCheckTimer.cancel (); + auto pool = GetTunnelPool (); + if (!pool || pool->GetOutboundTunnels ().empty ()) + { + // try again later + m_ReadinessCheckTimer.expires_from_now (boost::posix_time::seconds(I2CP_DESTINATION_READINESS_CHECK_INTERVAL)); + m_ReadinessCheckTimer.async_wait( + [s=GetSharedFromThis (), tunnels=std::move(tunnels)](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + s->PostCreateNewLeaseSet (tunnels); + }); + return; + } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); - leases[-1] = tunnels.size (); - if (m_Owner) + int numLeases = leases[-1]; + if (m_Owner && numLeases) { uint16_t sessionID = m_Owner->GetSessionID (); if (sessionID != 0xFFFF) { m_IsCreatingLeaseSet = true; htobe16buf (leases - 3, sessionID); - size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); + size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*numLeases; m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); auto s = GetSharedFromThis (); @@ -122,6 +146,8 @@ namespace client }); } } + else + LogPrint (eLogError, "I2CP: Can't request LeaseSet"); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -152,20 +178,32 @@ namespace client memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); - auto s = GetSharedFromThis (); auto remote = FindLeaseSet (ident); if (remote) { - GetService ().post ( - [s, msg, remote, nonce]() - { - bool sent = s->SendMsg (msg, remote); - if (s->m_Owner) - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); - }); + if (m_IsSameThread) + { + // send right a way + bool sent = SendMsg (msg, remote); + if (m_Owner) + m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + } + else + { + // send in destination's thread + auto s = GetSharedFromThis (); + boost::asio::post (GetService (), + [s, msg, remote, nonce]() + { + bool sent = s->SendMsg (msg, remote); + if (s->m_Owner) + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + }); + } } else { + auto s = GetSharedFromThis (); RequestDestination (ident, [s, msg, nonce](std::shared_ptr ls) { @@ -189,6 +227,7 @@ namespace client LogPrint (eLogError, "I2CP: Failed to create remote session"); return false; } + auto garlic = remoteSession->WrapSingleMessage (msg); // shared routing path mitgh be dropped here auto path = remoteSession->GetSharedRoutingPath (); std::shared_ptr outboundTunnel; std::shared_ptr remoteLease; @@ -202,35 +241,43 @@ namespace client else remoteSession->SetSharedRoutingPath (nullptr); } - else + if (!outboundTunnel || !remoteLease) { auto leases = remote->GetNonExpiredLeases (false); // without threshold if (leases.empty ()) leases = remote->GetNonExpiredLeases (true); // with threshold if (!leases.empty ()) { - remoteLease = leases[rand () % leases.size ()]; + auto pool = GetTunnelPool (); + remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()]; auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway); outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr, leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); } if (remoteLease && outboundTunnel) remoteSession->SetSharedRoutingPath (std::make_shared ( - i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT + i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0})); // 10 secs RTT else remoteSession->SetSharedRoutingPath (nullptr); } + m_Owner->AddRoutingSession (remote->GetIdentity ()->GetStandardIdentity ().signingKey + 96, remoteSession); // last 32 bytes + return SendMsg (garlic, outboundTunnel, remoteLease); + } + + bool I2CPDestination::SendMsg (std::shared_ptr garlic, + std::shared_ptr outboundTunnel, std::shared_ptr remoteLease) + { if (remoteLease && outboundTunnel) { - std::vector msgs; - auto garlic = remoteSession->WrapSingleMessage (msg); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeTunnel, - remoteLease->tunnelGateway, remoteLease->tunnelID, - garlic + outboundTunnel->SendTunnelDataMsgs ( + { + i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeTunnel, + remoteLease->tunnelGateway, remoteLease->tunnelID, + garlic + } }); - outboundTunnel->SendTunnelDataMsg (msgs); return true; } else @@ -240,13 +287,55 @@ namespace client else LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels"); return false; + } + } + + bool I2CPDestination::SendMsg (const uint8_t * payload, size_t len, + std::shared_ptr remoteSession, uint32_t nonce) + { + if (!remoteSession) return false; + auto path = remoteSession->GetSharedRoutingPath (); + if (!path) return false; + // get tunnels + std::shared_ptr outboundTunnel; + std::shared_ptr remoteLease; + if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags + { + outboundTunnel = path->outboundTunnel; + remoteLease = path->remoteLease; } + else + { + remoteSession->SetSharedRoutingPath (nullptr); + return false; + } + // create Data message + auto msg = m_I2NPMsgsPool.AcquireSharedMt (); + uint8_t * buf = msg->GetPayload (); + htobe32buf (buf, len); + memcpy (buf + 4, payload, len); + msg->len += len + 4; + msg->FillI2NPMessageHeader (eI2NPData); + // wrap in gralic + auto garlic = remoteSession->WrapSingleMessage (msg); + // send + bool sent = SendMsg (garlic, outboundTunnel, remoteLease); + m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + if (!sent) + remoteSession->SetSharedRoutingPath (nullptr); + return sent; } + void I2CPDestination::CleanupDestination () + { + m_I2NPMsgsPool.CleanUpMt (); + if (m_Owner) m_Owner->CleanupRoutingSessions (); + } + RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): RunnableService ("I2CP"), - I2CPDestination (GetIOService (), owner, identity, isPublic, params) + I2CPDestination (GetIOService (), owner, identity, isPublic, false, params) { } @@ -275,8 +364,8 @@ namespace client } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): - m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), - m_MessageID (0), m_IsSendAccepted (true), m_IsSending (false) + m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), m_MessageID (0), + m_IsSendAccepted (true), m_IsSending (false) { } @@ -287,6 +376,11 @@ namespace client void I2CPSession::Start () { + if (m_Socket) + { + m_Socket->set_option (boost::asio::socket_base::receive_buffer_size (I2CP_MAX_MESSAGE_LENGTH)); + m_Socket->set_option (boost::asio::socket_base::send_buffer_size (I2CP_MAX_MESSAGE_LENGTH)); + } ReadProtocolByte (); } @@ -333,7 +427,27 @@ namespace client if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) - ReceivePayload (); + { + if (!m_Socket) return; + boost::system::error_code ec; + size_t moreBytes = m_Socket->available(ec); + if (!ec) + { + if (moreBytes >= m_PayloadLen) + { + // read and process payload immediately if available + moreBytes = boost::asio::read (*m_Socket, boost::asio::buffer(m_Payload, m_PayloadLen), boost::asio::transfer_all (), ec); + HandleReceivedPayload (ec, moreBytes); + } + else + ReceivePayload (); + } + else + { + LogPrint (eLogWarning, "I2CP: Socket error: ", ec.message ()); + Terminate (); + } + } else { LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); @@ -419,7 +533,7 @@ namespace client if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (sendBuf); + m_SendQueue.Add (std::move(sendBuf)); else { LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); @@ -463,30 +577,30 @@ namespace client m_IsSending = false; } - std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) + std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len) const { uint8_t l = buf[0]; if (l > len) l = len; - return std::string ((const char *)(buf + 1), l); + return { (const char *)(buf + 1), l }; } - size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) + size_t I2CPSession::PutString (uint8_t * buf, size_t len, std::string_view str) { auto l = str.length (); if (l + 1 >= len) l = len - 1; if (l > 255) l = 255; // 1 byte max buf[0] = l; - memcpy (buf + 1, str.c_str (), l); + memcpy (buf + 1, str.data (), l); return l + 1; } - void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) + void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const // TODO: move to Base.cpp { size_t offset = 0; while (offset < len) { - std::string param = ExtractString (buf + offset, len - offset); + auto param = ExtractString (buf + offset, len - offset); offset += param.length () + 1; if (buf[offset] != '=') { @@ -495,7 +609,7 @@ namespace client } offset++; - std::string value = ExtractString (buf + offset, len - offset); + auto value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { @@ -503,7 +617,7 @@ namespace client break; } offset++; - mapping.insert (std::make_pair (param, value)); + mapping.emplace (param, value); } } @@ -558,13 +672,13 @@ namespace client if (!m_Destination) { m_Destination = m_Owner.IsSingleThread () ? - std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, params): + std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, true, params): std::make_shared(shared_from_this (), identity, true, params); if (m_Owner.InsertSession (shared_from_this ())) { - SendSessionStatusMessage (eI2CPSessionStatusCreated); // created LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " created"); m_Destination->Start (); + SendSessionStatusMessage (eI2CPSessionStatusCreated); // created } else { @@ -584,7 +698,7 @@ namespace client SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid } } - + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy @@ -669,6 +783,26 @@ namespace client SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15); } + void I2CPSession::AddRoutingSession (const i2p::data::IdentHash& signingKey, std::shared_ptr remoteSession) + { + if (!remoteSession) return; + remoteSession->SetAckRequestInterval (I2CP_SESSION_ACK_REQUEST_INTERVAL); + std::lock_guard l(m_RoutingSessionsMutex); + m_RoutingSessions[signingKey] = remoteSession; + } + + void I2CPSession::CleanupRoutingSessions () + { + std::lock_guard l(m_RoutingSessionsMutex); + for (auto it = m_RoutingSessions.begin (); it != m_RoutingSessions.end ();) + { + if (it->second->IsTerminated ()) + it = m_RoutingSessions.erase (it); + else + it++; + } + } + void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); @@ -739,19 +873,44 @@ namespace client size_t offset = 2; if (m_Destination) { - i2p::data::IdentityEx identity; - size_t identsize = identity.FromBuffer (buf + offset, len - offset); - if (identsize) + const uint8_t * ident = buf + offset; + size_t identSize = i2p::data::GetIdentityBufferLen (ident, len - offset); + if (identSize) { - offset += identsize; + offset += identSize; uint32_t payloadLen = bufbe32toh (buf + offset); if (payloadLen + offset <= len) { offset += 4; uint32_t nonce = bufbe32toh (buf + offset + payloadLen); - if (m_IsSendAccepted) - SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted - m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); + if (m_Destination->IsReady ()) + { + if (m_IsSendAccepted) + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + std::shared_ptr remoteSession; + { + std::lock_guard l(m_RoutingSessionsMutex); + auto it = m_RoutingSessions.find (ident + i2p::data::DEFAULT_IDENTITY_SIZE - 35); // 32 bytes signing key + if (it != m_RoutingSessions.end ()) + { + if (!it->second->IsTerminated ()) + remoteSession = it->second; + else + m_RoutingSessions.erase (it); + } + } + if (!remoteSession || !m_Destination->SendMsg (buf + offset, payloadLen, remoteSession, nonce)) + { + i2p::data::IdentHash identHash; + SHA256(ident, identSize, identHash); // calculate ident hash, because we don't need full identity + m_Destination->SendMsgTo (buf + offset, payloadLen, identHash, nonce); + } + } + else + { + LogPrint(eLogInfo, "I2CP: Destination is not ready"); + SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLocalTunnels); + } } else LogPrint(eLogError, "I2CP: Cannot send message, too big"); @@ -891,8 +1050,12 @@ namespace client { uint8_t limits[64]; memset (limits, 0, 64); - htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound - htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound + uint32_t limit; i2p::config::GetOption("i2cp.inboundlimit", limit); + if (!limit) limit = i2p::context.GetBandwidthLimit (); + htobe32buf (limits, limit); // inbound + i2p::config::GetOption("i2cp.outboundlimit", limit); + if (!limit) limit = i2p::context.GetBandwidthLimit (); + htobe32buf (limits + 4, limit); // outbound SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); } @@ -916,7 +1079,7 @@ namespace client if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (sendBuf); + m_SendQueue.Add (std::move(sendBuf)); else { LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); @@ -936,10 +1099,10 @@ namespace client } } - I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): + I2CPServer::I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread): RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), m_Acceptor (GetIOService (), - boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) + boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(interface), port)) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -970,12 +1133,12 @@ namespace client void I2CPServer::Stop () { m_Acceptor.cancel (); - { - auto sessions = m_Sessions; - for (auto& it: sessions) - it.second->Stop (); - } - m_Sessions.clear (); + + decltype(m_Sessions) sessions; + m_Sessions.swap (sessions); + for (auto& it: sessions) + it.second->Stop (); + StopIOService (); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index e38da0ac..37c14dbb 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,13 +11,17 @@ #include #include +#include #include +#include #include #include +#include #include #include "util.h" #include "Destination.h" #include "Streaming.h" +#include "CryptoKey.h" namespace i2p { @@ -28,6 +32,8 @@ namespace client const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds + const int I2CP_DESTINATION_READINESS_CHECK_INTERVAL = 5; // in seconds + const int I2CP_SESSION_ACK_REQUEST_INTERVAL = 12100; // in milliseconds const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -58,6 +64,7 @@ namespace client eI2CPMessageStatusAccepted = 1, eI2CPMessageStatusGuaranteedSuccess = 4, eI2CPMessageStatusGuaranteedFailure = 5, + eI2CPMessageStatusNoLocalTunnels = 16, eI2CPMessageStatusNoLeaseSet = 21 }; @@ -78,11 +85,12 @@ namespace client { public: - I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, const std::map& params); + I2CPDestination (boost::asio::io_context& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, bool isSameThread, + const std::map& params); ~I2CPDestination () {}; - void Stop (); + void Stop () override; void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; @@ -90,25 +98,36 @@ namespace client void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession void LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession - + bool SendMsg (const uint8_t * payload, size_t len, std::shared_ptr remoteSession, uint32_t nonce); + // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; - bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; - const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only - std::shared_ptr GetIdentity () const { return m_Identity; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; + const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; // for 4 only + std::shared_ptr GetIdentity () const override { return m_Identity; }; protected: + // GarlicDestination + i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override + { + return m_ECIESx25519Decryptor ? i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD : 0; + } + // LeaseSetDestination + void CleanupDestination () override; + i2p::data::CryptoKeyType GetPreferredCryptoType () const override; // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (const std::vector >& tunnels); - + void HandleDataMessage (const uint8_t * buf, size_t len) override; + void CreateNewLeaseSet (const std::vector >& tunnels) override; + private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); - + bool SendMsg (std::shared_ptr garlic, + std::shared_ptr outboundTunnel, std::shared_ptr remoteLease); + void PostCreateNewLeaseSet (std::vector > tunnels); private: @@ -120,8 +139,8 @@ namespace client std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; - bool m_IsCreatingLeaseSet; - boost::asio::deadline_timer m_LeaseSetCreationTimer; + bool m_IsCreatingLeaseSet, m_IsSameThread; + boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer; i2p::util::MemoryPoolMt > m_I2NPMsgsPool; }; @@ -155,6 +174,8 @@ namespace client void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); void SendMessagePayloadMessage (const uint8_t * payload, size_t len); void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status); + void AddRoutingSession (const i2p::data::IdentHash& signingKey, std::shared_ptr remoteSession); + void CleanupRoutingSessions (); // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); @@ -181,12 +202,12 @@ namespace client void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - std::string ExtractString (const uint8_t * buf, size_t len); - size_t PutString (uint8_t * buf, size_t len, const std::string& str); - void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); + std::string_view ExtractString (const uint8_t * buf, size_t len) const; + size_t PutString (uint8_t * buf, size_t len, std::string_view str); + void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const; void SendSessionStatusMessage (I2CPSessionStatus status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); - + private: I2CPServer& m_Owner; @@ -195,6 +216,8 @@ namespace client size_t m_PayloadLen; std::shared_ptr m_Destination; + std::mutex m_RoutingSessionsMutex; + std::unordered_map > m_RoutingSessions; // signing key->session uint16_t m_SessionID; uint32_t m_MessageID; bool m_IsSendAccepted; @@ -210,12 +233,12 @@ namespace client { public: - I2CPServer (const std::string& interface, int port, bool isSingleThread); + I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return GetIOService (); }; + auto& GetService () { return GetIOService (); }; bool IsSingleThread () const { return m_IsSingleThread; }; bool InsertSession (std::shared_ptr session); @@ -224,8 +247,6 @@ namespace client private: - void Run (); - void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 2a6fe44e..4ec2648a 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -107,7 +107,7 @@ namespace client m_ReadyTimerTriggered = false; } - void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { + void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port) { assert(streamRequestComplete); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); if (address) @@ -119,7 +119,7 @@ namespace client } } - void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, int port) + void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, uint16_t port) { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { @@ -147,196 +147,5 @@ namespace client m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } } - - TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) - { - boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE); - upstream->set_option(option); - downstream->set_option(option); - } - - TCPIPPipe::~TCPIPPipe() - { - Terminate(); - } - - void TCPIPPipe::Start() - { - AsyncReceiveUpstream(); - AsyncReceiveDownstream(); - } - - void TCPIPPipe::Terminate() - { - if(Kill()) return; - if (m_up) - { - if (m_up->is_open()) - m_up->close(); - m_up = nullptr; - } - if (m_down) - { - if (m_down->is_open()) - m_down->close(); - m_down = nullptr; - } - Done(shared_from_this()); - } - - void TCPIPPipe::AsyncReceiveUpstream() - { - if (m_up) - { - m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE), - std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - } - else - LogPrint(eLogError, "TCPIPPipe: Upstream receive: No socket"); - } - - void TCPIPPipe::AsyncReceiveDownstream() - { - if (m_down) { - m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE), - std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - } - else - LogPrint(eLogError, "TCPIPPipe: Downstream receive: No socket"); - } - - void TCPIPPipe::UpstreamWrite(size_t len) - { - if (m_up) - { - LogPrint(eLogDebug, "TCPIPPipe: Upstream: ", (int) len, " bytes written"); - boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len), - boost::asio::transfer_all(), - std::bind(&TCPIPPipe::HandleUpstreamWrite, - shared_from_this(), - std::placeholders::_1)); - } - else - LogPrint(eLogError, "TCPIPPipe: Upstream write: no socket"); - } - - void TCPIPPipe::DownstreamWrite(size_t len) - { - if (m_down) - { - LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) len, " bytes written"); - boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len), - boost::asio::transfer_all(), - std::bind(&TCPIPPipe::HandleDownstreamWrite, - shared_from_this(), - std::placeholders::_1)); - } - else - LogPrint(eLogError, "TCPIPPipe: Downstream write: No socket"); - } - - - void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) - { - LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) bytes_transfered, " bytes received"); - if (ecode) - { - LogPrint(eLogError, "TCPIPPipe: Downstream read error:" , ecode.message()); - if (ecode != boost::asio::error::operation_aborted) - Terminate(); - } else { - if (bytes_transfered > 0 ) - memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered); - UpstreamWrite(bytes_transfered); - } - } - - void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { - if (ecode) - { - LogPrint(eLogError, "TCPIPPipe: Downstream write error:" , ecode.message()); - if (ecode != boost::asio::error::operation_aborted) - Terminate(); - } - else - AsyncReceiveUpstream(); - } - - void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { - if (ecode) - { - LogPrint(eLogError, "TCPIPPipe: Upstream write error:" , ecode.message()); - if (ecode != boost::asio::error::operation_aborted) - Terminate(); - } - else - AsyncReceiveDownstream(); - } - - void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) - { - LogPrint(eLogDebug, "TCPIPPipe: Upstream ", (int)bytes_transfered, " bytes received"); - if (ecode) - { - LogPrint(eLogError, "TCPIPPipe: Upstream read error:" , ecode.message()); - if (ecode != boost::asio::error::operation_aborted) - Terminate(); - } else { - if (bytes_transfered > 0 ) - memcpy(m_downstream_buf, m_upstream_to_down_buf, bytes_transfered); - DownstreamWrite(bytes_transfered); - } - } - - void TCPIPAcceptor::Start () - { - m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint)); - // update the local end point in case port has been set zero and got updated now - m_LocalEndpoint = m_Acceptor->local_endpoint(); - m_Acceptor->listen (); - Accept (); - } - - void TCPIPAcceptor::Stop () - { - if (m_Acceptor) - { - m_Acceptor->close(); - m_Acceptor.reset (nullptr); - } - m_Timer.cancel (); - ClearHandlers(); - } - - void TCPIPAcceptor::Accept () - { - auto newSocket = std::make_shared (GetService ()); - m_Acceptor->async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this, - std::placeholders::_1, newSocket)); - } - - void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr socket) - { - if (!ecode) - { - LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted"); - auto handler = CreateHandler(socket); - if (handler) - { - AddHandler(handler); - handler->Handle(); - } - else - socket->close(); - Accept(); - } - else - { - if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ()); - } - } } } diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h index e14f85c1..d19c28e2 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -59,9 +59,9 @@ namespace client if (dest) dest->Acquire (); m_LocalDestination = dest; } - void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); - void CreateStream(StreamRequestComplete complete, std::shared_ptr address, int port); - inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } + void CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port = 0); + void CreateStream(StreamRequestComplete complete, std::shared_ptr address, uint16_t port); + auto& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; virtual void Stop () = 0; @@ -99,6 +99,7 @@ namespace client virtual ~I2PServiceHandler() { } //If you override this make sure you call it from the children virtual void Handle() {}; //Start handling the socket + virtual void Start () {}; void Terminate () { Kill (); }; @@ -119,72 +120,171 @@ namespace client std::atomic m_Dead; //To avoid cleaning up multiple times }; - const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8; + const size_t SOCKETS_PIPE_BUFFER_SIZE = 8192 * 8; - // bidirectional pipe for 2 tcp/ip sockets - class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this + // bidirectional pipe for 2 stream sockets + template + class SocketsPipe: public I2PServiceHandler, + public std::enable_shared_from_this > { public: - TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); - ~TCPIPPipe(); - void Start(); - - protected: - - void Terminate(); - void AsyncReceiveUpstream(); - void AsyncReceiveDownstream(); - void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); - void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); - void HandleUpstreamWrite(const boost::system::error_code & ecode); - void HandleDownstreamWrite(const boost::system::error_code & ecode); - void UpstreamWrite(size_t len); - void DownstreamWrite(size_t len); + SocketsPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream): + I2PServiceHandler(owner), m_up(upstream), m_down(downstream) + { + boost::asio::socket_base::receive_buffer_size option(SOCKETS_PIPE_BUFFER_SIZE); + upstream->set_option(option); + downstream->set_option(option); + } + ~SocketsPipe() { Terminate(); } + + void Start() override + { + Transfer (m_up, m_down, m_upstream_to_down_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream + Transfer (m_down, m_up, m_downstream_to_up_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream + } private: - uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE]; - uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE]; - std::shared_ptr m_up, m_down; + void Terminate() + { + if(Kill()) return; + if (m_up) + { + if (m_up->is_open()) + m_up->close(); + m_up = nullptr; + } + if (m_down) + { + if (m_down->is_open()) + m_down->close(); + m_down = nullptr; + } + Done(SocketsPipe::shared_from_this()); + } + + template + void Transfer (std::shared_ptr from, std::shared_ptr to, uint8_t * buf, size_t len) + { + if (!from || !to || !buf) return; + auto s = SocketsPipe::shared_from_this (); + from->async_read_some(boost::asio::buffer(buf, len), + [from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred) + { + if (ecode == boost::asio::error::operation_aborted) return; + if (!ecode) + { + boost::asio::async_write(*to, boost::asio::buffer(buf, transferred), boost::asio::transfer_all(), + [from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred) + { + (void) transferred; + if (ecode == boost::asio::error::operation_aborted) return; + if (!ecode) + s->Transfer (from, to, buf, len); + else + { + LogPrint(eLogWarning, "SocketsPipe: Write error:" , ecode.message()); + s->Terminate(); + } + }); + } + else + { + LogPrint(eLogWarning, "SocketsPipe: Read error:" , ecode.message()); + s->Terminate(); + } + }); + } + + private: + + uint8_t m_upstream_to_down_buf[SOCKETS_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[SOCKETS_PIPE_BUFFER_SIZE]; + std::shared_ptr m_up; + std::shared_ptr m_down; }; - /* TODO: support IPv6 too */ - //This is a service that listens for connections on the IP network and interacts with I2P - class TCPIPAcceptor: public I2PService + template + std::shared_ptr CreateSocketsPipe (I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) + { + return std::make_shared >(owner, upstream, downstream); + } + + //This is a service that listens for connections on the IP network or local socket and interacts with I2P + template + class ServiceAcceptor: public I2PService { public: - TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : - I2PService(localDestination), - m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), - m_Timer (GetService ()) {} - TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) : - I2PService(kt), - m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), - m_Timer (GetService ()) {} - virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } - //If you override this make sure you call it from the children - void Start (); - //If you override this make sure you call it from the children - void Stop (); + ServiceAcceptor (const typename Protocol::endpoint& localEndpoint, std::shared_ptr localDestination = nullptr) : + I2PService(localDestination), m_LocalEndpoint (localEndpoint) {} + + virtual ~ServiceAcceptor () { Stop(); } + void Start () override + { + m_Acceptor.reset (new typename Protocol::acceptor (GetService (), m_LocalEndpoint)); + // update the local end point in case port has been set zero and got updated now + m_LocalEndpoint = m_Acceptor->local_endpoint(); + m_Acceptor->listen (); + Accept (); + } + void Stop () override + { + if (m_Acceptor) + { + m_Acceptor->close(); + m_Acceptor.reset (nullptr); + } + ClearHandlers(); + } + const typename Protocol::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; - const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; - - virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } + const char* GetName() override { return "Generic TCP/IP accepting daemon"; } protected: - virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; + virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; private: - void Accept(); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); - boost::asio::ip::tcp::endpoint m_LocalEndpoint; - std::unique_ptr m_Acceptor; - boost::asio::deadline_timer m_Timer; + void Accept() + { + auto newSocket = std::make_shared (GetService ()); + m_Acceptor->async_accept (*newSocket, + [newSocket, this](const boost::system::error_code& ecode) + { + if (ecode == boost::asio::error::operation_aborted) return; + if (!ecode) + { + LogPrint(eLogDebug, "ServiceAcceptor: ", GetName(), " accepted"); + auto handler = CreateHandler(newSocket); + if (handler) + { + AddHandler(handler); + handler->Handle(); + } + else + newSocket->close(); + Accept(); + } + else + LogPrint (eLogError, "ServiceAcceptor: ", GetName(), " closing socket on accept because: ", ecode.message ()); + }); + } + + private: + + typename Protocol::endpoint m_LocalEndpoint; + std::unique_ptr m_Acceptor; }; + + class TCPIPAcceptor: public ServiceAcceptor + { + public: + + TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr localDestination = nullptr) : + ServiceAcceptor (boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port), localDestination) {} + }; } } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index c2839c62..d6436c78 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,7 @@ namespace client { /** set standard socket options */ - static void I2PTunnelSetSocketOptions(std::shared_ptr socket) + static void I2PTunnelSetSocketOptions (std::shared_ptr socket) { if (socket && socket->is_open()) { @@ -31,9 +31,8 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, int port): - I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), - m_IsQuiet (true) + std::shared_ptr leaseSet, uint16_t port): + I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -41,15 +40,17 @@ namespace client I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), - m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) + m_RemoteEndpoint (socket->remote_endpoint ()) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): - I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), - m_RemoteEndpoint (target), m_IsQuiet (quiet) + const boost::asio::ip::tcp::endpoint& target,std::shared_ptr sslCtx): + I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target) { + m_Socket = std::make_shared (owner->GetService ()); + if (sslCtx) + m_SSL = std::make_shared > (*m_Socket, *sslCtx); } I2PTunnelConnection::~I2PTunnelConnection () @@ -69,7 +70,7 @@ namespace client Receive (); } - static boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) + boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; @@ -80,24 +81,26 @@ namespace client } #ifdef __linux__ - static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) + static void MapToLoopback(std::shared_ptr sock, const i2p::data::IdentHash & addr) { - // bind to 127.x.x.x address - // where x.x.x are first three bytes from ident - auto ourIP = GetLoopbackAddressFor(addr); - boost::system::error_code ec; - sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); - if (ec) - LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); - + if (sock) + { + // bind to 127.x.x.x address + // where x.x.x are first three bytes from ident + auto ourIP = GetLoopbackAddressFor(addr); + boost::system::error_code ec; + sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); + if (ec) + LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); + } } #endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { - I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { + I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) @@ -131,6 +134,7 @@ namespace client void I2PTunnelConnection::Terminate () { if (Kill()) return; + if (m_SSL) m_SSL = nullptr; if (m_Stream) { m_Stream->Close (); @@ -145,12 +149,17 @@ namespace client void I2PTunnelConnection::Receive () { - m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), - std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + if (m_SSL) + m_SSL->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + else + m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } - void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { @@ -239,8 +248,12 @@ namespace client void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { - boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), - std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); + if (m_SSL) + boost::asio::async_write (*m_SSL, boost::asio::buffer (buf, len), boost::asio::transfer_all (), + std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); + else + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), + std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) @@ -253,22 +266,34 @@ namespace client else { LogPrint (eLogDebug, "I2PTunnel: Connected"); - if (m_IsQuiet) - StreamReceive (); + if (m_SSL) + m_SSL->async_handshake (boost::asio::ssl::stream_base::client, + std::bind (&I2PTunnelConnection::HandleHandshake, shared_from_this (), std::placeholders::_1)); else - { - // send destination first like received from I2P - std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); - dest += "\n"; - if(sizeof(m_StreamBuffer) >= dest.size()) { - memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); - } - HandleStreamReceive (boost::system::error_code (), dest.size ()); - } - Receive (); + Established (); } } + void I2PTunnelConnection::HandleHandshake (const boost::system::error_code& ecode) + { + if (ecode) + { + LogPrint (eLogError, "I2PTunnel: Handshake error: ", ecode.message ()); + Terminate (); + } + else + { + LogPrint (eLogDebug, "I2PTunnel: SSL connected"); + Established (); + } + } + + void I2PTunnelConnection::Established () + { + StreamReceive (); + Receive (); + } + void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) @@ -308,7 +333,12 @@ namespace client } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -321,15 +351,24 @@ namespace client m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } + else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) + StreamReceive (); // read more header + else + { + LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); + Terminate (); + } } } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_Host (host), - m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) + const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, + std::shared_ptr sslCtx): + I2PTunnelConnection (owner, stream, target, sslCtx), m_Host (host), m_XI2P (XI2P), + m_HeaderSent (false), m_ResponseHeaderSent (false) { + if (sslCtx) + SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ()); } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) @@ -345,15 +384,16 @@ namespace client while (!endOfHeader) { std::getline(m_InHeader, line); - if (!m_InHeader.fail ()) + if (m_InHeader.fail ()) break; + if (!m_InHeader.eof ()) { if (line == "\r") endOfHeader = true; else { // strip up some headers - static const std::vector excluded // list of excluded headers + static const std::array excluded // list of excluded headers { - "Keep-Alive:", "X-I2P" + "Keep-Alive:", "X-I2P" }; bool matched = false; for (const auto& it: excluded) @@ -362,7 +402,7 @@ namespace client matched = true; break; } - if (matched) break; + if (matched) continue; // replace some headers if (!m_Host.empty () && boost::iequals (line.substr (0, 5), "Host:")) @@ -375,13 +415,18 @@ namespace client else m_OutHeader << "Connection: close\r\n"; connection = true; - } - else // forward as is + } + else // forward as is m_OutHeader << line << "\n"; } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -390,20 +435,22 @@ namespace client if (!connection) m_OutHeader << "Connection: close\r\n"; // add X-I2P fields - if (m_From) - { - m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; - m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; - m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; - } - - m_OutHeader << "\r\n"; // end of header + m_OutHeader << m_XI2P; + // end of header + m_OutHeader << "\r\n"; + m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); - m_From = nullptr; m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } + else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) + StreamReceive (); // read more header + else + { + LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); + Terminate (); + } } } @@ -421,12 +468,13 @@ namespace client while (!endOfHeader) { std::getline(m_InHeader, line); - if (!m_InHeader.fail ()) + if (m_InHeader.fail ()) break; + if (!m_InHeader.eof ()) { if (line == "\r") endOfHeader = true; else { - static const std::vector excluded // list of excluded headers + static const std::array excluded // list of excluded headers { "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" }; @@ -442,7 +490,12 @@ namespace client } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -460,9 +513,9 @@ namespace client } I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): - I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), + const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass, + std::shared_ptr sslCtx): + I2PTunnelConnection (owner, stream, target, sslCtx), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } @@ -473,7 +526,8 @@ namespace client if (m_NeedsWebIrc) { m_NeedsWebIrc = false; - m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; + m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) + << " " << GetSocket ()->local_endpoint ().address () << std::endl; } m_InPacket.clear (); @@ -509,7 +563,7 @@ namespace client { public: I2PClientTunnelHandler (I2PClientTunnel * parent, std::shared_ptr address, - int destinationPort, std::shared_ptr socket): + uint16_t destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_Address(address), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); @@ -517,7 +571,7 @@ namespace client private: void HandleStreamRequestComplete (std::shared_ptr stream); std::shared_ptr m_Address; - int m_DestinationPort; + uint16_t m_DestinationPort; std::shared_ptr m_Socket; }; @@ -558,7 +612,7 @@ namespace client } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): + const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { @@ -633,17 +687,19 @@ namespace client } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, uint16_t inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { - m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); + m_PortDestination = localDestination->GetStreamingDestination (inport); + if (!m_PortDestination) // default destination + m_PortDestination = localDestination->CreateStreamingDestination (inport, gzip); } void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string (m_Address, ec); + auto addr = boost::asio::ip::make_address (m_Address, ec); if (!ec) { m_Endpoint.address (addr); @@ -652,7 +708,7 @@ namespace client else { auto resolver = std::make_shared(GetService ()); - resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), + resolver->async_resolve (m_Address, "", std::bind (&I2PServerTunnel::HandleResolve, this, std::placeholders::_1, std::placeholders::_2, resolver)); } @@ -665,11 +721,11 @@ namespace client auto localDestination = GetLocalDestination (); if (localDestination) localDestination->StopAcceptingStreams (); - + ClearHandlers (); } - void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, std::shared_ptr resolver) { if (!ecode) @@ -678,10 +734,9 @@ namespace client boost::asio::ip::tcp::endpoint ep; if (m_LocalAddress) { - boost::asio::ip::tcp::resolver::iterator end; - while (it != end) + for (const auto& it: endpoints) { - ep = *it; + ep = it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) @@ -700,27 +755,26 @@ namespace client } } if (found) break; - it++; } } else { found = true; - ep = *it; // first available + ep = *endpoints.begin (); // first available } if (!found) { - LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); + LogPrint (eLogError, "I2PTunnel: Unable to resolve ", m_Address, " to compatible address"); return; } auto addr = ep.address (); - LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*it).host_name (), " has been resolved to ", addr); + LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*endpoints.begin ()).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); } else - LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address ", m_Address, ": ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) @@ -732,13 +786,24 @@ namespace client void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) { boost::system::error_code ec; - auto addr = boost::asio::ip::address::from_string(localAddress, ec); + auto addr = boost::asio::ip::make_address(localAddress, ec); if (!ec) m_LocalAddress.reset (new boost::asio::ip::address (addr)); else LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress); } + void I2PServerTunnel::SetSSL (bool ssl) + { + if (ssl) + { + m_SSLCtx = std::make_shared (boost::asio::ssl::context::sslv23); + m_SSLCtx->set_verify_mode(boost::asio::ssl::context::verify_none); + } + else + m_SSLCtx = nullptr; + } + void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -779,13 +844,13 @@ namespace client std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint ()); + return std::make_shared (this, stream, GetEndpoint (), m_SSLCtx); } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, - const std::string& host, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, + const std::string& host, uint16_t inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { @@ -793,13 +858,22 @@ namespace client std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, - std::make_shared (GetService ()), GetEndpoint (), m_Host); + if (m_XI2P.empty () || stream->GetRemoteIdentity () != m_From.lock ()) + { + auto from = stream->GetRemoteIdentity (); + m_From = from; + std::stringstream ss; + ss << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(from->GetIdentHash ()) << "\r\n"; + ss << X_I2P_DEST_HASH << ": " << from->GetIdentHash ().ToBase64 () << "\r\n"; + ss << X_I2P_DEST_B64 << ": " << from->ToBase64 () << "\r\n"; + m_XI2P = ss.str (); + } + return std::make_shared (this, stream, GetEndpoint (), m_Host, m_XI2P, GetSSLCtx ()); } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, - const std::string& webircpass, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, + const std::string& webircpass, uint16_t inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { @@ -807,371 +881,7 @@ namespace client std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); - } - - void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) - { - std::lock_guard lock(m_SessionsMutex); - m_LastSession = ObtainUDPSession(from, toPort, fromPort); - } - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - - void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) - { - if (m_LastSession) - { - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - } - - void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - auto itr = m_Sessions.begin(); - while(itr != m_Sessions.end()) { - if(now - (*itr)->LastActivity >= delta ) - itr = m_Sessions.erase(itr); - else - ++itr; - } - } - - void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - std::vector removePorts; - for (const auto & s : m_Sessions) { - if (now - s.second->second >= delta) - removePorts.push_back(s.first); - } - for(auto port : removePorts) { - m_Sessions.erase(port); - } - } - - UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) - { - auto ih = from.GetIdentHash(); - for (auto & s : m_Sessions ) - { - if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) - { - /** found existing session */ - LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); - return s; - } - } - boost::asio::ip::address addr; - /** create new udp session */ - if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) - { - auto ident = from.GetIdentHash(); - addr = GetLoopbackAddressFor(ident); - } - else - addr = m_LocalAddress; - boost::asio::ip::udp::endpoint ep(addr, 0); - m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); - auto & back = m_Sessions.back(); - return back; - } - - UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, - uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination->GetDatagramDestination()), - IPSocket(localDestination->GetService(), localEndpoint), - SendEndpoint(endpoint), - LastActivity(i2p::util::GetMillisecondsSinceEpoch()), - LocalPort(ourPort), - RemotePort(theirPort) - { - IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); - memcpy(Identity, to->data(), 32); - Receive(); - } - - void UDPSession::Receive() - { - LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), - FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); - } - - void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) - { - if(!ecode) - { - LogPrint(eLogDebug, "UDPSession: Forward ", len, "B from ", FromEndpoint); - auto ts = i2p::util::GetMillisecondsSinceEpoch(); - auto session = m_Destination->GetSession (Identity); - if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); - else - m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = IPSocket.available(ec); - if (ec || !moreBytes) break; - len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); - m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); - numPackets++; - } - if (numPackets > 0) - LogPrint(eLogDebug, "UDPSession: Forward more ", numPackets, "packets B from ", FromEndpoint); - m_Destination->FlushSendQueue (session); - LastActivity = ts; - Receive(); - } - else - LogPrint(eLogError, "UDPSession: ", ecode.message()); - } - - I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr localDestination, - boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip) : - m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), - m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_Gzip (gzip) - { - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel () - { - Stop (); - } - - void I2PUDPServerTunnel::Start () - { - m_LocalDest->Start (); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - } - - void I2PUDPServerTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) dgram->ResetReceiver (); - } - - std::vector > I2PUDPServerTunnel::GetSessions () - { - std::vector > sessions; - std::lock_guard lock (m_SessionsMutex); - - for (UDPSessionPtr s: m_Sessions) - { - if (!s->m_Destination) continue; - auto info = s->m_Destination->GetInfoForRemote (s->Identity); - if (!info) continue; - - auto sinfo = std::make_shared (); - sinfo->Name = m_Name; - sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); - sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); - sinfo->CurrentIBGW = info->IBGW; - sinfo->CurrentOBEP = info->OBEP; - sessions.push_back (sinfo); - } - return sessions; - } - - I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort, bool gzip) : - m_Name (name), m_RemoteDest (remoteDest), m_LocalDest (localDestination), m_LocalEndpoint (localEndpoint), - m_RemoteIdent (nullptr), m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), - m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip) - { - } - - I2PUDPClientTunnel::~I2PUDPClientTunnel () - { - Stop (); - } - - void I2PUDPClientTunnel::Start () - { - // Reset flag in case of tunnel reload - if (m_cancel_resolve) m_cancel_resolve = false; - - m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); - m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); - m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); - dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - - m_LocalDest->Start (); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); - RecvFromLocal (); - } - - void I2PUDPClientTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) dgram->ResetReceiver (); - m_cancel_resolve = true; - - m_Sessions.clear(); - - if(m_LocalSocket && m_LocalSocket->is_open ()) - m_LocalSocket->close (); - - if(m_ResolveThread) - { - m_ResolveThread->join (); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - if (m_RemoteIdent) - { - delete m_RemoteIdent; - m_RemoteIdent = nullptr; - } - } - - void I2PUDPClientTunnel::RecvFromLocal () - { - m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), - m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); - } - - void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) - { - if (m_cancel_resolve) { - LogPrint (eLogDebug, "UDP Client: Ignoring incomming data: stopping"); - return; - } - if (ec) { - LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); - RecvFromLocal (); // Restart listener and continue work - return; - } - if (!m_RemoteIdent) { - LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); - RecvFromLocal (); - return; // drop, remote not resolved - } - auto remotePort = m_RecvEndpoint.port (); - if (!m_LastPort || m_LastPort != remotePort) - { - auto itr = m_Sessions.find (remotePort); - if (itr != m_Sessions.end ()) - m_LastSession = itr->second; - else - { - m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); - m_Sessions.emplace (remotePort, m_LastSession); - } - m_LastPort = remotePort; - } - // send off to remote i2p destination - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteIdent->ToBase32 (), ":", RemotePort); - auto session = m_LocalDest->GetDatagramDestination ()->GetSession (*m_RemoteIdent); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - else - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = m_LocalSocket->available (ec); - if (ec || !moreBytes) break; - transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); - remotePort = m_RecvEndpoint.port (); - // TODO: check remotePort - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - numPackets++; - } - if (numPackets) - LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32 ()); - m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); - - // mark convo as active - if (m_LastSession) - m_LastSession->second = ts; - RecvFromLocal (); - } - - std::vector > I2PUDPClientTunnel::GetSessions () - { - // TODO: implement - std::vector > infos; - return infos; - } - - void I2PUDPClientTunnel::TryResolving () - { - i2p::util::SetThreadName ("UDP Resolver"); - LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - - std::shared_ptr addr; - while (!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) - { - LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for (std::chrono::seconds (1)); - } - if (m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); - return; - } - if (!addr || !addr->IsIdentHash ()) - { - LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); - return; - } - m_RemoteIdent = new i2p::data::IdentHash; - *m_RemoteIdent = addr->identHash; - LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) - HandleRecvFromI2PRaw (fromPort, toPort, buf, len); - else - LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - auto itr = m_Sessions.find (toPort); - // found convo ? - if (itr != m_Sessions.end ()) - { - // found convo - if (len > 0) - { - LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32 () : ""); - m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); - // mark convo as active - itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); - } - } - else - LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); + return std::make_shared (this, stream, GetEndpoint (), m_WebircPass, GetSSLCtx ()); } } } diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index dfa23fb2..7d4c3400 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,9 +16,9 @@ #include #include #include +#include #include "Identity.h" #include "Destination.h" -#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" @@ -27,24 +27,26 @@ namespace i2p { namespace client { - const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; + const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 16384; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels - const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 - const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 - const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address + constexpr char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 + constexpr char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 + constexpr char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address + const int I2P_TUNNEL_HTTP_MAX_HEADER_SIZE = 8192; class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, int port = 0); // to I2P + std::shared_ptr leaseSet, uint16_t port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API - I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P + I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, + const boost::asio::ip::tcp::endpoint& target, + std::shared_ptr sslCtx = nullptr); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (bool isUniqueLocal = true); @@ -52,27 +54,34 @@ namespace client protected: + virtual void Established (); void Terminate (); void Receive (); - void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - virtual void Write (const uint8_t * buf, size_t len); // can be overloaded - void HandleWrite (const boost::system::error_code& ecode); - virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded - void StreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); + virtual void Write (const uint8_t * buf, size_t len); // can be overloaded + virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded + + std::shared_ptr GetSocket () const { return m_Socket; }; + std::shared_ptr GetStream () const { return m_Stream; }; + std::shared_ptr > GetSSL () const { return m_SSL; }; + uint8_t * GetStreamBuffer () { return m_StreamBuffer; }; + + private: + void HandleConnect (const boost::system::error_code& ecode); - - std::shared_ptr GetSocket () const { return m_Socket; }; - + void HandleHandshake (const boost::system::error_code& ecode); + void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleWrite (const boost::system::error_code& ecode); + private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; + std::shared_ptr > m_SSL; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; - bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection @@ -86,7 +95,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: @@ -99,20 +108,19 @@ namespace client public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host); + const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, + std::shared_ptr sslCtx = nullptr); protected: - void Write (const uint8_t * buf, size_t len); - void WriteToStream (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; + void WriteToStream (const uint8_t * buf, size_t len) override; private: - std::string m_Host; + std::string m_Host, m_XI2P; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ResponseHeaderSent; - std::shared_ptr m_From; }; class I2PTunnelConnectionIRC: public I2PTunnelConnection @@ -120,12 +128,12 @@ namespace client public: I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); + const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass, + std::shared_ptr sslCtx = nullptr); protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: @@ -146,7 +154,7 @@ namespace client public: I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); + const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort = 0); ~I2PClientTunnel () {} void Start (); @@ -166,173 +174,17 @@ namespace client std::string m_Name, m_Destination; std::shared_ptr m_Address; - int m_DestinationPort; + uint16_t m_DestinationPort; uint32_t m_KeepAliveInterval; std::unique_ptr m_KeepAliveTimer; }; - - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds - - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 64*1024; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; - - uint16_t LocalPort; - uint16_t RemotePort; - - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, - uint16_t ourPort, uint16_t theirPort); - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; - - - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - std::string Name; - /** ident hash of local destination */ - std::shared_ptr LocalIdent; - /** ident hash of remote destination */ - std::shared_ptr RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this converstation been idle in ms */ - uint64_t idle; - }; - - typedef std::shared_ptr UDPSessionPtr; - - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - - I2PUDPServerTunnel (const std::string & name, - std::shared_ptr localDestination, - boost::asio::ip::address localAddress, - boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); - ~I2PUDPServerTunnel (); - - /** expire stale udp conversations */ - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - - void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } - - private: - - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - - private: - - bool m_IsUniqueLocal; - const std::string m_Name; - boost::asio::ip::address m_LocalAddress; - boost::asio::ip::udp::endpoint m_RemoteEndpoint; - std::mutex m_SessionsMutex; - std::vector m_Sessions; - std::shared_ptr m_LocalDest; - UDPSessionPtr m_LastSession; - bool m_Gzip; - - public: - - bool isUpdated; // transient, used during reload only - }; - - class I2PUDPClientTunnel - { - public: - - I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort, bool gzip); - ~I2PUDPClientTunnel (); - - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - - bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } - - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - inline void SetLocalDestination (std::shared_ptr dest) - { - if (m_LocalDest) m_LocalDest->Release (); - if (dest) dest->Acquire (); - m_LocalDest = dest; - } - - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - - private: - - typedef std::pair UDPConvo; - void RecvFromLocal (); - void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving (); - - private: - - const std::string m_Name; - std::mutex m_SessionsMutex; - std::unordered_map > m_Sessions; // maps i2p port -> local udp convo - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - i2p::data::IdentHash * m_RemoteIdent; - std::thread * m_ResolveThread; - std::unique_ptr m_LocalSocket; - boost::asio::ip::udp::endpoint m_RecvEndpoint; - uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort, m_LastPort; - bool m_cancel_resolve; - bool m_Gzip; - std::shared_ptr m_LastSession; - - public: - - bool isUpdated; // transient, used during reload only - }; - class I2PServerTunnel: public I2PService { public: - I2PServerTunnel (const std::string& name, const std::string& address, int port, - std::shared_ptr localDestination, int inport = 0, bool gzip = true); + I2PServerTunnel (const std::string& name, const std::string& address, uint16_t port, + std::shared_ptr localDestination, uint16_t inport = 0, bool gzip = true); void Start (); void Stop (); @@ -342,10 +194,13 @@ namespace client void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } + void SetSSL (bool ssl); + std::shared_ptr GetSSLCtx () const { return m_SSLCtx; }; + void SetLocalAddress (const std::string& localAddress); const std::string& GetAddress() const { return m_Address; } - int GetPort () const { return m_Port; }; + uint16_t GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } @@ -353,7 +208,7 @@ namespace client private: - void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, std::shared_ptr resolver); void Accept (); @@ -364,21 +219,22 @@ namespace client bool m_IsUniqueLocal; std::string m_Name, m_Address; - int m_Port; + uint16_t m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; bool m_IsAccessList; std::unique_ptr m_LocalAddress; + std::shared_ptr m_SSLCtx; }; class I2PServerTunnelHTTP: public I2PServerTunnel { public: - I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, + I2PServerTunnelHTTP (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, const std::string& host, - int inport = 0, bool gzip = true); + uint16_t inport = 0, bool gzip = true); private: @@ -386,16 +242,17 @@ namespace client private: - std::string m_Host; + std::string m_Host, m_XI2P; + std::weak_ptr m_From; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: - I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, + I2PServerTunnelIRC (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, const std::string& webircpass, - int inport = 0, bool gzip = true); + uint16_t inport = 0, bool gzip = true); private: @@ -405,6 +262,8 @@ namespace client std::string m_WebircPass; }; + + boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr); } } diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index ce800ecc..40752f5b 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,8 +72,9 @@ namespace client bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); - if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, - std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, std::placeholders::_2))) + if(!pool || !pool->StandardSelectPeers(path, hops, inbound, + std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3))) return false; // more here for outbound tunnels if(!inbound && m_RemoteLeaseSet) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 5d0f4425..2c0f8d92 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-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -43,23 +43,21 @@ namespace client m_Stream->AsyncClose (); m_Stream = nullptr; } - auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; case eSAMSocketTypeStream: - { - break; - } + break; case eSAMSocketTypeAcceptor: case eSAMSocketTypeForward: { - if (Session) + auto session = m_Owner.FindSession(m_ID); + if (session) { - if (m_IsAccepting && Session->GetLocalDestination ()) - Session->GetLocalDestination ()->StopAcceptingStreams (); + if (m_IsAccepting && session->GetLocalDestination ()) + session->GetLocalDestination ()->StopAcceptingStreams (); } break; } @@ -360,7 +358,7 @@ namespace client if (type == eSAMSessionTypeUnknown) { // unknown style - SendI2PError("Unknown STYLE"); + SendSessionI2PError("Unknown STYLE"); return; } @@ -371,18 +369,18 @@ namespace client // udp forward selected boost::system::error_code e; // TODO: support hostnames in udp forward - auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e); + auto addr = boost::asio::ip::make_address(params[SAM_PARAM_HOST], e); if (e) { // not an ip address - SendI2PError("Invalid IP Address in HOST"); + SendSessionI2PError("Invalid IP Address in HOST"); return; } auto port = std::stoi(params[SAM_PARAM_PORT]); if (port == -1) { - SendI2PError("Invalid port"); + SendSessionI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); @@ -415,12 +413,17 @@ namespace client { session->UDPEndpoint = forward; auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); + auto port = forward ? std::stoi(params[SAM_PARAM_PORT]) : 0; if (type == eSAMSessionTypeDatagram) dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + port + ); else // raw dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + port + ); } if (session->GetLocalDestination ()->IsReady ()) @@ -440,18 +443,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"); } } @@ -460,15 +468,11 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - uint8_t buf[1024]; - char priv[1024]; - size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024); - size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024); - priv[l1] = 0; + std::string priv = session->GetLocalDestination ()->GetPrivateKeys ().ToBase64 (); #ifdef _MSC_VER - size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); + size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); #else - size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); + size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); #endif SendMessageReply (m_Buffer, l2, false); } @@ -479,7 +483,7 @@ namespace client LogPrint (eLogDebug, "SAM: Stream connect: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { - SendI2PError ("Socket already in use"); + SendSessionI2PError ("Socket already in use"); return; } std::map params; @@ -518,15 +522,20 @@ namespace client { if (addr->IsIdentHash ()) { - auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash); - if (leaseSet) - Connect(leaseSet, session); - else + if (session->GetLocalDestination ()->GetIdentHash () != addr->identHash) { - session->GetLocalDestination ()->RequestDestination(addr->identHash, - std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, - shared_from_this(), std::placeholders::_1)); + auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash); + if (leaseSet) + Connect(leaseSet, session); + else + { + session->GetLocalDestination ()->RequestDestination(addr->identHash, + std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, + shared_from_this(), std::placeholders::_1)); + } } + else + SendStreamCantReachPeer ("Can't connect to myself"); } else // B33 session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, @@ -545,17 +554,22 @@ namespace client if (!session) session = m_Owner.FindSession(m_ID); if (session) { - m_SocketType = eSAMSocketTypeStream; - m_Stream = session->GetLocalDestination ()->CreateStream (remote); - if (m_Stream) + if (session->GetLocalDestination ()->SupportsEncryptionType (remote->GetEncryptionType ())) { - m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send - m_BufferOffset = 0; - I2PReceive (); - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + m_SocketType = eSAMSocketTypeStream; + m_Stream = session->GetLocalDestination ()->CreateStream (remote); + if (m_Stream) + { + m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send + m_BufferOffset = 0; + I2PReceive (); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + } + else + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); + SendStreamCantReachPeer ("Incompatible crypto"); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); @@ -568,7 +582,7 @@ namespace client else { LogPrint (eLogError, "SAM: Destination to connect not found"); - SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); + SendStreamCantReachPeer ("LeaseSet not found"); } } @@ -577,7 +591,7 @@ namespace client LogPrint (eLogDebug, "SAM: Stream accept: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { - SendI2PError ("Socket already in use"); + SendSessionI2PError ("Socket already in use"); return; } std::map params; @@ -593,9 +607,31 @@ namespace client if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { m_IsAccepting = true; + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + else + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts) + { + auto socket = session->acceptQueue.front ().first; + session->acceptQueue.pop_front (); + if (socket) + boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket)); + } + if (session->acceptQueue.size () < SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE) + { + // already accepting, queue up + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + session->acceptQueue.push_back (std::make_pair(shared_from_this(), ts)); + } + else + { + LogPrint (eLogInfo, "SAM: Session ", m_ID, " accept queue is full ", session->acceptQueue.size ()); + SendStreamI2PError ("Already accepting"); + } + } } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); @@ -615,26 +651,26 @@ namespace client } if (session->GetLocalDestination ()->IsAcceptingStreams ()) { - SendI2PError ("Already accepting"); + SendSessionI2PError ("Already accepting"); return; } auto it = params.find (SAM_PARAM_PORT); if (it == params.end ()) { - SendI2PError ("PORT is missing"); + SendSessionI2PError ("PORT is missing"); return; } auto port = std::stoi (it->second); if (port <= 0 || port >= 0xFFFF) { - SendI2PError ("Invalid PORT"); + SendSessionI2PError ("Invalid PORT"); return; } boost::system::error_code ec; auto ep = m_Socket.remote_endpoint (ec); if (ec) { - SendI2PError ("Socket error"); + SendSessionI2PError ("Socket error"); return; } ep.port (port); @@ -709,7 +745,7 @@ namespace client LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); @@ -786,13 +822,13 @@ namespace client if (type == eSAMSessionTypeUnknown) { // unknown style - SendI2PError("Unsupported STYLE"); + SendSessionI2PError("Unsupported STYLE"); return; } auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); if (fromPort == -1) { - SendI2PError("Invalid from port"); + SendSessionI2PError("Invalid from port"); return; } auto subsession = std::make_shared(masterSession, id, type, fromPort); @@ -805,7 +841,7 @@ namespace client SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); } else - SendI2PError ("Wrong session type"); + SendSessionI2PError ("Wrong session type"); } void SAMSocket::ProcessSessionRemove (char * buf, size_t len) @@ -827,20 +863,36 @@ namespace client SendSessionCreateReplyOk (); } else - SendI2PError ("Wrong session type"); + SendSessionI2PError ("Wrong session type"); } - void SAMSocket::SendI2PError(const std::string & msg) + void SAMSocket::SendReplyWithMessage (const char * reply, const std::string & msg) { - LogPrint (eLogError, "SAM: I2P error: ", msg); #ifdef _MSC_VER - size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); + size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); #else - size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); + size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); #endif SendMessageReply (m_Buffer, len, true); } + void SAMSocket::SendSessionI2PError(const std::string & msg) + { + LogPrint (eLogError, "SAM: Session I2P error: ", msg); + SendReplyWithMessage (SAM_SESSION_STATUS_I2P_ERROR, msg); + } + + void SAMSocket::SendStreamI2PError(const std::string & msg) + { + LogPrint (eLogError, "SAM: Stream I2P error: ", msg); + SendReplyWithMessage (SAM_STREAM_STATUS_I2P_ERROR, msg); + } + + void SAMSocket::SendStreamCantReachPeer(const std::string & msg) + { + SendReplyWithMessage (SAM_STREAM_STATUS_CANT_REACH_PEER, msg); + } + void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name) { if (leaseSet) @@ -988,13 +1040,13 @@ namespace client else { auto s = shared_from_this (); - m_Owner.GetService ().post ([s] { s->Terminate ("stream read error"); }); + boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error"); }); } } else { auto s = shared_from_this (); - m_Owner.GetService ().post ([s] { s->Terminate ("stream read error (op aborted)"); }); + boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error (op aborted)"); }); } } else @@ -1035,30 +1087,42 @@ namespace client m_Stream = stream; context.GetAddressBook ().InsertFullAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); - if (session) + if (session && !session->acceptQueue.empty ()) { - // find more pending acceptors - for (auto & it: m_Owner.ListSockets (m_ID)) - if (it->m_SocketType == eSAMSocketTypeAcceptor) + // pending acceptors + auto ts = i2p::util::GetSecondsSinceEpoch (); + while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts) + { + auto socket = session->acceptQueue.front ().first; + session->acceptQueue.pop_front (); + if (socket) + boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket)); + } + if (!session->acceptQueue.empty ()) + { + auto socket = session->acceptQueue.front ().first; + session->acceptQueue.pop_front (); + if (socket && socket->GetSocketType () == eSAMSocketTypeAcceptor) { - it->m_IsAccepting = true; - session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); - break; + socket->m_IsAccepting = true; + session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, socket, std::placeholders::_1)); } + } } if (!m_IsSilent) - { - // get remote peer address - auto ident_ptr = stream->GetRemoteIdentity(); - const size_t ident_len = ident_ptr->GetFullLen(); - uint8_t* ident = new uint8_t[ident_len]; - - // send remote peer address as base64 - const size_t l = ident_ptr->ToBuffer (ident, ident_len); - const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); - delete[] ident; - m_StreamBuffer[l1] = '\n'; - HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream + { + if (m_SocketType != eSAMSocketTypeTerminated) + { + // get remote peer address + auto ident = std::make_shared(stream->GetRemoteIdentity()->ToBase64 ()); // we need to keep it until sent + ident->push_back ('\n'); + // send remote peer address back to client like received from stream + boost::asio::async_write (m_Socket, boost::asio::buffer (ident->data (), ident->size ()), boost::asio::transfer_all(), + [ident, s = shared_from_this ()](const boost::system::error_code& ecode, size_t bytes_transferred) + { + s->HandleWriteI2PData (ecode, bytes_transferred); + }); + } } else I2PReceive (); @@ -1167,7 +1231,7 @@ namespace client void SAMSocket::HandleStreamSend(const boost::system::error_code & ec) { - m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); + boost::asio::post (m_Owner.GetService (), std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); } SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): @@ -1212,7 +1276,7 @@ namespace client subsessions.clear (); } - SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port): + SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, uint16_t port): SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port) { if (Type == eSAMSessionTypeStream) @@ -1239,10 +1303,10 @@ namespace client // TODO: implement datagrams } - SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): + SAMBridge::SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread): RunnableService ("SAM"), m_IsSingleThread (singleThread), - m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), - m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), portTCP)), + m_DatagramEndpoint (boost::asio::ip::make_address(address), (!portUDP) ? portTCP-1 : portUDP), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), m_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, @@ -1281,12 +1345,14 @@ namespace client LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); } + decltype(m_Sessions) sessions; { std::unique_lock l(m_SessionsMutex); - for (auto& it: m_Sessions) - it.second->Close (); - m_Sessions.clear (); - } + m_Sessions.swap (sessions); + } + for (auto& it: sessions) + it.second->Close (); + StopIOService (); } @@ -1411,17 +1477,39 @@ namespace client session->StopLocalDestination (); session->Close (); if (m_IsSingleThread) - { - auto timer = std::make_shared(GetService ()); - timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds - timer->async_wait ([timer, session](const boost::system::error_code& ecode) - { - // session's destructor is called here - }); - } + ScheduleSessionCleanupTimer (session); // let all session's streams close } } + void SAMBridge::ScheduleSessionCleanupTimer (std::shared_ptr session) + { + auto timer = std::make_shared(GetService ()); + timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds + timer->async_wait (std::bind (&SAMBridge::HandleSessionCleanupTimer, this, std::placeholders::_1, session, timer)); + } + + void SAMBridge::HandleSessionCleanupTimer (const boost::system::error_code& ecode, + std::shared_ptr session, std::shared_ptr timer) + { + if (ecode != boost::asio::error::operation_aborted && session) + { + auto dest = session->GetLocalDestination (); + if (dest) + { + auto streamingDest = dest->GetStreamingDestination (); + auto numStreams = streamingDest->GetNumStreams (); + if (numStreams > 0) + { + LogPrint (eLogInfo, "SAM: Session ", session->Name, " still has ", numStreams, " streams"); + ScheduleSessionCleanupTimer (session); + } + else + LogPrint (eLogDebug, "SAM: Session ", session->Name, " terminated"); + } + } + // session's destructor is called here unless rescheduled + } + std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); @@ -1481,7 +1569,7 @@ namespace client auto localDest = session->GetLocalDestination (); auto datagramDest = localDest ? localDest->GetDatagramDestination () : nullptr; if (datagramDest) - { + { i2p::data::IdentityEx dest; dest.FromBase64 (destination); if (session->Type == eSAMSessionTypeDatagram) @@ -1490,7 +1578,7 @@ namespace client datagramDest->SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); else LogPrint (eLogError, "SAM: Unexpected session type ", (int)session->Type, "for session ", sessionID); - } + } else LogPrint (eLogError, "SAM: Datagram destination is not set for session ", sessionID); } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 88990d7c..1886324a 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-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,7 +30,10 @@ 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 size_t SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE = 50; + const size_t SAM_SESSION_MAX_ACCEPT_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"; @@ -48,8 +51,8 @@ namespace client const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; const char SAM_STREAM_STATUS_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\n"; - const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; - const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; + const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"%s\"\n"; + const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"%s\"\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; const char SAM_STREAM_FORWARD[] = "STREAM FORWARD"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; @@ -141,7 +144,10 @@ namespace client void ProcessNamingLookup (char * buf, size_t len); void ProcessSessionAdd (char * buf, size_t len); void ProcessSessionRemove (char * buf, size_t len); - void SendI2PError(const std::string & msg); + void SendReplyWithMessage (const char * reply, const std::string & msg); + void SendSessionI2PError(const std::string & msg); + void SendStreamI2PError(const std::string & msg); + void SendStreamCantReachPeer(const std::string & msg); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); @@ -188,7 +194,8 @@ namespace client std::string Name; SAMSessionType Type; std::shared_ptr UDPEndpoint; // TODO: move - + std::list, uint64_t> > acceptQueue; // socket, receive time in seconds + SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); virtual ~SAMSession () {}; @@ -221,9 +228,9 @@ namespace client struct SAMSubSession: public SAMSession { std::shared_ptr masterSession; - int inPort; + uint16_t inPort; - SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port); + SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, uint16_t port); // implements SAMSession std::shared_ptr GetLocalDestination (); void StopLocalDestination (); @@ -233,13 +240,13 @@ namespace client { public: - SAMBridge (const std::string& address, int port, bool singleThread); + SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread); ~SAMBridge (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return GetIOService (); }; + auto& GetService () { return GetIOService (); }; std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); bool AddSession (std::shared_ptr session); @@ -264,6 +271,10 @@ namespace client void ReceiveDatagram (); void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void ScheduleSessionCleanupTimer (std::shared_ptr session); + void HandleSessionCleanupTimer (const boost::system::error_code& ecode, + std::shared_ptr session, std::shared_ptr timer); + private: bool m_IsSingleThread; diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 961ff934..27df33c8 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,7 @@ #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" +#include "Socks5.h" namespace i2p { @@ -27,10 +28,6 @@ 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_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; - struct SOCKSDnsAddress { uint8_t size; @@ -66,6 +63,11 @@ namespace proxy GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, + GET5_USERPASSWD, + GET5_USER_SIZE, + GET5_USER, + GET5_PASSWD_SIZE, + GET5_PASSWD, READY, UPSTREAM_RESOLVE, UPSTREAM_CONNECT, @@ -124,11 +126,10 @@ namespace proxy void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); - boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); - boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); - boost::asio::const_buffers_1 GenerateUpstreamRequest(); + boost::asio::const_buffer GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); + boost::asio::const_buffer GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); bool Socks5ChooseAuth(); + void Socks5UserPasswdResponse (); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); @@ -137,27 +138,26 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); - void SocksUpstreamSuccess(); + template + void SocksUpstreamSuccess(std::shared_ptr& upstreamSock); void AsyncUpstreamSockRead(); - void SendUpstreamRequest(); - void HandleUpstreamData(uint8_t * buff, std::size_t len); - void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); - void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + template + void SendUpstreamRequest(std::shared_ptr& upstreamSock); void HandleUpstreamConnected(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::iterator itr); + const boost::asio::ip::tcp::endpoint& ep); void HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::iterator itr); + boost::asio::ip::tcp::resolver::results_type endpoints); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; std::shared_ptr m_sock, m_upstreamSock; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + std::shared_ptr m_upstreamLocalSock; +#endif std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; - uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; - uint8_t m_upstream_request[14+max_socks_hostname_size]; - std::size_t m_upstream_response_len; address m_address; //Address std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests @@ -216,6 +216,14 @@ namespace proxy m_upstreamSock->close(); m_upstreamSock = nullptr; } +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + if (m_upstreamLocalSock) + { + LogPrint(eLogDebug, "SOCKS: Closing upstream local socket"); + m_upstreamLocalSock->close(); + m_upstreamLocalSock = nullptr; + } +#endif if (m_stream) { LogPrint(eLogDebug, "SOCKS: Closing stream"); @@ -224,17 +232,17 @@ namespace proxy Done(shared_from_this()); } - boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) + boost::asio::const_buffer SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) { assert(error >= SOCKS4_OK); m_response[0] = '\x00'; // version m_response[1] = error; // response code htobe16buf(m_response + 2, port); // port htobe32buf(m_response + 4, ip); // IP - return boost::asio::const_buffers_1(m_response,8); + return boost::asio::const_buffer (m_response,8); } - boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) + boost::asio::const_buffer SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { size_t size = 6; // header + port assert(error <= SOCKS5_ADDR_UNSUP); @@ -271,45 +279,14 @@ namespace proxy } break; } - return boost::asio::const_buffers_1(m_response, size); - } - - boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest() - { - size_t upstreamRequestSize = 0; - // TODO: negotiate with upstream - // SOCKS 4a - m_upstream_request[0] = '\x04'; //version - m_upstream_request[1] = m_cmd; - htobe16buf(m_upstream_request + 2, m_port); - m_upstream_request[4] = 0; - m_upstream_request[5] = 0; - m_upstream_request[6] = 0; - m_upstream_request[7] = 1; - // user id - m_upstream_request[8] = 'i'; - m_upstream_request[9] = '2'; - m_upstream_request[10] = 'p'; - m_upstream_request[11] = 'd'; - m_upstream_request[12] = 0; - upstreamRequestSize += 13; - if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) { - // bounds check okay - memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size); - upstreamRequestSize += m_address.dns.size; - // null terminate - m_upstream_request[++upstreamRequestSize] = 0; - } else { - LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )"); - } - return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize); + return boost::asio::const_buffer (m_response, size); } bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; // Version m_response[1] = m_authchosen; // Response code - boost::asio::const_buffers_1 response(m_response, 2); + boost::asio::const_buffer response(m_response, 2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); @@ -324,10 +301,19 @@ namespace proxy } } + void SOCKSHandler::Socks5UserPasswdResponse () + { + m_response[0] = 1; // Version of the subnegotiation + m_response[1] = 0; // Response code + LogPrint(eLogDebug, "SOCKS: v5 user/password response"); + boost::asio::async_write(*m_sock, boost::asio::const_buffer(m_response, 2), + std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); + } + /* All hope is lost beyond this point */ void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error) { - boost::asio::const_buffers_1 response(nullptr,0); + boost::asio::const_buffer response(nullptr,0); assert(error != SOCKS4_OK && error != SOCKS5_OK); switch (m_socksv) { @@ -347,7 +333,7 @@ namespace proxy void SOCKSHandler::SocksRequestSuccess() { - boost::asio::const_buffers_1 response(nullptr,0); + boost::asio::const_buffer response(nullptr,0); // TODO: this should depend on things like the command type and callbacks may change switch (m_socksv) { @@ -438,10 +424,15 @@ namespace proxy m_parseleft --; if (*sock_buff == AUTH_NONE) m_authchosen = AUTH_NONE; + else if (*sock_buff == AUTH_USERPASSWD) + m_authchosen = AUTH_USERPASSWD; if ( m_parseleft == 0 ) { if (!Socks5ChooseAuth()) return false; - EnterState(GET5_REQUESTV); + if (m_authchosen == AUTH_USERPASSWD) + EnterState(GET5_USERPASSWD); + else + EnterState(GET5_REQUESTV); } break; case GET_COMMAND: @@ -452,9 +443,7 @@ namespace proxy break; case CMD_UDP: if (m_socksv == SOCKS5) break; -#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif default: LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); @@ -557,6 +546,44 @@ namespace proxy m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; + case GET5_USERPASSWD: + if (*sock_buff != 1) + { + LogPrint(eLogError,"SOCKS: v5 rejected invalid username/password subnegotiation: ", ((int)*sock_buff)); + SocksRequestFailed(SOCKS5_GEN_FAIL); + return false; + } + EnterState(GET5_USER_SIZE); + break; + case GET5_USER_SIZE: + if (*sock_buff) + EnterState(GET5_USER, *sock_buff); + else // empty user + EnterState(GET5_PASSWD_SIZE); + break; + case GET5_USER: + // skip user for now + m_parseleft--; + if (m_parseleft == 0) EnterState(GET5_PASSWD_SIZE); + break; + case GET5_PASSWD_SIZE: + if (*sock_buff) + EnterState(GET5_PASSWD, *sock_buff); + else // empty password + { + Socks5UserPasswdResponse (); + EnterState(GET5_REQUESTV); + } + break; + case GET5_PASSWD: + // skip passwd for now + m_parseleft--; + if (m_parseleft == 0) + { + Socks5UserPasswdResponse (); + EnterState(GET5_REQUESTV); + } + break; default: LogPrint(eLogError, "SOCKS: Parse state?? ", m_state); Terminate(); @@ -660,42 +687,47 @@ namespace proxy void SOCKSHandler::ForwardSOCKS() { LogPrint(eLogInfo, "SOCKS: Forwarding to upstream"); - EnterState(UPSTREAM_RESOLVE); - boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort)); - m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - } - - void SOCKSHandler::AsyncUpstreamSockRead() - { - LogPrint(eLogDebug, "SOCKS: Async upstream sock read"); - if (m_upstreamSock) { - m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), - std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); - } else { - LogPrint(eLogError, "SOCKS: No upstream socket for read"); - SocksRequestFailed(SOCKS5_GEN_FAIL); + if (m_UpstreamProxyPort) // TCP + { + EnterState(UPSTREAM_RESOLVE); + m_proxy_resolver.async_resolve(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort), + std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + else if (!m_UpstreamProxyAddress.empty ())// local + { +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + EnterState(UPSTREAM_CONNECT); + m_upstreamLocalSock = std::make_shared(GetOwner()->GetService()); + auto s = shared_from_this (); + m_upstreamLocalSock->async_connect(m_UpstreamProxyAddress, + [s](const boost::system::error_code& ecode) + { + if (ecode) + { + LogPrint(eLogWarning, "SOCKS: Could not connect to local upstream proxy: ", ecode.message()); + s->SocksRequestFailed(SOCKS5_NET_UNREACH); + return; + } + LogPrint(eLogInfo, "SOCKS: Connected to local upstream proxy"); + s->SendUpstreamRequest(s->m_upstreamLocalSock); + }); +#else + LogPrint(eLogError, "SOCKS: Local sockets for upstream proxy not supported"); + SocksRequestFailed(SOCKS5_ADDR_UNSUP); +#endif } + else + { + LogPrint(eLogError, "SOCKS: Incorrect upstream proxy address"); + SocksRequestFailed(SOCKS5_ADDR_UNSUP); + } } - void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered) - { - if (ecode) { - if (m_state == UPSTREAM_HANDSHAKE ) { - // we are trying to handshake but it failed - SocksRequestFailed(SOCKS5_NET_UNREACH); - } else { - LogPrint(eLogError, "SOCKS: Bad state when reading from upstream: ", (int) m_state); - } - return; - } - HandleUpstreamData(m_upstream_response, bytes_transfered); - } - - void SOCKSHandler::SocksUpstreamSuccess() + template + void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr& upstreamSock) { LogPrint(eLogInfo, "SOCKS: Upstream success"); - boost::asio::const_buffers_1 response(nullptr, 0); + boost::asio::const_buffer response(nullptr, 0); switch (m_socksv) { case SOCKS4: @@ -709,58 +741,40 @@ namespace proxy break; } m_sock->send(response); - auto forwarder = std::make_shared(GetOwner(), m_sock, m_upstreamSock); - m_upstreamSock = nullptr; + auto forwarder = CreateSocketsPipe (GetOwner(), m_sock, upstreamSock); + upstreamSock = nullptr; m_sock = nullptr; GetOwner()->AddHandler(forwarder); forwarder->Start(); Terminate(); - } - void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) - { - if (m_state == UPSTREAM_HANDSHAKE) { - m_upstream_response_len += len; - // handle handshake data - if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { - // too small, continue reading - AsyncUpstreamSockRead(); - } else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { - // just right - uint8_t resp = m_upstream_response[1]; - if (resp == SOCKS4_OK) { - // we have connected ! - SocksUpstreamSuccess(); - } else { - // upstream failure - LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", (int) resp); - // TODO: runtime error? - SocksRequestFailed(SOCKS5_GEN_FAIL); - } - } else { - // too big - SocksRequestFailed(SOCKS5_GEN_FAIL); - } - } else { - // invalid state - LogPrint(eLogError, "SOCKS: Invalid state reading from upstream: ", (int) m_state); - } - } - - void SOCKSHandler::SendUpstreamRequest() + template + void SOCKSHandler::SendUpstreamRequest(std::shared_ptr& upstreamSock) { LogPrint(eLogInfo, "SOCKS: Negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); - if (m_upstreamSock) { - boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); - AsyncUpstreamSockRead(); - } else { + if (upstreamSock) + { + auto s = shared_from_this (); + i2p::transport::Socks5Handshake (*upstreamSock, std::make_pair(m_address.dns.ToString (), m_port), + [s, &upstreamSock](const boost::system::error_code& ec) + { + if (!ec) + s->SocksUpstreamSuccess(upstreamSock); + else + { + s->SocksRequestFailed(SOCKS5_NET_UNREACH); + LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", ec.message ()); + } + }); + } + else LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to"); - } } - void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) + void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, + const boost::asio::ip::tcp::endpoint& ep) { if (ecode) { LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message()); @@ -768,10 +782,11 @@ namespace proxy return; } LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy"); - SendUpstreamRequest(); + SendUpstreamRequest(m_upstreamSock); } - void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) + void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, + boost::asio::ip::tcp::resolver::results_type endpoints) { if (ecode) { // error resolving @@ -783,12 +798,12 @@ namespace proxy EnterState(UPSTREAM_CONNECT); auto & service = GetOwner()->GetService(); m_upstreamSock = std::make_shared(service); - boost::asio::async_connect(*m_upstreamSock, itr, + boost::asio::async_connect(*m_upstreamSock, endpoints, std::bind(&SOCKSHandler::HandleUpstreamConnected, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int port, + SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, uint16_t port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) diff --git a/libi2pd_client/SOCKS.h b/libi2pd_client/SOCKS.h index f41cfd72..bd88d6e6 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.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 * @@ -23,7 +23,7 @@ namespace proxy { public: - SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, + SOCKSServer(const std::string& name, const std::string& address, uint16_t port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp new file mode 100644 index 00000000..b173fc0f --- /dev/null +++ b/libi2pd_client/UDPTunnel.cpp @@ -0,0 +1,413 @@ +/* +* Copyright (c) 2013-2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "Log.h" +#include "util.h" +#include "ClientContext.h" +#include "I2PTunnel.h" // for GetLoopbackAddressFor +#include "UDPTunnel.h" + +namespace i2p +{ +namespace client +{ + void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) + m_LastSession = ObtainUDPSession(from, toPort, fromPort); + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + + void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (m_LastSession && (fromPort != m_LastSession->RemotePort || toPort != m_LastSession->LocalPort)) + { + std::lock_guard lock(m_SessionsMutex); + auto it = m_Sessions.find (GetSessionIndex (fromPort, toPort)); + if (it != m_Sessions.end ()) + m_LastSession = it->second; + else + m_LastSession = nullptr; + } + if (m_LastSession) + { + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + } + + void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + auto itr = m_Sessions.begin(); + while(itr != m_Sessions.end()) + { + if(now - itr->second->LastActivity >= delta ) + itr = m_Sessions.erase(itr); + else + itr++; + } + } + + void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + std::vector removePorts; + for (const auto & s : m_Sessions) { + if (now - s.second->second >= delta) + removePorts.push_back(s.first); + } + for(auto port : removePorts) { + m_Sessions.erase(port); + } + } + + UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) + { + auto ih = from.GetIdentHash(); + auto idx = GetSessionIndex (remotePort, localPort); + { + std::lock_guard lock(m_SessionsMutex); + auto it = m_Sessions.find (idx); + if (it != m_Sessions.end ()) + { + if (it->second->Identity.GetLL()[0] == ih.GetLL()[0]) + { + LogPrint(eLogDebug, "UDPServer: Found session ", it->second->IPSocket.local_endpoint(), " ", ih.ToBase32()); + return it->second; + } + else + { + LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from different address. Removed"); + m_Sessions.erase (it); + } + } + } + + boost::asio::ip::address addr; + /** create new udp session */ + if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) + { + auto ident = from.GetIdentHash(); + addr = GetLoopbackAddressFor(ident); + } + else + addr = m_LocalAddress; + + auto s = std::make_shared(boost::asio::ip::udp::endpoint(addr, 0), + m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort); + std::lock_guard lock(m_SessionsMutex); + m_Sessions.emplace (idx, s); + return s; + } + + UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + const boost::asio::ip::udp::endpoint& endpoint, const i2p::data::IdentHash& to, + uint16_t ourPort, uint16_t theirPort) : + m_Destination(localDestination->GetDatagramDestination()), + IPSocket(localDestination->GetService(), localEndpoint), + Identity (to), SendEndpoint(endpoint), + LastActivity(i2p::util::GetMillisecondsSinceEpoch()), + LocalPort(ourPort), + RemotePort(theirPort) + { + IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + Receive(); + } + + void UDPSession::Receive() + { + LogPrint(eLogDebug, "UDPSession: Receive"); + IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), + FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); + } + + void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) + { + if(!ecode) + { + LogPrint(eLogDebug, "UDPSession: Forward ", len, "B from ", FromEndpoint); + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + auto session = m_Destination->GetSession (Identity); + if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); + else + m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = IPSocket.available(ec); + if (ec || !moreBytes) break; + len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); + m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); + numPackets++; + } + if (numPackets > 0) + LogPrint(eLogDebug, "UDPSession: Forward more ", numPackets, "packets B from ", FromEndpoint); + m_Destination->FlushSendQueue (session); + LastActivity = ts; + Receive(); + } + else + LogPrint(eLogError, "UDPSession: ", ecode.message()); + } + + I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr localDestination, + const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t inPort, bool gzip) : + m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), + m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip) + { + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel () + { + Stop (); + } + + void I2PUDPServerTunnel::Start () + { + m_LocalDest->Start (); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + dgram->SetReceiver ( + std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + m_inPort + ); + dgram->SetRawReceiver ( + std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + m_inPort + ); + } + + void I2PUDPServerTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) { + dgram->ResetReceiver (m_inPort); + dgram->ResetRawReceiver (m_inPort); + } + } + + std::vector > I2PUDPServerTunnel::GetSessions () + { + std::vector > sessions; + std::lock_guard lock (m_SessionsMutex); + + for (const auto &it: m_Sessions) + { + auto s = it.second; + if (!s->m_Destination) continue; + auto info = s->m_Destination->GetInfoForRemote (s->Identity); + if (!info) continue; + + auto sinfo = std::make_shared (); + sinfo->Name = m_Name; + sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); + sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); + sinfo->CurrentIBGW = info->IBGW; + sinfo->CurrentOBEP = info->OBEP; + sessions.push_back (sinfo); + } + return sessions; + } + + I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + const boost::asio::ip::udp::endpoint& localEndpoint, + std::shared_ptr localDestination, + uint16_t remotePort, bool gzip) : + m_Name (name), m_RemoteDest (remoteDest), m_LocalDest (localDestination), m_LocalEndpoint (localEndpoint), + m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), + m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip) + { + } + + I2PUDPClientTunnel::~I2PUDPClientTunnel () + { + Stop (); + } + + void I2PUDPClientTunnel::Start () + { + // Reset flag in case of tunnel reload + if (m_cancel_resolve) m_cancel_resolve = false; + + m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); + m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); + m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5), + RemotePort + ); + dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + RemotePort + ); + + m_LocalDest->Start (); + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); + RecvFromLocal (); + } + + void I2PUDPClientTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) { + dgram->ResetReceiver (RemotePort); + dgram->ResetRawReceiver (RemotePort); + } + m_cancel_resolve = true; + + m_Sessions.clear(); + + if(m_LocalSocket && m_LocalSocket->is_open ()) + m_LocalSocket->close (); + + if(m_ResolveThread) + { + m_ResolveThread->join (); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + m_RemoteAddr = nullptr; + } + + void I2PUDPClientTunnel::RecvFromLocal () + { + m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), + m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); + } + + void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) + { + if (m_cancel_resolve) { + LogPrint (eLogDebug, "UDP Client: Ignoring incoming data: stopping"); + return; + } + if (ec) { + LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); + RecvFromLocal (); // Restart listener and continue work + return; + } + if (!m_RemoteAddr || !m_RemoteAddr->IsIdentHash ()) // TODO: handle B33 + { + LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); + RecvFromLocal (); + return; // drop, remote not resolved + } + auto remotePort = m_RecvEndpoint.port (); + if (!m_LastPort || m_LastPort != remotePort) + { + auto itr = m_Sessions.find (remotePort); + if (itr != m_Sessions.end ()) + m_LastSession = itr->second; + else + { + m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); + m_Sessions.emplace (remotePort, m_LastSession); + } + m_LastPort = remotePort; + } + // send off to remote i2p destination + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteAddr->identHash.ToBase32 (), ":", RemotePort); + auto session = m_LocalDest->GetDatagramDestination ()->GetSession (m_RemoteAddr->identHash); + if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + else + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = m_LocalSocket->available (ec); + if (ec || !moreBytes) break; + transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); + remotePort = m_RecvEndpoint.port (); + // TODO: check remotePort + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + numPackets++; + } + if (numPackets) + LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteAddr->identHash.ToBase32 ()); + m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); + + // mark convo as active + if (m_LastSession) + m_LastSession->second = ts; + RecvFromLocal (); + } + + std::vector > I2PUDPClientTunnel::GetSessions () + { + // TODO: implement + std::vector > infos; + return infos; + } + + void I2PUDPClientTunnel::TryResolving () + { + i2p::util::SetThreadName ("UDP Resolver"); + LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + + while (!(m_RemoteAddr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) + { + LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); + std::this_thread::sleep_for (std::chrono::seconds (1)); + } + if (m_cancel_resolve) + { + LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); + return; + } + if (!m_RemoteAddr) + { + LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); + return; + } + LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteAddr->identHash.ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (m_RemoteAddr && from.GetIdentHash() == m_RemoteAddr->identHash) + HandleRecvFromI2PRaw (fromPort, toPort, buf, len); + else + LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + auto itr = m_Sessions.find (toPort); + // found convo ? + if (itr != m_Sessions.end ()) + { + // found convo + if (len > 0) + { + LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteAddr ? m_RemoteAddr->identHash.ToBase32 () : ""); + m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); + // mark convo as active + itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); + } + } + 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 new file mode 100644 index 00000000..5650124c --- /dev/null +++ b/libi2pd_client/UDPTunnel.h @@ -0,0 +1,189 @@ +/* +* Copyright (c) 2013-2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef UDPTUNNEL_H__ +#define UDPTUNNEL_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "Identity.h" +#include "Destination.h" +#include "Datagram.h" +#include "AddressBook.h" + +namespace i2p +{ +namespace client +{ + /** 2 minute timeout for udp sessions */ + const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; + const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds + + /** max size for i2p udp */ + const size_t I2P_UDP_MAX_MTU = 64*1024; + + struct UDPSession + { + i2p::datagram::DatagramDestination * m_Destination; + boost::asio::ip::udp::socket IPSocket; + i2p::data::IdentHash Identity; + boost::asio::ip::udp::endpoint FromEndpoint; + boost::asio::ip::udp::endpoint SendEndpoint; + uint64_t LastActivity; + + uint16_t LocalPort; + uint16_t RemotePort; + + uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + + UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + const boost::asio::ip::udp::endpoint& remote, const i2p::data::IdentHash& ident, + uint16_t ourPort, uint16_t theirPort); + void HandleReceived(const boost::system::error_code & ecode, std::size_t len); + void Receive(); + }; + + + /** read only info about a datagram session */ + struct DatagramSessionInfo + { + /** the name of this forward */ + std::string Name; + /** ident hash of local destination */ + std::shared_ptr LocalIdent; + /** ident hash of remote destination */ + std::shared_ptr RemoteIdent; + /** ident hash of IBGW in use currently in this session or nullptr if none is set */ + std::shared_ptr CurrentIBGW; + /** ident hash of OBEP in use for this session or nullptr if none is set */ + std::shared_ptr CurrentOBEP; + /** i2p router's udp endpoint */ + boost::asio::ip::udp::endpoint LocalEndpoint; + /** client's udp endpoint */ + boost::asio::ip::udp::endpoint RemoteEndpoint; + /** how long has this conversation been idle in ms */ + uint64_t idle; + }; + + typedef std::shared_ptr UDPSessionPtr; + + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ + class I2PUDPServerTunnel + { + public: + + I2PUDPServerTunnel (const std::string & name, + std::shared_ptr localDestination, + const boost::asio::ip::address& localAddress, + const boost::asio::ip::udp::endpoint& forwardTo, uint16_t port, bool gzip); + ~I2PUDPServerTunnel (); + + /** expire stale udp conversations */ + void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + void Start (); + void Stop (); + const char * GetName () const { return m_Name.c_str(); } + std::vector > GetSessions (); + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + + void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } + + private: + + void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + uint32_t GetSessionIndex (uint16_t fromPort, uint16_t toPort) const { return ((uint32_t)fromPort << 16) + toPort; } + + private: + + bool m_IsUniqueLocal; + const std::string m_Name; + boost::asio::ip::address m_LocalAddress; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + std::mutex m_SessionsMutex; + std::unordered_map m_Sessions; // (from port, to port)->session + std::shared_ptr m_LocalDest; + UDPSessionPtr m_LastSession; + uint16_t m_inPort; + bool m_Gzip; + + public: + + bool isUpdated; // transient, used during reload only + }; + + class I2PUDPClientTunnel + { + public: + + I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + const boost::asio::ip::udp::endpoint& localEndpoint, std::shared_ptr localDestination, + uint16_t remotePort, bool gzip); + ~I2PUDPClientTunnel (); + + void Start (); + void Stop (); + const char * GetName () const { return m_Name.c_str(); } + std::vector > GetSessions (); + + bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } + + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + inline void SetLocalDestination (std::shared_ptr dest) + { + if (m_LocalDest) m_LocalDest->Release (); + if (dest) dest->Acquire (); + m_LocalDest = dest; + } + + void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + + private: + + typedef std::pair UDPConvo; + void RecvFromLocal (); + void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); + void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void TryResolving (); + + private: + + const std::string m_Name; + std::mutex m_SessionsMutex; + std::unordered_map > m_Sessions; // maps i2p port -> local udp convo + const std::string m_RemoteDest; + std::shared_ptr m_LocalDest; + const boost::asio::ip::udp::endpoint m_LocalEndpoint; + std::shared_ptr m_RemoteAddr; + std::thread * m_ResolveThread; + std::unique_ptr m_LocalSocket; + boost::asio::ip::udp::endpoint m_RecvEndpoint; + uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; + uint16_t RemotePort, m_LastPort; + bool m_cancel_resolve; + bool m_Gzip; + std::shared_ptr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only + }; + + +} +} + +#endif 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..fb03d434 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,123 @@ +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-aeadchacha20poly1305_SRCS + test-aeadchacha20poly1305.cpp +) + +set(test-blinding_SRCS + test-blinding.cpp +) + +SET(test-elligator_SRCS + test-elligator.cpp +) + +set(test-eddsa_SRCS + test-eddsa.cpp +) + +set(test-aes_SRCS + test-aes.cpp +) + +add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS}) +add_executable(test-http-req ${test-http-req_SRCS}) +add_executable(test-http-res ${test-http-res_SRCS}) +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-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) +add_executable(test-blinding ${test-blinding_SRCS}) +add_executable(test-elligator ${test-elligator_SRCS}) +add_executable(test-eddsa ${test-eddsa_SRCS}) +add_executable(test-aes ${test-aes_SRCS}) + +set(LIBS + libi2pd + ${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-aeadchacha20poly1305 ${LIBS}) +target_link_libraries(test-blinding ${LIBS}) +target_link_libraries(test-elligator ${LIBS}) +target_link_libraries(test-eddsa ${LIBS}) +target_link_libraries(test-aes ${LIBS}) + +add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked) +add_test(test-http-req ${TEST_PATH}/test-http-req) +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-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) +add_test(test-blinding ${TEST_PATH}/test-blinding) +add_test(test-elligator ${TEST_PATH}/test-elligator) +add_test(test-eddsa ${TEST_PATH}/test-eddsa) +add_test(test-aes ${TEST_PATH}/test-aes) diff --git a/tests/Makefile b/tests/Makefile index 8eb52fde..b020427d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,36 +1,66 @@ -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++17 -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-aeadchacha20poly1305 test-blinding \ + test-elligator test-eddsa test-aes + +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_system$(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-aeadchacha20poly1305: test-aeadchacha20poly1305.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-blinding: test-blinding.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-elligator: test-elligator.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +test-eddsa: test-eddsa.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +test-aes: test-aes.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) run: $(TESTS) - @for TEST in $(TESTS); do ./$$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..2ba6a253 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, @@ -43,18 +43,20 @@ uint8_t encrypted[114] = int main () { uint8_t buf[114+16]; + i2p::crypto::AEADChaCha20Poly1305Encryptor encryptor; // test encryption - i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true); + encryptor.Encrypt ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16); assert (memcmp (buf, encrypted, 114) == 0); assert (memcmp (buf + 114, tag, 16) == 0); // test decryption uint8_t buf1[114]; - assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false)); + i2p::crypto::AEADChaCha20Poly1305Decryptor decryptor; + assert (decryptor.Decrypt (buf, 114, ad, 12, key, nonce, buf1, 114)); assert (memcmp (buf1, text, 114) == 0); // test encryption of multiple buffers memcpy (buf, text, 114); - std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; - i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); - i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); + 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) }; + encryptor.Encrypt (bufs, key, nonce, buf + 114); + decryptor.Decrypt (buf, 114, nullptr, 0, key, nonce, buf1, 114); assert (memcmp (buf1, text, 114) == 0); } diff --git a/tests/test-aes.cpp b/tests/test-aes.cpp new file mode 100644 index 00000000..15f4de1e --- /dev/null +++ b/tests/test-aes.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "Crypto.h" + +uint8_t ecb_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t ecb_plain1[16] = +{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a +}; + +uint8_t ecb_cipher1[16] = +{ + 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8 +}; + +uint8_t cbc_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t cbc_iv1[16] = +{ + 0xF5, 0x8C, 0x4C, 0x04, 0xD6, 0xE5, 0xF1, 0xBA, 0x77, 0x9E, 0xAB, 0xFB, 0x5F, 0x7B, 0xFB, 0xD6 +}; + +uint8_t cbc_plain1[16] = +{ + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51 +}; + +uint8_t cbc_cipher1[16] = +{ + 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d +}; + +int main () +{ + // ECB encrypt test1 + i2p::crypto::ECBEncryption ecbencryption; + ecbencryption.SetKey (ecb_key1); + uint8_t out[16]; + ecbencryption.Encrypt (ecb_plain1, out); + assert (memcmp (ecb_cipher1, out, 16) == 0); + + // ECB decrypt test1 + i2p::crypto::ECBDecryption ecbdecryption; + ecbdecryption.SetKey (ecb_key1); + ecbdecryption.Decrypt (ecb_cipher1, out); + assert (memcmp (ecb_plain1, out, 16) == 0); + // CBC encrypt test + i2p::crypto::CBCEncryption cbcencryption; + cbcencryption.SetKey (cbc_key1); + cbcencryption.Encrypt (cbc_plain1, 16, cbc_iv1, out); + assert (memcmp (cbc_cipher1, out, 16) == 0); + // CBC decrypt test + i2p::crypto::CBCDecryption cbcdecryption; + cbcdecryption.SetKey (cbc_key1); + cbcdecryption.Decrypt (cbc_cipher1, 16, cbc_iv1, out); + assert (memcmp (cbc_plain1, out, 16) == 0); +} + diff --git a/tests/test-blinding.cpp b/tests/test-blinding.cpp index 5490acd4..10b72e4f 100644 --- a/tests/test-blinding.cpp +++ b/tests/test-blinding.cpp @@ -13,31 +13,29 @@ 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[64], blindedPub[128]; + uint8_t blindedPriv[32], blindedPub[32]; auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - uint8_t blindedPub1[128]; + 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)); // try to sign and verify - std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv)); - uint8_t buf[100], signature[128]; + std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); + uint8_t buf[100], signature[64]; memset (buf, 1, 100); - blindedSigner->Sign (buf, 100, signature); - std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (sigType)); - blindedVerifier->SetPublicKey (blindedPub1); - assert (blindedVerifier->Verify (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)); } int main () { - // RedDSA test + // EdDSA test + BlindTest (SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + // RedDSA test BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); - // P256 test - BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - // P384 test - BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384); } diff --git a/tests/test-eddsa.cpp b/tests/test-eddsa.cpp new file mode 100644 index 00000000..b3895e2b --- /dev/null +++ b/tests/test-eddsa.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "Signature.h" + +// TEST 1024 from RFC-8032 + +int main () +{ + uint8_t key[32], pub[32], msg[1024], sig[64]; + BIGNUM * input = BN_new(); + BN_hex2bn(&input, "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5"); + BN_bn2bin(input, key); + BN_hex2bn(&input, + "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98" + "fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8" + "79de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d" + "658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc" + "1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4fe" + "ba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e" + "06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbef" + "efd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7" + "aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed1" + "85ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2" + "d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24" + "554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f270" + "88d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc" + "2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b07" + "07e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128ba" + "b27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51a" + "ddd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429e" + "c96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb7" + "51fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c" + "42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8" + "ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34df" + "f7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08" + "d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649" + "de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e4" + "88acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a3" + "2ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e" + "6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5f" + "b93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b5" + "0d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1" + "369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380d" + "b2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c" + "0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0" + ); + BN_bn2bin(input, msg); + BN_hex2bn(&input, + "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350" + "aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03"); + BN_bn2bin(input, sig); + BN_hex2bn(&input, + "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e"); + BN_bn2bin(input, pub); + + uint8_t s[64]; + i2p::crypto::EDDSA25519Signer signer (key); + signer.Sign (msg, 1023, s); +#if OPENSSL_EDDSA + assert(memcmp (s, sig, 64) == 0); +#endif + + i2p::crypto::EDDSA25519Verifier verifier; + verifier.SetPublicKey (pub); + assert(verifier.Verify (msg, 1023, s)); +} 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 deleted file mode 100644 index 2ab8ad6a..00000000 --- a/tests/test-x25519.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include - -#include "Ed25519.h" - -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] = -{ - 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] = -{ - 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, - 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, - 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52 -}; - -int main () -{ -#if !OPENSSL_X25519 -// we test it for openssl < 1.1.0 - uint8_t buf[32]; - BN_CTX * ctx = BN_CTX_new (); - i2p::crypto::GetEd25519 ()->ScalarMul (u, k, buf, ctx); - BN_CTX_free (ctx); - assert(memcmp (buf, p, 32) == 0); -#endif -} -