diff --git a/.editorconfig b/.editorconfig index ed3973c5..0f57a84a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,3 +30,10 @@ indent_size = 4 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 index bda12dbc..32698498 100644 --- a/.github/workflows/build-deb.yml +++ b/.github/workflows/build-deb.yml @@ -1,32 +1,61 @@ name: Build Debian packages -on: [push, pull_request] +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'] + dist: ['bullseye', 'bookworm', 'trixie'] + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: change debian changelog - run: | - sudo apt-get update - sudo apt-get install devscripts - debchange -v "`git describe --tags`-${{ matrix.dist }}" -b -M --distribution ${{ matrix.dist }} "trunk build" - - uses: jtdor/build-deb-action@v1 + + - 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 - - uses: actions/upload-artifact@v3 + 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 - - uses: actions/upload-artifact@v3 + + - 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 557a9b13..a4a7566a 100644 --- a/.github/workflows/build-freebsd.yml +++ b/.github/workflows/build-freebsd.yml @@ -1,16 +1,37 @@ 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.3.0 + uses: vmactions/freebsd-vm@v1 with: usesh: true mem: 2048 @@ -21,8 +42,9 @@ jobs: cd build cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . gmake -j2 + - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: i2pd-freebsd - path: build/i2pd \ No newline at end of file + path: build/i2pd diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index afddb7e9..31f0b90d 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -1,21 +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 d8828f61..0b65ec9d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,38 +1,65 @@ 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 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1f3142e4..c6d55664 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,11 +5,22 @@ on: branches: - openssl - docker + paths: + - .github/workflows/docker.yml + - contrib/docker/** + - contrib/certificates/** + - daemon/** + - i18n/** + - libi2pd/** + - libi2pd_client/** + - Makefile + - Makefile.linux tags: - '*' jobs: build: + name: Building container for ${{ matrix.platform }} runs-on: ubuntu-latest permissions: packages: write @@ -25,42 +36,44 @@ jobs: ] steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v2 - 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@v2 - 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 container for ${{ matrix.archname }} - uses: docker/build-push-action@v3 - 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: 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 push: + name: Pushing merged manifest runs-on: ubuntu-latest + permissions: packages: write contents: read @@ -68,60 +81,60 @@ jobs: needs: build steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v2 - 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@v2 - 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: 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 - images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true + - name: Create and push latest manifest image to 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 - images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true + - name: Create and push 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: 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 }} - images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true + - name: Create and push release manifest 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 }} - images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true + - name: Create and push 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 1ef24b13..6c4ef5b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,389 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.58.0] - 2025-09-08 +### Added +- Post-quantum end-to-end crypto(ML-KEM-512, ML-KEM-768, ML-KEM-1024) support if openssl >= 3.5.0 +- Datagram2 and Datagram3 +- SAM PING command +- Support boost 1.89 +- Specify light or dark theme for webconsole +- "ssu2.firewalled4" and "ssu2.firewalled6" params to force Firewalled even if network is OK +- Streaming ping through BOB +- Ability to specify bandwidth with "mb" and "gb" suffixes +- "i2p.streaming.maxWindowSize" and "i2p.streaming.dontSign" params +### Changed +- Don't verify streaming SYN packet signature if comes from an ECIESx25519 session +- Non-blocking mode for UDP sockets in UDP tunnels +- Accept "HELLO VERSION" without "MIN" and "MAX" in SAM +- Try to resolve host again in server tunnel if failed before +- Don't call deprecated functions for openssl 3 +- Enable post-quantum crypto by default if supported +- Create unique loopback address from fd00::/8 range for ::1 if explicitly set in server tunnels +- Limit number of outbound streaming packets if the peer can't handle them +- Don't show Network status if ipv6 only +- Reseeds list +### Fixed +- Version in I2CP SetDate message +- Crash when SAM session is getting closed +- Max UDP buffer size for OpenBSD +- Lack of file descriptors for Haiku +- Outgoing stream constantly re-requests LeaseSet if the remote peer has gone away +- Numeric value for "i2p.streaming.answerPings" param +- 'R' and 'U' router caps together if operating through a proxy + +## [2.57.0] - 2025-06-02 +### Added +- Local domain sockets for I2PControl +- "keys=shareddest" tunnel param to run on shared local destination +- HTTP and SOCKS proxy through BOB +- Localization to Hebrew and Hindi +- NTCP2 probing resistance +- Support SAM v1 datagram sessions without port +- OpenIndiana support +### Changed +- Don't request LeaseSet until I2CP destination is ready +- Keep receiving new data from I2PTunnel/SAM socket while previous is being sent to stream +- Insert phony record to inbound tunnel build message with real x25519 ephemeral key +- Set min peer test version to 0.9.62 +- Increase I2NP message expiration timeout in SSU2 +- Cleanup ECIESx25519 new session reply keys on Alice side +- Reduced router profile persist interval to 22 minutes +- SSU2 max padding size to 32 bytes +- Send SSU2 path challenge of 8 bytes. Add datetime and address blocks +- Don't delete trusted routers from netdb +- Disable loss-control in streaming +- Reseeds list +### Fixed +- Crash after SAM stream disconnect +- FORWARD session host handling in SAM +- x86 build for Haiku +- SSU2 session's remote endpoint after receiving path response + +## [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 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 520a56ba..33f3365a 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) @@ -52,7 +51,6 @@ ifneq (, $(DESTDIR)) endif ifneq (, $(findstring darwin, $(SYS))) - DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) include Makefile.homebrew else @@ -62,22 +60,25 @@ else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(find 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 haiku, $(SYS))) + include Makefile.haiku +else ifneq (, $(findstring solaris, $(SYS))) + 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) +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)) @@ -110,13 +111,13 @@ 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) ifneq ($(USE_STATIC),yes) diff --git a/Makefile.bsd b/Makefile.bsd index 39e5651a..9c6de7f0 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_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..9d8d17cd 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_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 d01f2b73..4ea39e22 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -9,24 +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} "[7-9]"),1) # gcc 7 - 9 NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+ -# NEEDED_CXXFLAGS += -std=c++20 +else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9 NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -latomic + 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 @@ -38,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 @@ -50,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 @@ -58,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..a2ad515e 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_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 d0481ed9..3463ed3b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) [![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) +[![Github Downloads](https://img.shields.io/github/downloads/PurpleI2P/i2pd/total.svg)]() [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) *note: i2pd for Android can be found in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository and with Qt GUI in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository* @@ -77,7 +78,7 @@ Build instructions: * 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) +* Android * iOS Using i2pd @@ -99,13 +100,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 affc301a..48f65c27 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -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; @@ -64,7 +64,7 @@ namespace util //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 37458046..0e29c517 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.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 * @@ -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,30 +145,37 @@ 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; default: s << "Unk"; }; - if (i2p::context.GetError () != eRouterErrorNone) + if (testing) + s << " (Test)"; + if (error != eRouterErrorNone) { - switch (i2p::context.GetError ()) + switch (error) { case eRouterErrorClockSkew: - s << " - Clock skew"; + s << " - " << tr("Clock skew"); break; case eRouterErrorOffline: - s << " - Offline"; + s << " - " << tr("Offline"); break; case eRouterErrorSymmetricNAT: - s << " - Symmetric NAT"; + s << " - " << tr("Symmetric NAT"); + break; + case eRouterErrorFullConeNAT: + s << " - " << tr("Full cone NAT"); + break; + case eRouterErrorNoDescriptors: + s << " - " << tr("No Descriptors"); break; default: ; } @@ -178,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"; @@ -305,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; } @@ -347,6 +355,7 @@ namespace win32 } } } + [[fallthrough]]; } case WM_TRAYICON: { @@ -416,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); @@ -446,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; } @@ -466,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 7689cc88..39b8094c 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -2,7 +2,12 @@ /CMakeFiles/ /Testing/ /tests/ +/.ninja_* +/arch.c +/build.ninja /i2pd +/i2pd.exe +/i2pd.exe.debug /libi2pd.a /libi2pdclient.a /libi2pdlang.a @@ -12,7 +17,11 @@ /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 f6f52001..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) @@ -26,27 +43,22 @@ IF(BUILD_TESTING) enable_testing() ENDIF() -# paths -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") -set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") - # Handle paths nicely 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 "") @@ -57,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 "") @@ -74,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 "") @@ -86,6 +97,8 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() +include_directories(${DAEMON_SRC_DIR}) + set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" @@ -95,6 +108,22 @@ set(DAEMON_SRC "${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() @@ -102,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") @@ -155,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") @@ -176,6 +198,10 @@ if(WITH_THREADSANITIZER) endif() endif() +if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 + list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") +endif() + # Use std::atomic instead of GCC builtins on macOS PowerPC: # For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 # This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. @@ -188,10 +214,36 @@ 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") @@ -201,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() @@ -233,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}") @@ -245,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") @@ -275,11 +369,18 @@ 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 ${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}") diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake index d5ec6a0a..4954e3e5 100644 --- a/build/cmake_modules/CheckAtomic.cmake +++ b/build/cmake_modules/CheckAtomic.cmake @@ -8,7 +8,7 @@ INCLUDE(CheckLibraryExists) function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; @@ -25,7 +25,7 @@ endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include diff --git a/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 d59925c8..e611c4f7 100644 --- a/build/cmake_modules/TargetArch.cmake +++ b/build/cmake_modules/TargetArch.cmake @@ -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__) \\ 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/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/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/contrib/debian/trusty/patches/01-upnp.patch b/contrib/debian/trusty/patches/01-upnp.patch new file mode 100644 index 00000000..74d36c06 --- /dev/null +++ b/contrib/debian/trusty/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/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/i18n/English.po b/contrib/i18n/English.po index 22010680..7782187f 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2023-01-19 04:18\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,28 +18,28 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:106 +#: daemon/HTTPServer.cpp:107 #, c-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:110 +#: daemon/HTTPServer.cpp:111 #, c-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:114 +#: daemon/HTTPServer.cpp:115 #, c-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:117 +#: daemon/HTTPServer.cpp:118 #, c-format msgid "%d second" msgid_plural "%d seconds" @@ -47,560 +47,578 @@ msgstr[0] "" msgstr[1] "" #. tr: Kibibyte -#: daemon/HTTPServer.cpp:125 daemon/HTTPServer.cpp:153 +#: daemon/HTTPServer.cpp:126 #, c-format msgid "%.2f KiB" msgstr "" #. tr: Mebibyte -#: daemon/HTTPServer.cpp:127 +#: daemon/HTTPServer.cpp:128 #, c-format msgid "%.2f MiB" msgstr "" #. tr: Gibibyte -#: daemon/HTTPServer.cpp:129 +#: daemon/HTTPServer.cpp:130 #, c-format msgid "%.2f GiB" msgstr "" -#: daemon/HTTPServer.cpp:146 +#: daemon/HTTPServer.cpp:147 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:147 +#: daemon/HTTPServer.cpp:148 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:148 +#: daemon/HTTPServer.cpp:149 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:149 +#: daemon/HTTPServer.cpp:150 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:150 +#: daemon/HTTPServer.cpp:151 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:152 +#: daemon/HTTPServer.cpp:153 msgid "exploratory" msgstr "" #. tr: Webconsole page title -#: daemon/HTTPServer.cpp:183 +#: daemon/HTTPServer.cpp:185 msgid "Purple I2P Webconsole" msgstr "" -#: daemon/HTTPServer.cpp:188 +#: daemon/HTTPServer.cpp:190 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:191 +#: daemon/HTTPServer.cpp:193 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:192 daemon/HTTPServer.cpp:712 +#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:742 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:193 daemon/HTTPServer.cpp:387 -#: daemon/HTTPServer.cpp:399 +#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:395 +#: daemon/HTTPServer.cpp:407 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:357 -#: daemon/HTTPServer.cpp:443 daemon/HTTPServer.cpp:449 -#: daemon/HTTPServer.cpp:609 daemon/HTTPServer.cpp:652 -#: daemon/HTTPServer.cpp:656 +#: 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:197 daemon/HTTPServer.cpp:662 +#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:692 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:364 -#: daemon/HTTPServer.cpp:781 daemon/HTTPServer.cpp:797 +#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:372 +#: daemon/HTTPServer.cpp:813 daemon/HTTPServer.cpp:830 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:855 +#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:898 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:202 +#: daemon/HTTPServer.cpp:204 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:204 daemon/HTTPServer.cpp:884 -#: daemon/HTTPServer.cpp:894 +#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:927 +#: daemon/HTTPServer.cpp:937 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:220 daemon/HTTPServer.cpp:1278 -#: daemon/HTTPServer.cpp:1281 daemon/HTTPServer.cpp:1284 -#: daemon/HTTPServer.cpp:1298 daemon/HTTPServer.cpp:1343 -#: daemon/HTTPServer.cpp:1346 daemon/HTTPServer.cpp:1349 +#: 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 "" -#: daemon/HTTPServer.cpp:227 +#: daemon/HTTPServer.cpp:229 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:228 +#: daemon/HTTPServer.cpp:230 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:229 +#: daemon/HTTPServer.cpp:231 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:230 daemon/HTTPServer.cpp:233 -#: daemon/HTTPServer.cpp:329 +#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:235 +#: daemon/HTTPServer.cpp:336 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:231 daemon/HTTPServer.cpp:374 -#: daemon/HTTPServer.cpp:375 daemon/HTTPServer.cpp:952 -#: daemon/HTTPServer.cpp:961 +#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:382 +#: daemon/HTTPServer.cpp:383 daemon/HTTPServer.cpp:1003 +#: daemon/HTTPServer.cpp:1011 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:232 +#: daemon/HTTPServer.cpp:234 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:240 +#: daemon/HTTPServer.cpp:242 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:243 +#: daemon/HTTPServer.cpp:245 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:246 +#: daemon/HTTPServer.cpp:248 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:249 +#: daemon/HTTPServer.cpp:251 msgid "Full cone NAT" msgstr "" -#: daemon/HTTPServer.cpp:252 +#: daemon/HTTPServer.cpp:254 msgid "No Descriptors" msgstr "" -#: daemon/HTTPServer.cpp:261 +#: daemon/HTTPServer.cpp:263 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:264 +#: daemon/HTTPServer.cpp:266 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:269 +#: daemon/HTTPServer.cpp:271 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:275 daemon/HTTPServer.cpp:282 +#: daemon/HTTPServer.cpp:277 daemon/HTTPServer.cpp:284 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:289 +#: daemon/HTTPServer.cpp:291 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:290 +#: daemon/HTTPServer.cpp:292 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:291 +#: daemon/HTTPServer.cpp:296 +msgid "Total tunnel creation success rate" +msgstr "" + +#: daemon/HTTPServer.cpp:298 msgid "Received" msgstr "" #. tr: Kibibyte/s -#: daemon/HTTPServer.cpp:293 daemon/HTTPServer.cpp:296 -#: daemon/HTTPServer.cpp:299 +#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:303 +#: daemon/HTTPServer.cpp:306 #, c-format msgid "%.2f KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:294 +#: daemon/HTTPServer.cpp:301 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:297 +#: daemon/HTTPServer.cpp:304 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:300 +#: daemon/HTTPServer.cpp:307 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:303 +#: daemon/HTTPServer.cpp:310 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:307 +#: daemon/HTTPServer.cpp:314 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:309 +#: daemon/HTTPServer.cpp:316 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:310 +#: daemon/HTTPServer.cpp:317 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:311 +#: daemon/HTTPServer.cpp:318 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:312 +#: daemon/HTTPServer.cpp:319 msgid "Our external address" msgstr "" #. tr: Shown when router doesn't publish itself and have "Firewalled" state -#: daemon/HTTPServer.cpp:341 +#: daemon/HTTPServer.cpp:349 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:355 +#: daemon/HTTPServer.cpp:363 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:356 +#: daemon/HTTPServer.cpp:364 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:363 daemon/HTTPServer.cpp:938 +#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:987 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:373 +#: daemon/HTTPServer.cpp:381 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:374 daemon/HTTPServer.cpp:375 -#: daemon/HTTPServer.cpp:376 daemon/HTTPServer.cpp:377 -#: daemon/HTTPServer.cpp:378 daemon/HTTPServer.cpp:379 +#: 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:374 daemon/HTTPServer.cpp:375 -#: daemon/HTTPServer.cpp:376 daemon/HTTPServer.cpp:377 -#: daemon/HTTPServer.cpp:378 daemon/HTTPServer.cpp:379 +#: 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:422 +#: daemon/HTTPServer.cpp:434 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:431 +#: daemon/HTTPServer.cpp:442 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:436 +#: daemon/HTTPServer.cpp:447 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:437 +#: daemon/HTTPServer.cpp:448 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:438 +#: 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:444 +#: daemon/HTTPServer.cpp:457 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:444 +#: daemon/HTTPServer.cpp:459 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:444 +#: daemon/HTTPServer.cpp:460 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:454 daemon/HTTPServer.cpp:667 +#: 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:469 daemon/HTTPServer.cpp:489 -#: daemon/HTTPServer.cpp:681 daemon/HTTPServer.cpp:701 +#: daemon/HTTPServer.cpp:494 daemon/HTTPServer.cpp:514 +#: daemon/HTTPServer.cpp:711 daemon/HTTPServer.cpp:731 #, c-format msgid "%dms" msgstr "" -#: daemon/HTTPServer.cpp:474 daemon/HTTPServer.cpp:686 +#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:716 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:496 +#: daemon/HTTPServer.cpp:521 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:497 +#: daemon/HTTPServer.cpp:522 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 +#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:535 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:507 daemon/HTTPServer.cpp:526 +#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:551 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:507 +#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:814 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:515 +#: daemon/HTTPServer.cpp:540 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:523 daemon/HTTPServer.cpp:529 +#: daemon/HTTPServer.cpp:548 daemon/HTTPServer.cpp:554 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:526 +#: daemon/HTTPServer.cpp:551 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:536 daemon/HTTPServer.cpp:594 +#: daemon/HTTPServer.cpp:561 daemon/HTTPServer.cpp:621 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:547 daemon/HTTPServer.cpp:917 +#: daemon/HTTPServer.cpp:572 daemon/HTTPServer.cpp:960 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:570 +#: daemon/HTTPServer.cpp:595 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:599 +#: 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:602 +#: daemon/HTTPServer.cpp:629 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:628 +#: daemon/HTTPServer.cpp:658 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:631 +#: daemon/HTTPServer.cpp:661 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:632 +#: daemon/HTTPServer.cpp:662 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:637 +#: daemon/HTTPServer.cpp:667 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:640 +#: daemon/HTTPServer.cpp:670 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:641 +#: daemon/HTTPServer.cpp:671 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:642 +#: daemon/HTTPServer.cpp:672 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:652 +#: daemon/HTTPServer.cpp:682 msgid "floodfill mode is disabled" msgstr "" -#: daemon/HTTPServer.cpp:663 +#: daemon/HTTPServer.cpp:693 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:713 +#: daemon/HTTPServer.cpp:743 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:714 +#: daemon/HTTPServer.cpp:744 msgid "Reload tunnels configuration" msgstr "" -#: daemon/HTTPServer.cpp:717 +#: daemon/HTTPServer.cpp:747 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:719 +#: daemon/HTTPServer.cpp:749 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:723 daemon/HTTPServer.cpp:728 +#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:758 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:725 daemon/HTTPServer.cpp:730 +#: daemon/HTTPServer.cpp:755 daemon/HTTPServer.cpp:760 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:733 +#: daemon/HTTPServer.cpp:763 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:734 +#: daemon/HTTPServer.cpp:764 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:737 +#: daemon/HTTPServer.cpp:767 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:739 +#: daemon/HTTPServer.cpp:770 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:747 +#: daemon/HTTPServer.cpp:779 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:752 daemon/HTTPServer.cpp:771 +#: daemon/HTTPServer.cpp:784 daemon/HTTPServer.cpp:803 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:759 +#: daemon/HTTPServer.cpp:791 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:797 +#: daemon/HTTPServer.cpp:830 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:878 daemon/HTTPServer.cpp:901 +#: daemon/HTTPServer.cpp:921 daemon/HTTPServer.cpp:944 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:894 +#: daemon/HTTPServer.cpp:937 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:907 +#: daemon/HTTPServer.cpp:950 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:912 +#: daemon/HTTPServer.cpp:955 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:969 +#: daemon/HTTPServer.cpp:1020 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:985 +#: daemon/HTTPServer.cpp:1036 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:999 +#: daemon/HTTPServer.cpp:1050 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1199 +#: daemon/HTTPServer.cpp:1250 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1218 +#: daemon/HTTPServer.cpp:1269 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1276 daemon/HTTPServer.cpp:1333 -#: daemon/HTTPServer.cpp:1373 +#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1359 +#: daemon/HTTPServer.cpp:1414 daemon/HTTPServer.cpp:1454 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1276 +#: daemon/HTTPServer.cpp:1327 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1278 +#: daemon/HTTPServer.cpp:1329 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1281 +#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1365 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1284 +#: daemon/HTTPServer.cpp:1335 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1286 daemon/HTTPServer.cpp:1351 +#: daemon/HTTPServer.cpp:1337 daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1432 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1287 daemon/HTTPServer.cpp:1300 -#: daemon/HTTPServer.cpp:1375 +#: 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:1298 +#: 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:1299 daemon/HTTPServer.cpp:1374 +#: daemon/HTTPServer.cpp:1380 daemon/HTTPServer.cpp:1455 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1335 +#: daemon/HTTPServer.cpp:1416 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1336 +#: daemon/HTTPServer.cpp:1417 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1336 +#: daemon/HTTPServer.cpp:1417 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1337 +#: daemon/HTTPServer.cpp:1418 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1343 +#: daemon/HTTPServer.cpp:1424 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1346 +#: daemon/HTTPServer.cpp:1427 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1349 -msgid "Such destination is not found" -msgstr "" - -#: daemon/HTTPServer.cpp:1369 +#: daemon/HTTPServer.cpp:1450 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1373 +#: daemon/HTTPServer.cpp:1454 msgid "Command accepted" msgstr "" @@ -624,20 +642,20 @@ msgstr "" msgid "You may try to find this host on jump services below" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:309 libi2pd_client/HTTPProxy.cpp:324 -#: libi2pd_client/HTTPProxy.cpp:392 libi2pd_client/HTTPProxy.cpp:435 +#: 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:309 +#: libi2pd_client/HTTPProxy.cpp:333 msgid "Proxy unable to parse your request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:324 +#: libi2pd_client/HTTPProxy.cpp:348 msgid "Addresshelper is not supported" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:349 +#: libi2pd_client/HTTPProxy.cpp:373 #, c-format msgid "" "Host %s is already in router's addressbook. Be " @@ -645,121 +663,121 @@ msgid "" "Continue." msgstr "" -#: libi2pd_client/HTTPProxy.cpp:351 +#: libi2pd_client/HTTPProxy.cpp:375 msgid "Addresshelper forced update rejected" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:358 +#: libi2pd_client/HTTPProxy.cpp:382 #, c-format msgid "" -"To add host %s in router's addressbook, click here: Continue." +"To add host %s in router's addressbook, click here: Continue." msgstr "" -#: libi2pd_client/HTTPProxy.cpp:360 +#: libi2pd_client/HTTPProxy.cpp:384 msgid "Addresshelper request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:369 +#: 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:370 +#: libi2pd_client/HTTPProxy.cpp:395 msgid "Addresshelper adding" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:377 +#: 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:379 +#: libi2pd_client/HTTPProxy.cpp:404 msgid "Addresshelper update" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:392 +#: libi2pd_client/HTTPProxy.cpp:417 msgid "Invalid request URI" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:435 +#: libi2pd_client/HTTPProxy.cpp:460 msgid "Can't detect destination host from request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:452 libi2pd_client/HTTPProxy.cpp:456 +#: libi2pd_client/HTTPProxy.cpp:477 libi2pd_client/HTTPProxy.cpp:481 msgid "Outproxy failure" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:452 +#: libi2pd_client/HTTPProxy.cpp:477 msgid "Bad outproxy settings" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:455 +#: 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:544 +#: libi2pd_client/HTTPProxy.cpp:569 msgid "Unknown outproxy URL" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:550 +#: libi2pd_client/HTTPProxy.cpp:575 msgid "Cannot resolve upstream proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:558 +#: libi2pd_client/HTTPProxy.cpp:583 msgid "Hostname is too long" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:585 +#: libi2pd_client/HTTPProxy.cpp:610 msgid "Cannot connect to upstream SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:591 +#: libi2pd_client/HTTPProxy.cpp:616 msgid "Cannot negotiate with SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:633 +#: libi2pd_client/HTTPProxy.cpp:658 msgid "CONNECT error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:633 +#: libi2pd_client/HTTPProxy.cpp:658 msgid "Failed to connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:644 libi2pd_client/HTTPProxy.cpp:670 +#: libi2pd_client/HTTPProxy.cpp:669 libi2pd_client/HTTPProxy.cpp:695 msgid "SOCKS proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:652 +#: libi2pd_client/HTTPProxy.cpp:677 msgid "Failed to send request to upstream" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:673 +#: libi2pd_client/HTTPProxy.cpp:698 msgid "No reply from SOCKS proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:680 +#: libi2pd_client/HTTPProxy.cpp:705 msgid "Cannot connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:680 +#: libi2pd_client/HTTPProxy.cpp:705 msgid "HTTP out proxy not implemented" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:681 +#: libi2pd_client/HTTPProxy.cpp:706 msgid "Cannot connect to upstream HTTP proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:714 +#: libi2pd_client/HTTPProxy.cpp:739 msgid "Host is down" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:714 +#: 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 56d76c5a..ce775ecb 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -2,12 +2,17 @@ --- ``` -xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +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 @@ -18,10 +23,6 @@ in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n out: {"$1", "$2"},\n ``` -``` -in: ^#[:.,](.*)$\n -out: -``` ``` in: \n\n diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index c8baa046..1cc357e2 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,58 +71,60 @@ ## 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 -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 +## Enable SSU2 transport (default: true) # enabled = true -## Publish address in RouterInfo +## Publish address in RouterInfo (default: true) # published = true -## Port for incoming connections (default is global port option value or port + 1 if SSU is enabled) +## 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 @@ -129,16 +132,19 @@ port = 7070 # pass = changeme ## Select webconsole language ## Currently supported english (default), afrikaans, armenian, chinese, czech, french, -## german, italian, russian, spanish, turkmen, ukrainian and uzbek languages +## 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: transient-proxy) +## "Transient" means it changes on every new app launch +# If you want not change address then set like 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, @@ -149,15 +155,17 @@ port = 4444 ## 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: transient-proxy) +# "Transient" means it changes on every new app launch +# If you don't want the address to change, set a file name like 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 SOCKS outproxy (works only with SOCKS4, default: false) # outproxy.enabled = false ## Address and port of outproxy # outproxy = 127.0.0.1 @@ -165,33 +173,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] @@ -202,11 +211,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 @@ -214,13 +223,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 @@ -230,7 +239,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] @@ -238,25 +247,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: 5000) +## Maximum active transit sessions (default: 10000) ## This value is doubled if floodfill mode is enabled! -# transittunnels = 5000 +# transittunnels = 10000 ## 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] @@ -272,11 +281,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 79bf15c8..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 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 e902d1fd..23daddf6 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.46.0 +Version: 2.58.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -10,11 +10,7 @@ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz -%if 0%{?rhel} == 7 -BuildRequires: cmake3 -%else BuildRequires: cmake -%endif BuildRequires: chrpath BuildRequires: gcc-c++ @@ -24,90 +20,72 @@ 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 %build cd build -%if 0%{?rhel} == 7 -%cmake3 \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ - -DBUILD_SHARED_LIBS:BOOL=OFF -%else %cmake \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ %if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . + -DBUILD_SHARED_LIBS:BOOL=OFF \ + . %else - -DBUILD_SHARED_LIBS:BOOL=OFF -%endif + -DBUILD_SHARED_LIBS:BOOL=OFF %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 +121,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 +136,54 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Sep 08 2025 orignal - 2.58.0 +- update to 2.58.0 + +* Mon Jun 02 2025 orignal - 2.57.0 +- update to 2.57.0 + +* 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 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 5ddd8727..c2b7c923 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.46.0 +Version: 2.58.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -8,11 +8,7 @@ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz -%if 0%{?rhel} == 7 -BuildRequires: cmake3 -%else BuildRequires: cmake -%endif BuildRequires: chrpath BuildRequires: gcc-c++ @@ -22,89 +18,72 @@ 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 %build cd build -%if 0%{?rhel} == 7 -%cmake3 \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ - -DBUILD_SHARED_LIBS:BOOL=OFF -%else %cmake \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ %if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . + -DBUILD_SHARED_LIBS:BOOL=OFF \ + . %else - -DBUILD_SHARED_LIBS:BOOL=OFF -%endif + -DBUILD_SHARED_LIBS:BOOL=OFF %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 +119,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 +134,54 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Sep 08 2025 orignal - 2.58.0 +- update to 2.58.0 + +* Mon Jun 02 2025 orignal - 2.57.0 +- update to 2.57.0 + +* 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 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/hacker.css b/contrib/webconsole/hacker.css new file mode 100644 index 00000000..5d93be50 --- /dev/null +++ b/contrib/webconsole/hacker.css @@ -0,0 +1,174 @@ +/* + * 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 + * + ****************************************************************** + * + * 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: #0b0b0b; + --main-text-color: #00ff99; + --main-link-color: #ff00ff; + --main-link-hover-color: #00ffff; +} + +body { + font: 100%/1.5em "Courier New", Courier, monospace; + 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); +} +.slidecontent { + display:none; +} +#slide-info { + display:none; +} +.enabled { + color: green; + text-shadow: 2px green; +} +.disabled { + color: red; +} +#slide-info:checked ~ .slidecontent { + display: block; +} +#slide_ntcp2 { + display: none; +} +#slide_ssu2 { + display: none; +} +#slide_ssu2:checked ~ .slidecontent { + display: block; +} +#slide_ntcp2:checked ~ .slidecontent{ + display:block; +} +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: var(--main-link-color); + text-shadow: 0 0 5px #ff00ff, 0 0 10px #ff00ff; +} + +.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, .tableitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; + color: #00ff99; +} + +.content { +/* float: left;*/ + font-size: 1em; + margin-left: 2em; + padding: 4px; + max-width: 50em; + overflow: auto; + left: 35%; + position: absolute; +} + +.tunnel.established { + color: #0ff; +} + +.tunnel.expiring { + color: #ff0; +} + +.tunnel.failed { + color: #f00; +} + +.tunnel.building { + color: #888; +} + +caption { + font-size: 1.5em; + text-align: center; + color: var(--main-link-color); + text-shadow: 0 0 3px #ff00ff; +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +textarea, input, select { + background-color: #111; + color: var(--main-text-color); + border: 1px solid var(--main-link-color); + font-family: monospace; + font-size: 14px; + padding: 5px; + border-radius: 3px; +} + +button[type=submit], a.button { + background-color: transparent; + color: var(--main-link-color); + border: 1px solid var(--main-link-color); + padding: 5px 10px; + font-family: monospace; + border-radius: 4px; + cursor: pointer; + text-decoration: none; +} + +button[type=submit]:hover, a.button:hover { + color: var(--main-link-hover-color); + background-color: #222; + box-shadow: 0 0 5px var(--main-link-hover-color); +} + +@media screen and (max-width: 980px) { + .menu, .content, input, select, textarea, button[type=submit], a.button { + width: 90%; + margin: 0 auto 10px auto; + display: block; + text-align: center; + } +} diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css index 89021539..2f399fea 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, The PurpleI2P Project + * Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -92,12 +92,14 @@ a.button { } .content { - float: left; +/* float: left;*/ font-size: 1em; margin-left: 2em; padding: 4px; max-width: 50em; overflow: auto; + left: 35%; + position: absolute; } .tunnel.established { @@ -230,6 +232,8 @@ input[type=number]::-webkit-inner-spin-button { max-width: 100%; width: 100%; text-align: center; + position: absolute; + left: 0; } a, .slide label { diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index a0d7cf1a..57cbcef6 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,6 +8,7 @@ #include #include +#include #include "Daemon.h" @@ -117,8 +118,8 @@ namespace util if (logclftime) i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); -#ifdef WIN32_APP - // Win32 app with GUI supports only logging to file +#if defined(WIN32_APP) || defined(__HAIKU__) + // Win32 app with GUI or Haiku supports only logging to file logs = "file"; #else if (isDaemon && (logs == "" || logs == "stdout")) @@ -149,18 +150,19 @@ 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 (); i2p::transport::InitTransports (); @@ -176,21 +178,50 @@ namespace util bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); - uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + 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 - SetMaxNumTransitTunnels (transitTunnels); + 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') + const auto NumBandwithRegex = std::regex(R"(^\d+$)"); + const auto BandwithRegex = std::regex(R"((\d+)(b|kb|mb|gb))"); + std::smatch bandWithMatch; + + 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"); } - else + else if (std::regex_match(bandwidth, bandWithMatch, BandwithRegex)) { + const auto number = bandWithMatch[1].str(); + const auto unit = bandWithMatch[2].str(); + int limit = std::atoi(number.c_str()); + std::cout << unit; + if (unit == "b") + { + limit /= 1000; + } + else if(unit == "mb") + { + limit *= 1000; + } else if(unit == "gb") + { + limit *= 1000000; + } + // if limit more than 32 bits then its will be negative + if (limit < 0) + { + LogPrint(eLogInfo, "Daemon: Unexpected bandwidth ", bandwidth, ". Set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + } else { + i2p::context.SetBandwidth(limit); + } + } + else if(std::regex_search(bandwidth, NumBandwithRegex)) { auto value = std::atoi(bandwidth.c_str()); if (value > 0) @@ -203,7 +234,7 @@ namespace util LogPrint(eLogInfo, "Daemon: Unexpected bandwidth ", bandwidth, ". Set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } - } + } } else if (isFloodfill) { @@ -269,7 +300,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); @@ -299,18 +330,16 @@ namespace util bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); LogPrint(eLogInfo, "Daemon: Starting Transports"); if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled"); if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled"); - i2p::transport::transports.SetCheckReserved(checkInReserved); 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(); @@ -329,15 +358,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 (); @@ -354,7 +385,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 ()); } } @@ -366,6 +397,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/Daemon.h b/daemon/Daemon.h index 26d4a047..6f428ce4 100644 --- a/daemon/Daemon.h +++ b/daemon/Daemon.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 * @@ -97,15 +97,14 @@ namespace util return instance; } }; -#else -#define Daemon i2p::util::DaemonLinux::Instance() - class DaemonLinux : public Daemon_Singleton +#else // Unix-like systems, including Linux + class DaemonUnix : public Daemon_Singleton { public: - static DaemonLinux& Instance() + static DaemonUnix& Instance() { - static DaemonLinux instance; + static DaemonUnix instance; return instance; } @@ -122,6 +121,24 @@ namespace util int gracefulShutdownInterval; // in seconds }; +#if !defined(__HAIKU__) + #define Daemon i2p::util::DaemonUnix::Instance() +#else + class DaemonHaiku: public DaemonUnix + { + public: + + static DaemonHaiku& Instance () + { + static DaemonHaiku instance; + return instance; + } + + void run (); + }; +#define Daemon i2p::util::DaemonHaiku::Instance() +#endif + #endif } } diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index f78057b3..bb2f44db 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -42,10 +43,11 @@ namespace i2p { namespace http { - static void LoadExtCSS () + + static void LoadExtCSS (std::string fileName = "style") { std::stringstream s; - std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); + std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css"); if (i2p::fs::Exists(styleFile)) { std::ifstream f(styleFile, std::ifstream::binary); s << f.rdbuf(); @@ -87,6 +89,7 @@ namespace http { const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; + const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; static std::string ConvertTime (uint64_t time) { @@ -131,32 +134,29 @@ namespace http { 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 (stateText.empty ()) stateText = tr(state); - 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 << " " << 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"); @@ -168,21 +168,46 @@ namespace http { static void ShowPageHead (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string theme; i2p::config::GetOption("http.theme", theme); + + const auto isThemeRegex = std::regex("^(white|black|light)"); + if (!std::regex_search(theme, isThemeRegex)) + { + LoadExtCSS(theme); + } + else + { + LoadExtCSS(); + } // Page language std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language auto it = i2p::i18n::languages.find(currLang); std::string langCode = it->second.ShortCode; + // Right to Left language option + bool rtl = i2p::client::context.GetLanguage ()->GetRTL(); + s << "\r\n" - "\r\n" + "\r\n" " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ " \r\n" " \r\n" " \r\n" " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; GetStyles(s); + if (theme == "black") + { + s << + ""; + + } s << "\r\n" "\r\n" @@ -216,23 +241,24 @@ 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; default: s << tr("Unknown"); } + if (testing) + s << " (" << tr("Testing") << ")"; if (error != eRouterErrorNone) { switch (error) @@ -262,13 +288,16 @@ namespace http { s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; - s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetError ()); - s << "
\r\n"; + if (i2p::context.SupportsV4 () || i2p::context.GetStatus () != eRouterStatusUnknown) // don't show Unknown for ipv6-only + { + s << "" << tr("Network status") << ": "; + 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)) @@ -419,10 +448,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 ()); @@ -434,12 +476,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"; @@ -448,9 +489,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"; @@ -467,7 +522,9 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; } ); } @@ -488,7 +545,9 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; } ); } @@ -591,6 +650,8 @@ namespace http { } s << "\r\n"; } + else + ShowError(s, tr("Such destination is not found")); } void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) @@ -625,7 +686,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(), nullptr, false); + } if (!ls) return; s << "
IsExpired()) @@ -668,6 +732,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 (); @@ -679,7 +744,9 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; } ); } @@ -700,7 +767,9 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; } ); } @@ -745,13 +814,14 @@ namespace http { 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"; @@ -787,7 +857,7 @@ namespace http { if (i2p::tunnel::tunnels.CountTransitTunnels()) { s << "" << tr("Transit Tunnels") << ":
\r\n"; - s << ""; + s << "
ID" << tr("Amount") << "
"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (std::dynamic_pointer_cast(it)) @@ -797,7 +867,7 @@ namespace http { else s << "\r\n"; + s << "\r\n"; } s << "
ID" << tr("Amount") << "" << tr("Next") << "
" << it->GetTunnelID () << ""; ShowTraffic(s, it->GetNumTransmittedBytes ()); - s << "
" << it->GetNextPeerName () << "
\r\n"; } @@ -835,6 +905,7 @@ namespace http { 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++; } @@ -903,7 +974,8 @@ namespace http { for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); - s << "\r\n"; @@ -915,13 +987,26 @@ namespace http { void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) { + if (!sam) + { ShowError(s, tr("SAM disabled")); return; } - - auto session = sam->FindSession (id); - if (!session) { + if (id.empty ()) + { + ShowError(s, tr("No sam_id")); + return; + } + std::vector sam_id(id.length ()); // id is in base64 + size_t l = i2p::data::Base64ToByteStream (id, sam_id.data (), sam_id.size ()); + if (!l) + { + ShowError(s, tr("Invalid sam_id")); + return; + } + auto session = sam->FindSession ( { (const char *)sam_id.data (), l }); + if (!session) + { ShowError(s, tr("SAM session not found")); return; } @@ -933,18 +1018,19 @@ namespace http { s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "\r\n"; s << "
\r\n"; s << "" << tr("Streams") << ":
\r\n
\r\n"; - for (const auto& it: sam->ListSockets(id)) + for (const auto& it: sam->ListSockets({ (const char *)sam_id.data (), l })) { s << "
"; switch (it->GetSocketType ()) { - case i2p::client::eSAMSocketTypeSession : s << "session"; break; - case i2p::client::eSAMSocketTypeStream : s << "stream"; break; - case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; - case i2p::client::eSAMSocketTypeForward : s << "forward"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeSession : s << "session"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeStream : s << "stream"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeAcceptor : s << "acceptor"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } - s << " [" << it->GetSocket ().remote_endpoint() << "]"; + if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ()) + s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
\r\n"; } s << "
\r\n"; @@ -953,34 +1039,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 ()) { @@ -1214,7 +1308,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; } } @@ -1307,11 +1401,41 @@ namespace http { 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 <= TRANSIT_TUNNELS_LIMIT) - SetMaxNumTransitTunnels (limit); + i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); else { 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"; @@ -1340,13 +1464,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" @@ -1355,7 +1477,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"; @@ -1379,12 +1500,15 @@ namespace http { } else if (cmd == HTTP_COMMAND_RELOAD_CSS) { - LoadExtCSS(); + std::string theme; i2p::config::GetOption("http.theme", theme); + + if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme); + else LoadExtCSS(); } else { res.code = 400; - ShowError(s, tr("Unknown command") + ": " + cmd); + ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO return; } @@ -1403,13 +1527,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 f41d925d..38b790d4 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -25,7 +25,7 @@ namespace http const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds - const int TRANSIT_TUNNELS_LIMIT = 65535; + const int TRANSIT_TUNNELS_LIMIT = 1000000; class HTTPConnection: public std::enable_shared_from_this { @@ -83,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 1e5b6f75..2772fb69 100644 --- a/daemon/HTTPServerResources.h +++ b/daemon/HTTPServerResources.h @@ -45,7 +45,7 @@ namespace http ".menu { display: block; float: left; overflow: hidden; padding: 4px; max-width: 12em; white-space: nowrap; text-overflow: ellipsis ;}\r\n" ".listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" ".tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" - ".content { float: left; font-size: 1em; margin-left: 2em; padding: 4px; max-width: 50em; overflow: auto; }\r\n" + ".content { font-size: 1em; margin-left: 2em; padding: 4px; max-width: 50em; overflow: auto; position: absolute; left: 35%; }\r\n" ".tunnel.established { color: #56B734; }\r\n" ".tunnel.expiring { color: #D3AE3F; }\r\n" ".tunnel.failed { color: #D33F3F; }\r\n" @@ -73,7 +73,7 @@ namespace http "@media screen and (max-width: 980px) { body { font: 100%/1.2em sans-serif; padding: 1.2em 0 0 0; }\r\n" " .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px; text-align: center; }\r\n" " .menu a, .commands a { display: inline-block; padding: 4px; }\r\n" - " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%; text-align: center; }\r\n" + " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%; text-align: center; position: absolute; left: 0; }\r\n" " a, .slide label { display: block; }\r\n" " .header { margin: unset; font-size: 1.5em; }\r\n" " small { display: block; }\r\n" diff --git a/daemon/HaikuDaemon.cpp b/daemon/HaikuDaemon.cpp new file mode 100644 index 00000000..0a8b8a31 --- /dev/null +++ b/daemon/HaikuDaemon.cpp @@ -0,0 +1,67 @@ +/* +* 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 +*/ + +#if defined(__HAIKU__) + +#include "Daemon.h" + +#include +#include +#include +#include + +class MainWindow: public BWindow +{ + public: + MainWindow (); +}; + +class I2PApp: public BApplication +{ + public: + I2PApp (); +}; + +MainWindow::MainWindow (): + BWindow (BRect(100, 100, 500, 400), "i2pd", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE) +{ + auto r = Bounds (); r.bottom = 20; + auto menuBar = new BMenuBar (r, "menubar"); + AddChild (menuBar); + auto runMenu = new BMenu ("Run"); + runMenu->AddItem (new BMenuItem ("Quit", new BMessage (B_QUIT_REQUESTED), 'Q')); + menuBar->AddItem (runMenu); +} + +I2PApp::I2PApp (): BApplication("application/x-vnd.purplei2p-i2pd") +{ + auto mainWindow = new MainWindow (); + mainWindow->Show (); +} + +namespace i2p +{ +namespace util +{ + void DaemonHaiku::run () + { + if (isDaemon) + DaemonUnix::run (); + else + { + I2PApp app; + app.Run (); + } + } +} +} + +#endif + + + diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index da2443fd..9babce93 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,16 +8,13 @@ #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 "FS.h" #include "Log.h" @@ -32,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 @@ -47,15 +57,29 @@ 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; @@ -86,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)); } } @@ -95,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; } } @@ -122,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(); @@ -165,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) @@ -246,6 +300,7 @@ namespace client } } + template void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -255,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); @@ -268,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 @@ -338,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); @@ -386,48 +437,54 @@ namespace client void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { FILE *f = NULL; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + EVP_PKEY * pkey = EVP_RSA_gen(4096); // e = 65537 +#else EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); RSA_generate_key_ex (rsa, 4096, e, NULL); - BN_free (e); - if (rsa) - { - EVP_PKEY_assign_RSA (pkey, rsa); - X509 * x509 = X509_new (); - ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); - X509_gmtime_adj (X509_getm_notBefore (x509), 0); - X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration - X509_set_pubkey (x509, pkey); // public key - X509_NAME * name = X509_get_subject_name (x509); - X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) - 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 - - // save cert - if ((f = fopen (crt_path, "wb")) != NULL) { - LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); - PEM_write_X509 (f, x509); - fclose (f); - } else { - LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno)); - } - - // save key - if ((f = fopen (key_path, "wb")) != NULL) { - LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); - PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); - fclose (f); - } else { - LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); - } - - X509_free (x509); - } else { + BN_free (e); + if (rsa) EVP_PKEY_assign_RSA (pkey, rsa); + else + { LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); + EVP_PKEY_free (pkey); + return; + } +#endif + X509 * x509 = X509_new (); + ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); + X509_gmtime_adj (X509_getm_notBefore (x509), 0); + X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration + X509_set_pubkey (x509, pkey); // public key + X509_NAME * name = X509_get_subject_name (x509); + X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) + 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, last param must be NULL for EdDSA + + // save cert + if ((f = fopen (crt_path, "wb")) != NULL) + { + LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); + PEM_write_X509 (f, x509); + fclose (f); + } + else + LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno)); + X509_free (x509); + + // save key + if ((f = fopen (key_path, "wb")) != NULL) + { + LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); + PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); + fclose (f); } + else + LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); EVP_PKEY_free (pkey); } } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index af152631..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 * @@ -35,8 +35,6 @@ namespace client class I2PControlService: public I2PControlHandlers { - typedef boost::asio::ssl::stream ssl_socket; - public: I2PControlService (const std::string& address, int port); @@ -49,16 +47,18 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + template + void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr newSocket); + template void Handshake (std::shared_ptr socket); - void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); + template void ReadRequest (std::shared_ptr socket); + template void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + template void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); - void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); @@ -86,10 +86,13 @@ namespace client std::string m_Password; bool m_IsRunning; - std::thread * m_Thread; + std::unique_ptr m_Thread; - boost::asio::io_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; diff --git a/daemon/I2PControlHandlers.cpp b/daemon/I2PControlHandlers.cpp index f3ea7f61..c94887a3 100644 --- a/daemon/I2PControlHandlers.cpp +++ b/daemon/I2PControlHandlers.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 * @@ -340,7 +340,7 @@ namespace client for (const auto& socket: sam->ListSockets(it.first)) { boost::property_tree::ptree stream; - stream.put("type", socket->GetSocketType ()); + stream.put("type", (int)socket->GetSocketType ()); stream.put("peer", socket->GetSocket ().remote_endpoint()); sam_session_sockets.push_back(std::make_pair("", stream)); diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index dbaf864a..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 (); } @@ -166,11 +178,11 @@ namespace transport 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 (); }); } 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..0c182afe 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.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 * @@ -25,6 +25,7 @@ #include "RouterContext.h" #include "ClientContext.h" #include "Transports.h" +#include "util.h" void handle_signal(int sig) { @@ -70,7 +71,7 @@ namespace i2p { namespace util { - bool DaemonLinux::start() + bool DaemonUnix::start() { if (isDaemon) { @@ -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; } @@ -203,14 +213,15 @@ namespace i2p return Daemon_Singleton::start(); } - bool DaemonLinux::stop() + bool DaemonUnix::stop() { i2p::fs::Remove(pidfile); return Daemon_Singleton::stop(); } - void DaemonLinux::run () + void DaemonUnix::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 91d8f1dc..2267246d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,106 @@ +i2pd (2.58.0-1) unstable; urgency=medium + + * updated to version 2.58.0/0.9.67 + + -- orignal Mon, 08 Sep 2025 16:00:00 +0000 + +i2pd (2.57.0-1) unstable; urgency=medium + + * updated to version 2.57.0/0.9.66 + + -- orignal Mon, 02 Jun 2025 16:00:00 +0000 + +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 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.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 93eee7a1..bde52854 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,6 +1,6 @@ -i2pd usr/sbin/ +i2pd usr/bin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf 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/patches/01-upnp.patch b/debian/patches/01-upnp.patch index bec8f2b0..74d36c06 100644 --- a/debian/patches/01-upnp.patch +++ b/debian/patches/01-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2022-03-23 +Last-Update: 2024-12-30 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ include filelist.mk +@@ -31,7 +31,7 @@ # import source files lists + include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index b582a06a..0afa086f 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,10 @@ namespace afrikaans // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"failed", "Het misluk"}, {"unknown", "onbekend"}, @@ -73,7 +76,7 @@ namespace afrikaans // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp index b99e5032..af22d0d9 100644 --- a/i18n/Armenian.cpp +++ b/i18n/Armenian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace armenian // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f ԿիԲ"}, {"%.2f MiB", "%.2f ՄիԲ"}, @@ -196,7 +199,7 @@ namespace armenian // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp index cd38fa0f..f439bb67 100644 --- a/i18n/Chinese.cpp +++ b/i18n/Chinese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace chinese // language namespace return 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -64,11 +67,12 @@ namespace chinese // language namespace {"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", "已接收"}, {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "已发送"}, @@ -95,6 +99,7 @@ namespace chinese // language namespace {"Address", "地址"}, {"Type", "类型"}, {"EncType", "加密类型"}, + {"Expire LeaseSet", "到期租约集"}, {"Inbound tunnels", "入站隧道"}, {"%dms", "%dms"}, {"Outbound tunnels", "出站隧道"}, @@ -109,6 +114,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", "无效"}, @@ -150,6 +156,8 @@ namespace chinese // language namespace {"StreamID can't be null", "StreamID 不能为空"}, {"Return to destination page", "返回目标页面"}, {"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 注册域名"}, @@ -158,7 +166,6 @@ 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", "代理错误"}, @@ -211,7 +218,7 @@ namespace chinese // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Czech.cpp b/i18n/Czech.cpp index 09a56d50..c9697f41 100644 --- a/i18n/Czech.cpp +++ b/i18n/Czech.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,25 +29,28 @@ namespace czech // language namespace return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, {"%.2f GiB", "%.2f GiB"}, {"building", "vytváří se"}, {"failed", "selhalo"}, - {"expiring", "končící"}, + {"expiring", "vyprší platnost"}, {"established", "vytvořeno"}, {"unknown", "neznámý"}, {"exploratory", "průzkumné"}, - {"Purple I2P Webconsole", "Purple I2P Webkonsole"}, - {"i2pd webconsole", "i2pd webkonsole"}, + {"Purple I2P Webconsole", "Purple I2P webová konzole"}, + {"i2pd webconsole", "i2pd webová konzole"}, {"Main page", "Hlavní stránka"}, {"Router commands", "Router příkazy"}, - {"Local Destinations", "Lokální destinace"}, - {"LeaseSets", "LeaseSety"}, + {"Local Destinations", "Místní cíle"}, + {"LeaseSets", "Sety pronájmu"}, {"Tunnels", "Tunely"}, - {"Transit Tunnels", "Transitní tunely"}, + {"Transit Tunnels", "Tranzitní tunely"}, {"Transports", "Transporty"}, {"I2P tunnels", "I2P tunely"}, {"SAM sessions", "SAM relace"}, @@ -61,18 +64,21 @@ namespace czech // language namespace {"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", "Status sítě"}, - {"Network status v6", "Status sítě v6"}, + {"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 data souborům"}, - {"Hidden content. Press on text to see.", "Skrytý kontent. Pro zobrazení, klikni na text."}, + {"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ů"}, @@ -93,6 +99,7 @@ namespace czech // language namespace {"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"}, @@ -103,21 +110,24 @@ namespace czech // language namespace {"Amount", "Množství"}, {"Incoming Tags", "Příchozí štítky"}, {"Tags sessions", "Relace štítků"}, - {"Status", "Status"}, - {"Local Destination", "Lokální Destinace"}, + {"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", "Nevypršené Leasy"}, + {"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í"}, @@ -145,14 +155,17 @@ namespace czech // language namespace {"Destination not found", "Destinace nenalezena"}, {"StreamID can't be null", "StreamID nemůže být null"}, {"Return to destination page", "Zpět na stránku destinací"}, - {"Back to commands list", "Zpět na list příkazů"}, + {"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"}, - {"Such destination is not found", "Takováto destinace nebyla nalezena"}, {"Unknown command", "Neznámý příkaz"}, {"Command accepted", "Příkaz přijat"}, {"Proxy error", "Chyba proxy serveru"}, @@ -162,6 +175,15 @@ namespace czech // language namespace {"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í"}, @@ -196,7 +218,7 @@ namespace czech // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/English.cpp b/i18n/English.cpp index 2670e984..eb7067e2 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,10 @@ namespace english // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"", ""}, }; @@ -42,7 +45,7 @@ namespace english // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/French.cpp b/i18n/French.cpp index 0ce044e1..2618772f 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace french // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f Kio"}, {"%.2f MiB", "%.2f Mio"}, @@ -44,7 +47,7 @@ namespace french // language namespace {"i2pd webconsole", "Console web i2pd"}, {"Main page", "Page principale"}, {"Router commands", "Commandes du routeur"}, - {"Local Destinations", "Destinations locales"}, + {"Local Destinations", "Destinatioans localeAlger"}, {"LeaseSets", "Jeu de baux"}, {"Tunnels", "Tunnels"}, {"Transit Tunnels", "Tunnels transitoires"}, @@ -58,7 +61,7 @@ namespace french // language namespace {"Unknown", "Inconnu"}, {"Proxy", "Proxy"}, {"Mesh", "Maillé"}, - {"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"}, @@ -68,7 +71,8 @@ namespace french // language namespace {"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"}, {"%.2f KiB/s", "%.2f Kio/s"}, {"Sent", "Envoyé"}, @@ -91,10 +95,11 @@ 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"}, {"%dms", "%dms"}, {"Outbound tunnels", "Tunnels sortants"}, @@ -109,6 +114,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"}, @@ -128,7 +134,7 @@ namespace french // language namespace {"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"}, @@ -150,6 +156,8 @@ namespace french // language namespace {"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 %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"}, @@ -158,24 +166,23 @@ 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 %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."}, + {"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."}, + {"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."}, + {"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"}, @@ -211,7 +218,7 @@ namespace french // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/German.cpp b/i18n/German.cpp index 0db3d6be..b3b35e99 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace german // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -61,6 +64,7 @@ namespace german // language namespace {"Clock skew", "Zeitabweichung"}, {"Offline", "Offline"}, {"Symmetric NAT", "Symmetrisches NAT"}, + {"No Descriptors", "Keine Beschreibungen"}, {"Uptime", "Laufzeit"}, {"Network status", "Netzwerkstatus"}, {"Network status v6", "Netzwerkstatus v6"}, @@ -116,8 +120,10 @@ namespace german // language namespace {"Gateway", "Gateway"}, {"TunnelID", "TunnelID"}, {"EndDate", "Enddatum"}, + {"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"}, @@ -145,6 +151,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 %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"}, @@ -162,6 +170,15 @@ 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", "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"}, @@ -196,7 +213,7 @@ namespace german // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Hebrew.cpp b/i18n/Hebrew.cpp new file mode 100644 index 00000000..7e56395f --- /dev/null +++ b/i18n/Hebrew.cpp @@ -0,0 +1,202 @@ +/* +* 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 +#include +#include +#include +#include "I18N.h" + +// Hebrew localization file + +namespace i2p +{ +namespace i18n +{ +namespace hebrew // language namespace +{ + // language name in lowercase + static std::string language = "hebrew"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3; + } + + // Right to Left language? + static bool rtl = true; + + static const LocaleStrings strings + { + {"%.2f KiB", "%.2f קי״ב"}, + {"%.2f MiB", "%.2f מי״ב"}, + {"%.2f GiB", "%.2f קי״ב"}, + {"Purple I2P Webconsole", "קונסולת Purple I2P"}, + {"i2pd webconsole", "קונסולת i2pd"}, + {"Main page", "עמוד ראשי"}, + {"Router commands", "פקודות נתב"}, + {"Local Destinations", "יעדים מקומיים"}, + {"Tunnels", "מנהרות"}, + {"Transit Tunnels", "מנהרות מעבר"}, + {"Transports", "מובילים"}, + {"I2P tunnels", "מנהרות I2P"}, + {"SAM sessions", "הפעלות SAM"}, + {"Unknown", "לא מוכר"}, + {"Proxy", "פרוקסי"}, + {"Mesh", "סיבוך"}, + {"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", "נתקבל"}, + {"%.2f KiB/s", "%.2f קי״ב/ש"}, + {"Sent", "נשלח"}, + {"Transit", "מעבר"}, + {"Data path", "נתיב מידע"}, + {"Hidden content. Press on text to see.", "תוכן מוסתר. לחץ על הטקסט כדי לראותו."}, + {"Router Ident", "מזהה נתב"}, + {"Router Family", "משפחת נתב"}, + {"Version", "גרסא"}, + {"Our external address", "הכתובת החיצונית שלנו"}, + {"supported", "נתמך"}, + {"Routers", "נתבים"}, + {"Client Tunnels", "מנהרות לקוח"}, + {"Services", "שירותים"}, + {"Enabled", "מאופשר"}, + {"Disabled", "מנוטרל"}, + {"Encrypted B33 address", "כתובת B33 מוצפנת"}, + {"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."}, + {"Address", "כתובת"}, + {"Type", "טיפוס"}, + {"Expire LeaseSet", "פקיעת LeaseSet"}, + {"Inbound tunnels", "מנהרות פנימיות"}, + {"%dms", "מילישניות %d"}, + {"Outbound tunnels", "מנהרות חיצוניות"}, + {"Tags", "תוויות"}, + {"Incoming", "נכנס"}, + {"Outgoing", "יוצא"}, + {"Destination", "יעד"}, + {"Amount", "כמות"}, + {"Incoming Tags", "תוויות נכנסות"}, + {"Tags sessions", "הפעלות תוויות"}, + {"Status", "מצב"}, + {"Local Destination", "יעד מקומי"}, + {"Streams", "זרמים"}, + {"Close stream", "סגור זרם"}, + {"Such destination is not found", "יעד כזה לא נמצא"}, + {"I2CP session not found", "הפעלת I2CP לא נמצאה"}, + {"I2CP is not enabled", "I2CP לא מאופשר"}, + {"Invalid", "לא תקין"}, + {"Store type", "טיפוס אחסון"}, + {"Expires", "פוקע"}, + {"Non Expired Leases", "חכירות בלתי פקיעות"}, + {"Gateway", "שער-דרך"}, + {"TunnelID", "מזהה מנהרה"}, + {"EndDate", "תאריך סיום"}, + {"floodfill mode is disabled", "מצב floodfill הינו מנוטרל"}, + {"Queue size", "גודל תור"}, + {"Run peer test", "הרץ בדיקת עמית"}, + {"Reload tunnels configuration", "טען מחדש תצורת מנהרות"}, + {"Decline transit tunnels", "דחה מנהרות מעבר"}, + {"Accept transit tunnels", "קבל מנהרות מעבר"}, + {"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", "שנה שפה"}, + {"no transit tunnels currently built", "אין מנהרות מעבר אשר בנויות כעת"}, + {"SAM disabled", "SAM מנוטרל"}, + {"no sessions currently running", "אין הפעלה אשר מורצת כעת"}, + {"SAM session not found", "הפעלת SAM לא נמצאה"}, + {"SAM Session", "הפעלת SAM"}, + {"Server Tunnels", "מנהרות שרת"}, + {"Unknown page", "עמוד לא מוכר"}, + {"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 %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", "תיאור"}, + {"A bit information about service on domain", "מידע אודות שירות על תחום"}, + {"Submit", "שלח"}, + {"Domain can't end with .b32.i2p", "תחום לא יכול להסתיים עם ‎.b32.i2p"}, + {"Domain must end with .i2p", "תחום חייב להסתיים עם ‎.i2p"}, + {"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", "באפשרותך לנסות למצוא את מארח זה דרך שירותי קפיצה להלן"}, + {"Invalid request", "בקשה לא תקינה"}, + {"Proxy unable to parse your request", "פרוקסי לא מסוגל לנתח את בקשתך"}, + {"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 is כבר נמצא בפנקס כתובות של הנתב. זהירות: מקור URL זה עלול להזיק! לחץ כאן כדי לעדכן מרשם: המשך."}, + {"Addresshelper forced update rejected", "אילוץ עדכון של סייען-כתובות נדחה"}, + {"To add host %s in router's addressbook, click here: Continue.", "Tכדי להוסיף את מארח %s לפנקס כתובות של הנתב: המשך."}, + {"Addresshelper request", "בקשת סייען-כתובות"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "מארח %s נתווסף לסייען-כתובות של הנתב דרך סייען. לחץ כאן כדי proceed: המשך."}, + {"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", "לא יכול לאתר יעד מארח מתוך בקשה"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "מארח %s לא נמצא בתוך רשת I2P, אולם outproxy אינו מאופשר"}, + {"Hostname is too long", "שם-מארח הינו ארוך מדי"}, + {"Cannot negotiate with SOCKS proxy", "לא מסוגל להסדיר פרוקסי SOCKS"}, + {"CONNECT error", "שגיאת חיבור"}, + {"Failed to connect", "נכשל להתחבר"}, + {"SOCKS proxy error", "שגיאת פרוקסי SOCKS"}, + {"No reply from SOCKS proxy", "אין מענה מתוך פרוקסי SOCKS"}, + {"Cannot connect", "לא מסוגל להתחבר"}, + {"Host is down", "מארח הינו מושבת"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "לא יכול ליצור חיבור למארח מבוקש, המארח עשוי להיות מושבת. אנא נסה שוב מאוחר יותר."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"יום %d", "יומיים", "ימים %d", "ימים %d"}}, + {"%d hours", {"שעה %d", "שעתיים", "שעות %d", "שעות %d"}}, + {"%d minutes", {"דקה %d", "שתי דקות", "דקות %d", "דקות %d"}}, + {"%d seconds", {"שניה %d", "שתי שניות", "שניות %d", "שניות %d"}}, + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Hindi.cpp b/i18n/Hindi.cpp new file mode 100644 index 00000000..7ce9b65b --- /dev/null +++ b/i18n/Hindi.cpp @@ -0,0 +1,226 @@ +/* +* 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 +#include +#include +#include +#include "I18N.h" + +// Hindi localization file + +namespace i2p +{ +namespace i18n +{ +namespace hindi // language namespace +{ + // language name in lowercase + static std::string language = "hindi"; + + // 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; + } + + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings + { + {"%.2f KiB", "%.2f कीबी"}, + {"%.2f MiB", "%.2f मीबी"}, + {"%.2f GiB", "%.2f जीबी"}, + {"building", "निर्माण"}, + {"failed", "विफल"}, + {"expiring", "समाप्त होना"}, + {"established", "स्थापित"}, + {"unknown", "अज्ञात"}, + {"exploratory", "अन्वेषणात्मक"}, + {"Purple I2P Webconsole", "पर्पल I2P वेब कंसोल"}, + {"i2pd webconsole", "i2pd वेब कंसोल"}, + {"Main page", "मुख्य पृष्ठ"}, + {"Router commands", "राउटर आदेश"}, + {"Local Destinations", "स्थानीय गंतव्य"}, + {"LeaseSets", "पट्ट समुच्चय"}, + {"Tunnels", "सुरंग"}, + {"Transit Tunnels", "संचरण सुरंगें"}, + {"Transports", "परिवहन"}, + {"I2P tunnels", "I2P सुरंगें"}, + {"SAM sessions", "SAM सत्र"}, + {"ERROR", "त्रुटि"}, + {"OK", "ठीक है"}, + {"Testing", "परीक्षण"}, + {"Firewalled", "फायरवॉल"}, + {"Unknown", "अज्ञात"}, + {"Proxy", "प्रॉक्सी"}, + {"Mesh", "जाली"}, + {"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", "प्राप्त हुआ"}, + {"%.2f KiB/s", "%.2f कीबी/से"}, + {"Sent", "प्रेषित"}, + {"Transit", "संचरण"}, + {"Data path", "डेटा पथ"}, + {"Hidden content. Press on text to see.", "सामग्री छिपाई गई है। देखने हेतु पाठ पर दबाएँ।"}, + {"Router Ident", "राउटर परिचय"}, + {"Router Family", "राउटर परिवार"}, + {"Router Caps", "राउटर कैप्स"}, + {"Version", "संस्करण"}, + {"Our external address", "हमारा बाह्य पता"}, + {"supported", "समर्थित"}, + {"Routers", "राउटर"}, + {"Floodfills", "पूर्णक संवाहक"}, + {"Client Tunnels", "क्लाइंट सुरंगें"}, + {"Services", "सेवाएँ"}, + {"Enabled", "सक्षम है"}, + {"Disabled", "निष्क्रिय है"}, + {"Encrypted B33 address", "कूटलिखित B33 पता"}, + {"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 का उपयोग करें।"}, + {"Address", "पता"}, + {"Type", "प्रकार"}, + {"EncType", "कूट प्रकार"}, + {"Expire LeaseSet", "पट्ट समुच्चय का अवसान करें"}, + {"Inbound tunnels", "आगमनशील सुरंगें"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "प्रस्थानशील सुरंगें"}, + {"Tags", "चिन्हित"}, + {"Incoming", "आगामी"}, + {"Outgoing", "निर्गामी"}, + {"Destination", "गंतव्य"}, + {"Amount", "मात्रा"}, + {"Incoming Tags", "आगामी चिन्हित"}, + {"Tags sessions", "चिन्हित सत्र OR सत्र को चिन्हित करें"}, + {"Status", "स्थिति"}, + {"Local Destination", "स्थानीय गंतव्य"}, + {"Streams", "धाराएँ"}, + {"Close stream", "प्रवाह समाप्त करें"}, + {"Such destination is not found", "ऐसा गंतव्य नहीं मिला"}, + {"I2CP session not found", "I2CP सत्र नहीं मिला"}, + {"I2CP is not enabled", "I2CP निष्क्रिय है"}, + {"Invalid", "अमान्य"}, + {"Store type", "भण्डारगार का प्रकार"}, + {"Expires", "अवसान होता है"}, + {"Non Expired Leases", "अनवसित पट्ट"}, + {"Gateway", "प्रवेशद्वार"}, + {"TunnelID", "सुरंग ID"}, + {"EndDate", "समाप्ति तिथि"}, + {"floodfill mode is disabled", "पूर्णक संवाहक विधि निष्क्रिय है"}, + {"Queue size", "क्यू आकार"}, + {"Run peer test", "सहकर्मी परीक्षण चलाएँ"}, + {"Reload tunnels configuration", "सुरंग विन्यास पुनः लोड करें"}, + {"Decline transit tunnels", "संचरण सुरंगों को अस्वीकार करें"}, + {"Accept transit tunnels", "संचरण सुरंगों को स्वीकार करें"}, + {"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", "भाषा बदलें"}, + {"no transit tunnels currently built", "संचरण सुरंगों का निर्माण नहीं हुआ है"}, + {"SAM disabled", "SAM निष्क्रिय है"}, + {"no sessions currently running", "वर्तमान में कोई सत्र सक्रिय नहीं है"}, + {"SAM session not found", "SAM सत्र नहीं मिला"}, + {"SAM Session", "SAM सत्र"}, + {"Server Tunnels", "सर्वर सुरंग"}, + {"Client Forwards", "क्लाइंट फॉरवर्ड्स"}, + {"Server Forwards", "सर्वर फॉरवर्ड्स"}, + {"Unknown page", "अज्ञात पृष्ठ"}, + {"Invalid token", "अमान्य टोकन"}, + {"SUCCESS", "सफलता"}, + {"Stream closed", "प्रवाह समाप्त हो गया है"}, + {"Stream not found or already was closed", "प्रवाह प्राप्त नहीं हुआ अथवा इसका पूर्व में ही समापन हो चुका है"}, + {"Destination not found", "गंतव्य नहीं मिला"}, + {"StreamID can't be null", "प्रवाह ID शून्य नहीं हो सकता है"}, + {"Return to destination page", "गंतव्य पृष्ठ पर पुनः वापस जाएँ"}, + {"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", "विवरण"}, + {"A bit information about service on domain", "डोमेन पर सेवा से संबंधित थोड़ी जानकारी"}, + {"Submit", "प्रस्तुत करें"}, + {"Domain can't end with .b32.i2p", "डोमेन का अंत .b32.i2p से नहीं हो सकता"}, + {"Domain must end with .i2p", "डोमेन का अंत .i2p से होना आवश्यक है"}, + {"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", "आप नीचे दिए गए जंप सेवाओं में इस होस्ट को खोजने की कोशिश कर सकते हैं"}, + {"Invalid request", "अमान्य अनुरोध"}, + {"Proxy unable to parse your request", "प्रॉक्सी आपके अनुरोध को विश्लेषित करने में असमर्थ है"}, + {"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 पहले से ही राउटर की पता-पुस्तिका में उपस्थित हैसावधान रहें: इस URL का स्रोत हानिकारक हो सकता है! अभिलेख को अद्यतन करने हेतु यहाँ क्लिक करें: जारी रखें।"}, + {"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", "गलत आउटप्रॉक्सी सेटिंग्स"}, + {"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", "संपर्क त्रुटि"}, + {"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.", "अनुरोधित होस्ट से संपर्क स्थापित नहीं किया जा सका। संभवतः वह सक्रिय नहीं है। कृपया बाद में पुनः प्रयास करें।"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d दिन", "%d दिन"}}, + {"%d hours", {"%d घंटा", "%dघंटे"}}, + {"%d minutes", {"%d मिनट", "%d मिनट"}}, + {"%d seconds", {"%d सेकंड", "%d सेकंड"}}, + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/I18N.cpp b/i18n/I18N.cpp index cf4873eb..48a02357 100644 --- a/i18n/I18N.cpp +++ b/i18n/I18N.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,12 +30,12 @@ namespace i18n } } - std::string translate (const std::string& arg) + std::string_view translate (std::string_view arg) { return i2p::client::context.GetLanguage ()->GetString (arg); } - std::string translate (const std::string& arg, const std::string& arg2, const int& n) + std::string translate (const std::string& arg, const std::string& arg2, const int n) { return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); } diff --git a/i18n/I18N.h b/i18n/I18N.h index 395c18eb..00398f78 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,6 +10,7 @@ #define __I18N_H__ #include +#include #include #include #include @@ -18,15 +19,17 @@ namespace i2p { namespace i18n { + typedef std::map LocaleStrings; class Locale { public: Locale ( const std::string& language, - const std::map& strings, + const bool& rtl, + const LocaleStrings& strings, const std::map>& plurals, std::function formula - ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + ): m_Language (language), m_RTL (rtl), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; // Get activated language name for webconsole std::string GetLanguage() const @@ -34,7 +37,12 @@ namespace i18n return m_Language; } - std::string GetString (const std::string& arg) const + bool GetRTL() const + { + return m_RTL; + } + + std::string_view GetString (std::string_view arg) const { const auto it = m_Strings.find(arg); if (it == m_Strings.end()) @@ -47,7 +55,7 @@ namespace i18n } } - std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const + std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const { const auto it = m_Plurals.find(arg2); if (it == m_Plurals.end()) // not found, fallback to english @@ -63,14 +71,15 @@ namespace i18n private: const std::string m_Language; - const std::map m_Strings; + const bool m_RTL; + const LocaleStrings m_Strings; const std::map> m_Plurals; std::function m_Formula; }; void SetLanguage(const std::string &lang); - std::string translate (const std::string& arg); - std::string translate (const std::string& arg, const std::string& arg2, const int& n); + std::string_view translate (std::string_view arg); + std::string translate (const std::string& arg, const std::string& arg2, int n); } // i18n } // i2p @@ -79,7 +88,7 @@ namespace i18n * @param arg String with message */ template -std::string tr (TValue&& arg) +std::string_view tr (TValue&& arg) { return i2p::i18n::translate(std::forward(arg)); } @@ -92,12 +101,11 @@ std::string tr (TValue&& arg) template std::string tr (TValue&& arg, TArgs&&... args) { - std::string tr_str = i2p::i18n::translate(std::forward(arg)); + std::string tr_str = std::string (i2p::i18n::translate(std::forward(arg))); // TODO: size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - size = size + 1; std::string str(size, 0); - std::snprintf(&str.front(), size, tr_str.c_str(), std::forward(args)...); + std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); return str; } @@ -109,7 +117,7 @@ std::string tr (TValue&& arg, TArgs&&... args) * @param n Integer, used for selection of form */ template -std::string ntr (TValue&& arg, TValue2&& arg2, int& n) +std::string ntr (TValue&& arg, TValue2&& arg2, int n) { return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); } @@ -122,14 +130,13 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int& n) * @param args Array of arguments for string formatting */ template -std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args) +std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) { std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - size = size + 1; std::string str(size, 0); - std::snprintf(&str.front(), size, tr_str.c_str(), std::forward(args)...); + std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); return str; } diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 6426e2ce..b3d09af8 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,6 +30,8 @@ namespace i18n namespace english { std::shared_ptr GetLocale (); } namespace french { std::shared_ptr GetLocale (); } namespace german { std::shared_ptr GetLocale (); } + namespace hebrew { std::shared_ptr GetLocale (); } + namespace hindi { std::shared_ptr GetLocale (); } namespace italian { std::shared_ptr GetLocale (); } namespace polish { std::shared_ptr GetLocale (); } namespace portuguese { std::shared_ptr GetLocale (); } @@ -53,6 +55,8 @@ namespace i18n { "english", {"English", "en", i2p::i18n::english::GetLocale} }, { "french", {"Français", "fr", i2p::i18n::french::GetLocale} }, { "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} }, + { "hebrew", {"עִבְרִית‎", "he", i2p::i18n::hebrew::GetLocale} }, + { "hindi", {"हिन्दी", "hi", i2p::i18n::hindi::GetLocale} }, { "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} }, { "polish", {"Polski", "pl", i2p::i18n::polish::GetLocale} }, { "portuguese", {"Português", "pt", i2p::i18n::portuguese::GetLocale} }, diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp index c51e15a5..339654ba 100644 --- a/i18n/Italian.cpp +++ b/i18n/Italian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace italian // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -61,12 +64,15 @@ namespace italian // language namespace {"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"}, @@ -93,6 +99,7 @@ namespace italian // language namespace {"Address", "Indirizzo"}, {"Type", "Tipologia"}, {"EncType", "Tipo di crittografia"}, + {"Expire LeaseSet", "Scadenza LeaseSet"}, {"Inbound tunnels", "Tunnel in entrata"}, {"%dms", "%dms"}, {"Outbound tunnels", "Tunnel in uscita"}, @@ -107,6 +114,7 @@ namespace italian // language namespace {"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"}, @@ -116,6 +124,7 @@ namespace italian // language namespace {"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"}, @@ -147,6 +156,8 @@ namespace italian // language namespace {"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"}, @@ -155,7 +166,6 @@ namespace italian // language namespace {"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"}, - {"Such destination is not found", "Questa destinazione non è stata trovata"}, {"Unknown command", "Comando sconosciuto"}, {"Command accepted", "Comando accettato"}, {"Proxy error", "Errore del proxy"}, @@ -165,8 +175,15 @@ namespace italian // language namespace {"You may try to find this host on jump services below", "Si può provare a trovare questo host sui servizi di salto qui sotto"}, {"Invalid request", "Richiesta non valida"}, {"Proxy unable to parse your request", "Il proxy non è in grado di elaborare la tua richiesta"}, + {"Addresshelper is not supported", "Addresshelper non è supportato"}, + {"Host %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"}, @@ -201,7 +218,7 @@ namespace italian // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp index 26661231..97422e45 100644 --- a/i18n/Polish.cpp +++ b/i18n/Polish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,29 +29,196 @@ namespace polish // language namespace return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); } - static std::map strings + // Right to Left language? + static bool rtl = false; + + 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); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp index 528be389..970a7b8f 100644 --- a/i18n/Portuguese.cpp +++ b/i18n/Portuguese.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace portuguese // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -58,7 +61,7 @@ namespace portuguese // language namespace {"Unknown", "Desconhecido"}, {"Proxy", "Proxy"}, {"Mesh", "Malha"}, - {"Clock skew", "Defasagem do Relógio"}, + {"Clock skew", "Desvio de Relógio"}, {"Offline", "Desligado"}, {"Symmetric NAT", "NAT Simétrico"}, {"Full cone NAT", "Full cone NAT"}, @@ -69,11 +72,12 @@ namespace portuguese // language namespace {"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", "Caminho dos dados"}, + {"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"}, @@ -95,6 +99,7 @@ namespace portuguese // language namespace {"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"}, @@ -104,11 +109,12 @@ namespace portuguese // language namespace {"Destination", "Destinos"}, {"Amount", "Quantidade"}, {"Incoming Tags", "Etiquetas de Entrada"}, - {"Tags sessions", "Sessões de etiquetas"}, + {"Tags sessions", "Sessões de Etiquetas"}, {"Status", "Estado"}, - {"Local Destination", "Destinos Locais"}, + {"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"}, @@ -122,15 +128,15 @@ namespace portuguese // language namespace {"Queue size", "Tamanho da fila"}, {"Run peer test", "Executar teste de peers"}, {"Reload tunnels configuration", "Recarregar a configuração dos túneis"}, - {"Decline transit tunnels", "Negar túnel de trânsito"}, - {"Accept transit tunnels", "Aceitar túnel de trânsito"}, + {"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 nos túneis de trânsito"}, + {"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"}, @@ -145,42 +151,43 @@ namespace portuguese // language namespace {"Invalid token", "Token Inválido"}, {"SUCCESS", "SUCESSO"}, {"Stream closed", "Fluxo fechado"}, - {"Stream not found or already was closed", "Fluxo não encontrado ou já encerrado"}, + {"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 na reg.i2p"}, + {"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"}, - {"Such destination is not found", "Tal destino não foi encontrado"}, {"Unknown command", "Comando desconhecido"}, {"Command accepted", "Comando aceito"}, {"Proxy error", "Erro no proxy"}, {"Proxy info", "Informações do proxy"}, {"Proxy error: Host not found", "Erro no proxy: Host não encontrado"}, {"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"}, - {"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos jump services abaixo"}, + {"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 do Auxiliar de Endereços"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para proceder: Continuar ."}, + {"Addresshelper 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", "Configurações ruins de 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"}, @@ -211,7 +218,7 @@ namespace portuguese // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index dbfb13f2..e5bcb0b0 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ 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 + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f КиБ"}, {"%.2f MiB", "%.2f МиБ"}, @@ -69,6 +72,7 @@ namespace russian // language namespace {"Stopping in", "Остановка через"}, {"Family", "Семейство"}, {"Tunnel creation success rate", "Успешно построенных туннелей"}, + {"Total tunnel creation success rate", "Общий процент успешно построенных туннелей"}, {"Received", "Получено"}, {"%.2f KiB/s", "%.2f КиБ/с"}, {"Sent", "Отправлено"}, @@ -95,6 +99,7 @@ namespace russian // language namespace {"Address", "Адрес"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, + {"Expire LeaseSet", "Просрочить Лизсет"}, {"Inbound tunnels", "Входящие туннели"}, {"%dms", "%dмс"}, {"Outbound tunnels", "Исходящие туннели"}, @@ -109,6 +114,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", "Некорректный"}, @@ -150,6 +156,8 @@ namespace russian // language namespace {"StreamID can't be null", "StreamID не может быть пустым"}, {"Return to destination page", "Вернуться на страницу точки назначения"}, {"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"}, @@ -158,7 +166,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", "Ошибка прокси"}, @@ -211,7 +218,7 @@ namespace russian // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp index a5ecc30a..5b51ce4d 100644 --- a/i18n/Spanish.cpp +++ b/i18n/Spanish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace spanish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -196,7 +199,7 @@ namespace spanish // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp index e7f84e69..1dcb7e61 100644 --- a/i18n/Swedish.cpp +++ b/i18n/Swedish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace swedish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -61,6 +64,8 @@ namespace swedish // language namespace {"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"}, @@ -107,6 +112,7 @@ namespace swedish // language namespace {"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"}, @@ -116,8 +122,10 @@ namespace swedish // language namespace {"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"}, @@ -154,7 +162,6 @@ namespace swedish // language namespace {"Submit", "Skicka"}, {"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"}, {"Domain must end with .i2p", "Domänen måste sluta med .i2p"}, - {"Such destination is not found", "En sådan plats hittas ej"}, {"Unknown command", "Okänt kommando"}, {"Command accepted", "Kommando accepterades"}, {"Proxy error", "Proxyfel"}, @@ -165,6 +172,14 @@ namespace swedish // language namespace {"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"}, @@ -199,7 +214,7 @@ namespace swedish // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp index d4398ebe..139b004e 100644 --- a/i18n/Turkish.cpp +++ b/i18n/Turkish.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, The PurpleI2P Project +* Copyright (c) 2023-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace turkish // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -106,7 +109,7 @@ namespace turkish // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 35ee0f89..8cdcae52 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace turkmen // language namespace return n != 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -196,7 +199,7 @@ namespace turkmen // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index c1e222ea..3c3c44a4 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ 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 + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f КіБ"}, {"%.2f MiB", "%.2f МіБ"}, @@ -69,6 +72,7 @@ namespace ukrainian // language namespace {"Stopping in", "Зупинка через"}, {"Family", "Сімейство"}, {"Tunnel creation success rate", "Успішно побудованих тунелів"}, + {"Total tunnel creation success rate", "Загальна кількість створених тунелів"}, {"Received", "Отримано"}, {"%.2f KiB/s", "%.2f КіБ/с"}, {"Sent", "Відправлено"}, @@ -95,6 +99,7 @@ namespace ukrainian // language namespace {"Address", "Адреса"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, + {"Expire LeaseSet", "Завершити LeaseSet"}, {"Inbound tunnels", "Вхідні тунелі"}, {"%dms", "%dмс"}, {"Outbound tunnels", "Вихідні тунелі"}, @@ -109,6 +114,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", "Некоректний"}, @@ -150,6 +156,8 @@ namespace ukrainian // language namespace {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, {"Return to destination page", "Повернутися на сторінку точки призначення"}, {"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"}, @@ -158,7 +166,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", "Помилка проксі"}, @@ -211,7 +218,7 @@ namespace ukrainian // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp index 9d798be4..681a8e19 100644 --- a/i18n/Uzbek.cpp +++ b/i18n/Uzbek.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2023, The PurpleI2P Project +* Copyright (c) 2021-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,10 @@ namespace uzbek // language namespace return n > 1 ? 1 : 0; } - static std::map strings + // Right to Left language? + static bool rtl = false; + + static const LocaleStrings strings { {"%.2f KiB", "%.2f KiB"}, {"%.2f MiB", "%.2f MiB"}, @@ -69,6 +72,7 @@ namespace uzbek // language namespace {"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"}, {"%.2f KiB/s", "%.2f KiB/s"}, {"Sent", "Yuborilgan"}, @@ -95,6 +99,7 @@ namespace uzbek // language namespace {"Address", "Manzil"}, {"Type", "Turi"}, {"EncType", "ShifrlashTuri"}, + {"Expire LeaseSet", "LeaseSet muddati tugaydi"}, {"Inbound tunnels", "Kirish tunnellari"}, {"%dms", "%dms"}, {"Outbound tunnels", "Chiquvchi tunnellar"}, @@ -109,6 +114,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"}, @@ -150,6 +156,8 @@ namespace uzbek // language namespace {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, {"Return to destination page", "Manzilgoh sahifasiga qaytish"}, {"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"}, @@ -158,7 +166,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"}, @@ -211,7 +218,7 @@ namespace uzbek // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); } } // language 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 ced086e1..2006462a 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,15 +1,17 @@ /* -* 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 #include // for crc32 #include #include #include +#include #include #include "Base.h" #include "Crypto.h" @@ -65,10 +67,10 @@ namespace data BIGNUM * x = BN_bin2bn (pub, publicKeyLen/2, NULL); BIGNUM * y = BN_bin2bn (pub + publicKeyLen/2, publicKeyLen/2, NULL); EC_POINT * p = EC_POINT_new (group); - EC_POINT_set_affine_coordinates_GFp (group, p, x, y, NULL); + EC_POINT_set_affine_coordinates (group, p, x, y, NULL); EC_POINT * p1 = BlindPublicKeyECDSA (group, p, seed); EC_POINT_free (p); - EC_POINT_get_affine_coordinates_GFp (group, p1, x, y, NULL); + EC_POINT_get_affine_coordinates (group, p1, x, y, NULL); EC_POINT_free (p1); i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); @@ -88,7 +90,7 @@ namespace data BN_CTX_free (ctx); BN_free (a1); BIGNUM * x = BN_new(), * y = BN_new(); - EC_POINT_get_affine_coordinates_GFp (group, p, x, y, NULL); + EC_POINT_get_affine_coordinates (group, p, x, y, NULL); EC_POINT_free (p); i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); @@ -152,11 +154,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 +200,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 +210,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 @@ -296,12 +297,14 @@ namespace data void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, p.c_str (), p.length ()); + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + + EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); + EVP_DigestUpdate (ctx, p.c_str (), p.length ()); for (const auto& it: bufs) - SHA256_Update (&ctx, it.first, it.second); - SHA256_Final (hash, &ctx); + EVP_DigestUpdate (ctx, it.first, it.second); + EVP_DigestFinal_ex (ctx, (uint8_t * )hash, nullptr); + EVP_MD_CTX_free (ctx); } i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const @@ -320,11 +323,12 @@ namespace data if (publicKeyLength) { auto stA1 = htobe16 (m_BlindedSigType); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, (const uint8_t *)&stA1, 2); - SHA256_Update (&ctx, blinded, publicKeyLength); - SHA256_Final ((uint8_t *)hash, &ctx); + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); + EVP_DigestUpdate (ctx, (const uint8_t *)&stA1, 2); + EVP_DigestUpdate (ctx, blinded, publicKeyLength); + EVP_DigestFinal_ex (ctx, (uint8_t * )hash, nullptr); + EVP_MD_CTX_free(ctx); } else LogPrint (eLogError, "Blinding: Blinded key type ", (int)m_BlindedSigType, " is not supported"); diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index c78db003..fc11f613 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include "Identity.h" @@ -23,7 +24,7 @@ namespace data public: BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p std::string ToB33 () const; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp deleted file mode 100644 index 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 8df08118..d87b3ba1 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -76,8 +76,14 @@ namespace config { options_description limits("Limits options"); limits.add_options() ("limits.coresize", value()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") +#if defined(__HAIKU__) + // Haiku's system default is 512, so we set 4096 explicitly + ("limits.openfiles", value()->default_value(4096), "Maximum number of open files (4096 by default)") +#else ("limits.openfiles", value()->default_value(0), "Maximum number of open files (0 - use system default)") - ("limits.transittunnels", value()->default_value(5000), "Maximum active transit tunnels (default:5000)") +#endif + ("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") @@ -96,6 +102,7 @@ namespace config { ("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)") + ("http.theme", value()->default_value("light"), "Theme for http web console") ; options_description httpproxy("HTTP Proxy options"); @@ -116,9 +123,18 @@ 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") +#if OPENSSL_PQ + ("httpproxy.i2cp.leaseSetEncType", value()->default_value("6,4,0"), "Local destination's LeaseSet encryption type") +#else + ("httpproxy.i2cp.leaseSetEncType", value()->default_value("4,0"), "Local destination's LeaseSet encryption type") +#endif ("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)") + ("httpproxy.i2p.streaming.maxWindowSize", value()->default_value("512"), "HTTP Proxy stream max window size. 512 by default") ; options_description socksproxy("SOCKS Proxy options"); @@ -141,15 +157,39 @@ namespace config { ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") ("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") +#if OPENSSL_PQ + ("socksproxy.i2cp.leaseSetEncType", value()->default_value("6,4,0"), "Local destination's LeaseSet encryption type") +#else + ("socksproxy.i2cp.leaseSetEncType", value()->default_value("4,0"), "Local destination's LeaseSet encryption type") +#endif ("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)") + ("socksproxy.i2p.streaming.maxWindowSize", value()->default_value("512"), "SOCKS Proxy stream max window size. 512 by default") ; + 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") +#if OPENSSL_PQ + ("shareddest.i2cp.leaseSetEncType", value()->default_value("6,4,0"), "Shared local destination's LeaseSet encryption type") +#else + ("shareddest.i2cp.leaseSetEncType", value()->default_value("4,0"), "Shared local destination's LeaseSet encryption type") +#endif + ("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") ; @@ -166,6 +206,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"); @@ -191,7 +233,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), @@ -203,7 +245,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") @@ -211,20 +253,19 @@ 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.novg.net/," + "https://reseed.stormycloud.org/" ), "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") ; @@ -281,6 +322,8 @@ namespace config { ("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") + ("ssu2.firewalled4", value()->default_value(false), "Set ipv4 network status to Firewalled even if OK (default: disabled)") + ("ssu2.firewalled6", value()->default_value(false), "Set ipv6 network status to Firewalled even if OK (default: disabled)") ; options_description nettime("Time sync options"); @@ -302,11 +345,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"); @@ -328,6 +371,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 c9670f6c..dd43d045 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 * @@ -16,13 +16,12 @@ #include #include "TunnelBase.h" #include -#if OPENSSL_HKDF #include +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +#include +#include #endif -#if !OPENSSL_AEAD_CHACHA20_POLY1305 -#include "ChaCha20.h" -#include "Poly1305.h" -#endif +#include "CPU.h" #include "Crypto.h" #include "Ed25519.h" #include "I2PEndian.h" @@ -32,7 +31,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 +51,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 +65,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 +83,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 +148,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,10 +186,11 @@ namespace crypto DSA_set0_key (dsa, NULL, NULL); return dsa; } - +#endif + // DH/ElGamal -#if !defined(__x86_64__) +#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 @@ -244,17 +275,12 @@ namespace crypto // 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) @@ -264,29 +290,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); @@ -298,16 +311,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; @@ -315,25 +323,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); @@ -343,11 +343,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 @@ -361,7 +356,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 @@ -428,7 +423,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 @@ -461,14 +456,14 @@ namespace crypto auto p = EC_POINT_new (curve); EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); + EC_POINT_get_affine_coordinates (curve, p, x, y, nullptr); encrypted[0] = 0; bn2buf (x, encrypted + 1, len); bn2buf (y, encrypted + 1 + len, len); RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); // encryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); - EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); + EC_POINT_get_affine_coordinates (curve, p, x, y, nullptr); uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); bn2buf (y, iv, len); @@ -481,9 +476,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); @@ -502,11 +496,11 @@ namespace crypto BN_bin2bn (encrypted + 1, len, x); BN_bin2bn (encrypted + 1 + len, len, y); auto p = EC_POINT_new (curve); - if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) + if (EC_POINT_set_affine_coordinates (curve, p, x, y, nullptr)) { auto s = EC_POINT_new (curve); EC_POINT_mul (curve, s, nullptr, p, key, ctx); - EC_POINT_get_affine_coordinates_GFp (curve, s, x, y, nullptr); + EC_POINT_get_affine_coordinates (curve, s, x, y, nullptr); EC_POINT_free (s); uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); @@ -516,8 +510,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); @@ -555,435 +548,114 @@ namespace crypto } // 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); @@ -991,11 +663,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)); @@ -1004,144 +685,103 @@ 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 } - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, + + 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, std::string_view info, uint8_t * out, size_t outLen) { -#if OPENSSL_HKDF EVP_PKEY_CTX * pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_HKDF, nullptr); EVP_PKEY_derive_init (pctx); EVP_PKEY_CTX_set_hkdf_md (pctx, EVP_sha256()); @@ -1159,118 +799,141 @@ namespace crypto EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); } if (info.length () > 0) - EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), info.length ()); + EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.data (), info.length ()); EVP_PKEY_derive (pctx, out, &outLen); EVP_PKEY_CTX_free (pctx); -#else - uint8_t prk[32]; unsigned int len; - HMAC(EVP_sha256(), salt, 32, key, keyLen, prk, &len); - auto l = info.length (); - memcpy (out, info.c_str (), l); out[l] = 0x01; - HMAC(EVP_sha256(), prk, 32, out, l + 1, out, &len); - if (outLen > 32) // 64 - { - memcpy (out + 32, info.c_str (), l); out[l + 32] = 0x02; - HMAC(EVP_sha256(), prk, 32, out, l + 33, out + 32, &len); - } -#endif } // 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); + + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); + EVP_DigestUpdate (ctx, hh, 32); + EVP_DigestUpdate (ctx, pub, 32); + EVP_DigestFinal_ex (ctx, m_H, nullptr); // h = MixHash(pub) = SHA256(hh || pub) + EVP_MD_CTX_free (ctx); + m_N = 0; + } + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); + EVP_DigestUpdate (ctx, m_H, 32); + EVP_DigestUpdate (ctx, buf, len); + EVP_DigestFinal_ex (ctx, m_H, nullptr); + EVP_MD_CTX_free (ctx); } void NoiseSymmetricState::MixHash (const std::vector >& bufs) { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); + EVP_DigestUpdate (ctx, m_H, 32); for (const auto& it: bufs) - SHA256_Update (&ctx, it.first, it.second); - SHA256_Final (m_H, &ctx); + EVP_DigestUpdate (ctx, it.first, it.second); + EVP_DigestFinal_ex (ctx, m_H, nullptr); + EVP_MD_CTX_free (ctx); } void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) { 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; @@ -1285,19 +948,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 @@ -1312,7 +971,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 d4cbda97..3b9dfb93 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 * @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -21,33 +22,17 @@ #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 -# endif -# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them -# define OPENSSL_AEAD_CHACHA20_POLY1305 1 -# endif +#if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL +# define OPENSSL_SIPHASH 1 +#endif +#if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0 +# define OPENSSL_PQ 1 #endif namespace i2p @@ -57,7 +42,11 @@ 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 (); @@ -83,13 +72,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 }; @@ -104,142 +88,70 @@ namespace crypto void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); // AES - struct ChipherBlock - { - uint8_t buf[16]; - - void operator^=(const ChipherBlock& other) // XOR - { - if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ? - { - for (int i = 0; i < 4; i++) - reinterpret_cast(buf)[i] ^= reinterpret_cast(other.buf)[i]; - } - else - { - for (int i = 0; i < 16; i++) - buf[i] ^= other.buf[i]; - } - } - }; - typedef i2p::data::Tag<32> AESKey; - - template - class AESAlignedBuffer // 16 bytes alignment - { - public: - - AESAlignedBuffer () - { - m_Buf = m_UnalignedBuffer; - uint8_t rem = ((size_t)m_Buf) & 0x0f; - if (rem) - m_Buf += (16 - rem); - } - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; }; - const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; }; - - private: - - uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment - uint8_t * m_Buf; - }; - - -#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 @@ -279,112 +191,88 @@ 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 + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, std::string_view info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 // Noise 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..1a448ed1 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 * @@ -41,7 +41,7 @@ namespace crypto m_PublicKey = EC_POINT_new (m_Curve); BIGNUM * x = BN_bin2bn (pub, 32, nullptr); BIGNUM * y = BN_bin2bn (pub + 32, 32, nullptr); - if (!EC_POINT_set_affine_coordinates_GFp (m_Curve, m_PublicKey, x, y, nullptr)) + if (!EC_POINT_set_affine_coordinates (m_Curve, m_PublicKey, x, y, nullptr)) LogPrint (eLogError, "ECICS P256 invalid public key"); BN_free (x); BN_free (y); } @@ -87,7 +87,7 @@ namespace crypto RAND_bytes (priv + 32, 224); BN_free (key); BIGNUM * x = BN_new (), * y = BN_new (); - EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, NULL); + EC_POINT_get_affine_coordinates (curve, p, x, y, NULL); bn2buf (x, pub, 32); bn2buf (y, pub + 32, 32); RAND_bytes (pub + 64, 192); @@ -102,7 +102,7 @@ namespace crypto m_PublicKey = EC_POINT_new (curve->GetGroup ()); BIGNUM * x = BN_bin2bn (pub, 32, nullptr); BIGNUM * y = BN_bin2bn (pub + 32, 32, nullptr); - if (!EC_POINT_set_affine_coordinates_GFp (curve->GetGroup (), m_PublicKey, x, y, nullptr)) + if (!EC_POINT_set_affine_coordinates (curve->GetGroup (), m_PublicKey, x, y, nullptr)) LogPrint (eLogError, "ECICS GOST R 34.10 invalid public key"); BN_free (x); BN_free (y); } @@ -146,7 +146,7 @@ namespace crypto RAND_bytes (priv + 32, 224); BN_free (key); BIGNUM * x = BN_new (), * y = BN_new (); - EC_POINT_get_affine_coordinates_GFp (curve->GetGroup (), p, x, y, NULL); + EC_POINT_get_affine_coordinates (curve->GetGroup (), p, x, y, NULL); bn2buf (x, pub, 32); bn2buf (y, pub + 32, 32); RAND_bytes (pub + 64, 192); @@ -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..099bdd56 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 256; // actual size is 32, but we use 256 for compatibility with old keys files + 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..eff2533a 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 * @@ -18,8 +18,10 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): - m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) + DatagramDestination::DatagramDestination (std::shared_ptr owner, + bool gzip, DatagramVersion version): + m_Owner (owner), m_DefaultReceiver (nullptr), m_DefaultRawReceiver (nullptr), + m_Gzip (gzip), m_Version (version) { if (m_Gzip) m_Deflator.reset (new i2p::data::GzipDeflator); @@ -58,25 +60,56 @@ namespace datagram { if (session) { - if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + std::shared_ptr msg; + switch (session->GetVersion ()) { - uint8_t hash[32]; - SHA256(payload, len, hash); - m_Owner->Sign (hash, 32, m_Signature.data ()); - } - else - m_Owner->Sign (payload, len, m_Signature.data ()); + case eDatagramV3: + { + constexpr uint8_t flags[] = { 0x00, 0x03 }; // datagram3, no options + msg = CreateDataMessage ({{m_Owner->GetIdentity ()->GetIdentHash (), 32}, + {flags, 2}, {payload, len}}, fromPort, toPort, i2p::client::PROTOCOL_TYPE_DATAGRAM3, false); // datagram3 + break; + } + case eDatagramV1: + { + if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + { + uint8_t hash[32]; + SHA256(payload, len, hash); + m_Owner->Sign (hash, 32, m_Signature.data ()); + } + else + m_Owner->Sign (payload, len, m_Signature.data ()); + msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, + fromPort, toPort, i2p::client::PROTOCOL_TYPE_DATAGRAM, !session->IsRatchets ()); // datagram1 + break; + } + case eDatagramV2: + { + constexpr uint8_t flags[] = { 0x00, 0x02 }; // datagram2, no options + // signature + std::vector signedData (len + 32 + 2); + memcpy (signedData.data (), m_Owner->GetIdentity ()->GetIdentHash (), 32); + memcpy (signedData.data () + 32, flags, 2); + memcpy (signedData.data () + 34, payload, len); + m_Owner->Sign (signedData.data (), signedData.size (), m_Signature.data ()); + // TODO: offline signatures and options + msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {flags, 2}, {payload, len}, + {m_Signature.data (), m_Signature.size ()}}, fromPort, toPort, i2p::client::PROTOCOL_TYPE_DATAGRAM2, false); // datagram2 - auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, - fromPort, toPort, false, !session->IsRatchets ()); // datagram - session->SendMsg(msg); + break; + } + default: + LogPrint (eLogError, "Datagram: datagram type ", (int)session->GetVersion (), " is not supported"); + } + if (msg) session->SendMsg(msg); } } void DatagramDestination::SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { if (session) - session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw + session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, i2p::client::PROTOCOL_TYPE_RAW, !session->IsRatchets ())); // raw } void DatagramDestination::FlushSendQueue (std::shared_ptr session) @@ -85,27 +118,50 @@ namespace datagram session->FlushSendQueue (); } - void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, + const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) { i2p::data::IdentityEx identity; size_t identityLen = identity.FromBuffer (buf, len); + if (!identityLen) return; const uint8_t * signature = buf + identityLen; size_t headerLen = identityLen + identity.GetSignatureLen (); + std::shared_ptr ls; bool verified = false; - if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (from) { - uint8_t hash[32]; - SHA256(buf + headerLen, len - headerLen, hash); - verified = identity.Verify (hash, 32, signature); - } - else - verified = identity.Verify (buf + headerLen, len - headerLen, signature); + ls = m_Owner->FindLeaseSet (identity.GetIdentHash ()); + if (ls) + { + uint8_t staticKey[32]; + ls->Encrypt (nullptr, staticKey); + if (!memcmp (from->GetRemoteStaticKey (), staticKey, 32)) + verified = true; + else + { + LogPrint (eLogError, "Datagram: Remote LeaseSet static key mismatch for datagram from ", + identity.GetIdentHash ().ToBase32 ()); + return; + } + } + } + if (!verified) + { + if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + { + uint8_t hash[32]; + SHA256(buf + headerLen, len - headerLen, hash); + verified = identity.Verify (hash, 32, signature); + } + else + verified = identity.Verify (buf + headerLen, len - headerLen, signature); + } if (verified) { - auto h = identity.GetIdentHash(); - auto session = ObtainSession(h); + auto session = ObtainSession (identity.GetIdentHash()); + if (ls) session->SetRemoteLeaseSet (ls); session->Ack(); auto r = FindReceiver(toPort); if(r) @@ -119,33 +175,234 @@ 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::HandleDatagram2 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) + { + if (len < 433) + { + LogPrint (eLogWarning, "Datagram: datagram2 is too short ", len); + return; + } + i2p::data::IdentityEx identity; + size_t identityLen = identity.FromBuffer (buf, len); + if (!identityLen) return; + size_t signatureLen = identity.GetSignatureLen (); + if (signatureLen + identityLen > len) return; + + std::shared_ptr ls; + bool verified = false; + if (from) + { + ls = m_Owner->FindLeaseSet (identity.GetIdentHash ()); + if (ls) + { + uint8_t staticKey[32]; + ls->Encrypt (nullptr, staticKey); + if (!memcmp (from->GetRemoteStaticKey (), staticKey, 32)) + verified = true; + else + { + LogPrint (eLogError, "Datagram: Remote LeaseSet static key mismatch for datagram2 from ", + identity.GetIdentHash ().ToBase32 ()); + return; + } + } + } + uint16_t flags = bufbe16toh (buf + identityLen); + size_t offset = identityLen + 2; + if (flags & DATAGRAM2_FLAG_OPTIONS) + offset += bufbe16toh (buf + offset) + 2; + if (offset > len) + { + LogPrint (eLogWarning, "Datagram: datagram2 is too short ", len, " expected ", offset); + return; + } + if (!verified) + { + std::shared_ptr transientVerifier; + if (flags & DATAGRAM2_FLAG_OFFLINE_SIGNATURE) + { + transientVerifier = i2p::data::ProcessOfflineSignature (&identity, buf, len, offset); + if (!transientVerifier) + { + LogPrint (eLogWarning, "Datagram: datagram2 offline signature failed"); + return; + } + signatureLen = transientVerifier->GetSignatureLen (); + } + std::vector signedData (len + 32 - identityLen - signatureLen); + memcpy (signedData.data (), identity.GetIdentHash (), 32); + memcpy (signedData.data () + 32, buf + identityLen, signedData.size () - 32); + verified = transientVerifier ? transientVerifier->Verify (signedData.data (), signedData.size (), buf + len - signatureLen) : + identity.Verify (signedData.data (), signedData.size (), buf + len - signatureLen); + if (!verified) + { + LogPrint (eLogWarning, "Datagram: datagram2 signature verification failed"); + return; + } + } + + auto session = ObtainSession (identity.GetIdentHash()); + session->SetVersion (eDatagramV2); + if (ls) session->SetRemoteLeaseSet (ls); + session->Ack(); + auto r = FindReceiver(toPort); + if(r) + r(identity, fromPort, toPort, buf + offset, len - offset - signatureLen); + else + LogPrint (eLogWarning, "DatagramDestination: no receiver for port ", toPort); + } + + void DatagramDestination::HandleDatagram3 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) + { + if (len < 34) + { + LogPrint (eLogWarning, "Datagram: datagram3 is too short ", len); + return; + } + if (from) + { + i2p::data::IdentHash ident(buf); + auto ls = m_Owner->FindLeaseSet (ident); + if (ls) + { + uint8_t staticKey[32]; + ls->Encrypt (nullptr, staticKey); + if (!memcmp (from->GetRemoteStaticKey (), staticKey, 32)) + { + auto session = ObtainSession (ident); + session->SetVersion (eDatagramV3); + session->SetRemoteLeaseSet (ls); + session->Ack (); + auto r = FindReceiver(toPort); + if (r) + { + uint16_t flags = bufbe16toh (buf + 32); + size_t offset = 34; + if (flags & DATAGRAM3_FLAG_OPTIONS) + offset += bufbe16toh (buf + offset) + 2; + if (offset > len) + { + LogPrint (eLogWarning, "Datagram: datagram3 is too short ", len, " expected ", offset); + return; + } + r(*ls->GetIdentity (), fromPort, toPort, buf + offset, len - offset); + } + else + LogPrint (eLogWarning, "Datagram: no receiver for port ", toPort); + } + else + LogPrint (eLogError, "Datagram: Remote LeaseSet static key mismatch for datagram3 from ", ident.ToBase32 ()); + } + else + LogPrint (eLogError, "Datagram: No remote LeaseSet for ", ident.ToBase32 ()); + } + else + LogPrint (eLogInfo, "Datagram: datagram3 received from non-ratchets session"); + } + + 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; } - void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw) + 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; + } + + void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, + const uint8_t * buf, size_t len, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from) { // unzip it uint8_t uncompressed[MAX_DATAGRAM_SIZE]; size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE); if (uncompressedLen) { - if (isRaw) - HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen); - else - HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); + switch (protocolType) + { + case i2p::client::PROTOCOL_TYPE_RAW: + HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen); + break; + case i2p::client::PROTOCOL_TYPE_DATAGRAM3: + HandleDatagram3 (fromPort, toPort, uncompressed, uncompressedLen, from); + break; + case i2p::client::PROTOCOL_TYPE_DATAGRAM: + HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen, from); + break; + case i2p::client::PROTOCOL_TYPE_DATAGRAM2: + HandleDatagram2 (fromPort, toPort, uncompressed, uncompressedLen, from); + break; + default: + LogPrint (eLogInfo, "Datagram: unknown protocol type ", protocolType); + }; } else LogPrint (eLogWarning, "Datagram: decompression failed"); @@ -153,7 +410,7 @@ namespace datagram std::shared_ptr DatagramDestination::CreateDataMessage ( const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) + uint16_t fromPort, uint16_t toPort, uint8_t protocolType, bool checksum) { size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); @@ -169,8 +426,8 @@ namespace datagram { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, fromPort); // source port - htobe16buf (buf + 6, toPort); // destination port - buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol + htobe16buf (buf + 6, toPort); // destination port + buf[9] = protocolType; // raw or datagram protocol msg->len += size + 4; msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } @@ -205,14 +462,16 @@ namespace datagram std::shared_ptr session = nullptr; std::lock_guard lock(m_SessionsMutex); auto itr = m_Sessions.find(identity); - if (itr == m_Sessions.end()) { + if (itr == m_Sessions.end()) + { // not found, create new session session = std::make_shared(m_Owner, identity); + session->SetVersion (m_Version); session->Start (); - m_Sessions[identity] = session; - } else { + m_Sessions.emplace (identity, session); + } + else session = itr->second; - } return session; } @@ -228,9 +487,8 @@ namespace datagram DatagramSession::DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent) : - m_LocalDestination(localDestination), - m_RemoteIdent(remoteIdent), - m_RequestingLS(false) + m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), + m_LastUse (0), m_LastFlush (0), m_RequestingLS (false), m_Version (eDatagramV1) { } @@ -250,8 +508,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 +546,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 +579,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 +619,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 +652,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 +705,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 8f3d5ceb..f962d402 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.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 "LeaseSet.h" #include "I2NPProtocol.h" #include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -31,8 +32,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 @@ -43,7 +42,20 @@ 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 + enum DatagramVersion + { + eDatagramV1 = 1, + eDatagramV2 = 2, + eDatagramV3 = 3, + }; + + constexpr uint16_t DATAGRAM2_FLAG_OPTIONS = 0x10; + constexpr uint16_t DATAGRAM2_FLAG_OFFLINE_SIGNATURE = 0x20; + constexpr uint16_t DATAGRAM3_FLAG_OPTIONS = 0x10; + class DatagramSession : public std::enable_shared_from_this { @@ -64,8 +76,12 @@ namespace datagram /** get the last time in milliseconds for when we used this datagram session */ uint64_t LastActivity() const { return m_LastUse; } - bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } + bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } + void SetRemoteLeaseSet (std::shared_ptr ls) { m_RemoteLeaseSet = ls; } + DatagramVersion GetVersion () const { return m_Version; } + void SetVersion (DatagramVersion version) { m_Version = version; } + struct Info { std::shared_ptr IBGW; @@ -98,8 +114,9 @@ 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; + DatagramVersion m_Version; }; typedef std::shared_ptr DatagramSession_ptr; @@ -112,7 +129,7 @@ namespace datagram public: - DatagramDestination (std::shared_ptr owner, bool gzip); + DatagramDestination (std::shared_ptr owner, bool gzip, DatagramVersion version); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); @@ -124,16 +141,14 @@ namespace datagram void SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void FlushSendQueue (std::shared_ptr session); - void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, + const uint8_t * buf, size_t len, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from); - void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; - void ResetReceiver () { m_Receiver = nullptr; }; + void SetReceiver (const Receiver& receiver, uint16_t port); + void ResetReceiver (uint16_t port); - 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 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); @@ -145,25 +160,37 @@ namespace datagram std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); std::shared_ptr CreateDataMessage (const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true); + uint16_t fromPort, uint16_t toPort, uint8_t protocolType, bool checksum = true); - void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); + void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from); void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleDatagram2 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from); + void HandleDatagram3 (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from); - /** 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 + DatagramVersion m_Version; // default for destination 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 5d6b71b9..ae6e46ef 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,8 +11,10 @@ #include #include #include +#include #include #include "Crypto.h" +#include "ECIESX25519AEADRatchetSession.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" @@ -23,7 +25,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 +39,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 { @@ -91,10 +94,8 @@ namespace client } it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); if (it != params->end ()) - { - // oveeride isPublic - m_IsPublic = (it->second != "true"); - } + // override isPublic + m_IsPublic = GetBoolParamValue (it->second); it = params->find (I2CP_PARAM_LEASESET_TYPE); if (it != params->end ()) m_LeaseSetType = std::stoi(it->second); @@ -108,7 +109,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 +118,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 +132,7 @@ namespace client LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } SetNumTags (numTags); - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -164,7 +168,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)); } @@ -183,6 +187,24 @@ namespace client CleanUp (); // GarlicDestination } + bool LeaseSetDestination::GetBoolParamValue (std::string_view value) + { + bool ret = false; + if (value == "true") + ret = true; + else if (value == "false") + ret = false; + else + { + int v = 0; + auto res = std::from_chars(value.data(), value.data() + value.size(), v); + if (res.ec != std::errc()) + LogPrint (eLogError, "Destination: Unable to parse bool param value ", value, ": ", std::make_error_code (res.ec).message ()); + ret = v; + } + return ret; + } + bool LeaseSetDestination::Reconfigure(std::map params) { auto itr = params.find("i2cp.dontPublishLeaseSet"); @@ -191,7 +213,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 +284,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 +312,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 +340,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 +357,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,35 +365,57 @@ 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) { case eI2NPData: - HandleDataMessage (payload, len); + HandleDataMessage (payload, len, from); 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,7 +430,8 @@ 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) { @@ -418,6 +452,7 @@ namespace client } 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 @@ -432,7 +467,7 @@ namespace client leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) { - leaseSet->Update (buf + offset, len - offset); + leaseSet->Update (buf + offset, len - offset, shared_from_this(), true); if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else @@ -451,13 +486,29 @@ 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, shared_from_this (), + 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 ()) { LogPrint (eLogDebug, "Destination: New remote LeaseSet added"); - m_RemoteLeaseSets[key] = leaseSet; + m_RemoteLeaseSets.insert_or_assign (key, leaseSet); + if (from) + from->SetDestination (key); } else LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped"); @@ -473,34 +524,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 () && !ls2->IsExpired ()) + request = it2->second; + m_LeaseSetRequests.erase (it2); + if (request->requestedBlindedKey) { - 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 + auto ls2 = std::make_shared (buf + offset, len - offset, + request->requestedBlindedKey, shared_from_this (), + 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 - LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed"); + { + // 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); } } @@ -513,38 +590,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) @@ -553,7 +635,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)); } @@ -561,9 +644,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 () @@ -589,12 +675,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"); @@ -605,26 +686,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; } } @@ -632,10 +726,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; } @@ -645,22 +748,9 @@ namespace client { if (m_PublishReplyToken) { + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); 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"); - Publish (); - } - else - { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds 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.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, - shared_from_this (), std::placeholders::_1)); - - } + Publish (); } } } @@ -711,10 +801,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; } @@ -723,7 +813,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 (); @@ -731,17 +821,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 ()) @@ -761,7 +851,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) { @@ -769,7 +859,7 @@ namespace client request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2 if (requestComplete) request->requestComplete.push_back (requestComplete); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ret = m_LeaseSetRequests.insert (std::pair >(dest,request)); if (ret.second) // inserted { @@ -833,9 +923,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 { @@ -843,7 +942,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)); } @@ -860,7 +959,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); @@ -897,7 +996,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)); } @@ -911,7 +1011,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 @@ -919,20 +1019,17 @@ 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_ReadyChecker(service) + 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_StreamingMaxWindowSize (i2p::stream::MAX_WINDOW_SIZE), + m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_IsStreamingDontSign (DEFAULT_DONT_SIGN), + 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) SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // offline keys can be published with LS2 only @@ -951,7 +1048,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) { @@ -962,29 +1067,24 @@ 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 (), +#if OPENSSL_PQ + i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD, +#endif + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4 or 0,6,4 if post quantum 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 ()) @@ -998,10 +1098,29 @@ 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); + it = params->find (I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS); + if (it != params->end ()) + m_StreamingMaxConcurrentStreams = std::stoi(it->second); + it = params->find (I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE); + if (it != params->end ()) + { + m_StreamingMaxWindowSize = std::stoi(it->second); + if (m_StreamingMaxWindowSize < i2p::stream::MIN_WINDOW_SIZE) + m_StreamingMaxWindowSize = i2p::stream::MIN_WINDOW_SIZE; + } it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end ()) - m_IsStreamingAnswerPings = (it->second == "true"); - + m_IsStreamingAnswerPings = GetBoolParamValue (it->second); + it = params->find (I2CP_PARAM_STREAMING_DONT_SIGN); + if (it != params->end ()) + m_IsStreamingDontSign = GetBoolParamValue (it->second); + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet @@ -1014,12 +1133,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; } } @@ -1028,7 +1147,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()); } } @@ -1047,25 +1166,34 @@ 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) + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) { uint32_t length = bufbe32toh (buf); if(length > len - 4) @@ -1082,33 +1210,35 @@ 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, from); else LogPrint (eLogError, "Destination: Missing streaming destination"); } break; case PROTOCOL_TYPE_DATAGRAM: + case PROTOCOL_TYPE_RAW: + case PROTOCOL_TYPE_DATAGRAM2: + case PROTOCOL_TYPE_DATAGRAM3: // datagram protocol if (m_DatagramDestination) - m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); + m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, buf[9], from); else LogPrint (eLogError, "Destination: Missing datagram destination"); break; - case PROTOCOL_TYPE_RAW: - // raw datagram - if (m_DatagramDestination) - m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, true); - else - LogPrint (eLogError, "Destination: Missing raw datagram destination"); - break; default: LogPrint (eLogError, "Destination: Data: Unexpected protocol ", buf[9]); } } - 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) { @@ -1119,7 +1249,7 @@ namespace client if (leaseSet) { auto stream = CreateStream (leaseSet, port); - GetService ().post ([streamRequestComplete, stream]() + boost::asio::post (GetService (), [streamRequestComplete, stream]() { streamRequestComplete(stream); }); @@ -1138,7 +1268,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) { @@ -1157,7 +1287,7 @@ namespace client } template - std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, int port) + std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, uint16_t port) { volatile bool done = false; std::shared_ptr stream; @@ -1181,17 +1311,17 @@ namespace client return stream; } - std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port) + 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, int 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, int port) + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, uint16_t port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); @@ -1228,7 +1358,7 @@ namespace client }); } - std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + std::shared_ptr ClientDestination::GetStreamingDestination (uint16_t port) const { if (port) { @@ -1236,8 +1366,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) @@ -1265,7 +1396,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) @@ -1275,7 +1406,7 @@ namespace client return dest; } - std::shared_ptr ClientDestination::RemoveStreamingDestination (int port) + std::shared_ptr ClientDestination::RemoveStreamingDestination (uint16_t port) { if (port) { @@ -1290,10 +1421,11 @@ namespace client return nullptr; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip, + i2p::datagram::DatagramVersion version) { - if (m_DatagramDestination == nullptr) - m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); + if (!m_DatagramDestination) + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip, version); return m_DatagramDestination; } @@ -1311,32 +1443,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) @@ -1344,9 +1500,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 ()); } @@ -1356,18 +1513,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); } @@ -1379,11 +1558,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; @@ -1391,14 +1581,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) @@ -1413,7 +1615,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)); } } } @@ -1432,6 +1634,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 9e2d171f..f7ba269f 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,17 @@ 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 uint8_t PROTOCOL_TYPE_DATAGRAM2 = 19; + const uint8_t PROTOCOL_TYPE_DATAGRAM3 = 20; + 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 +89,22 @@ 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; + const char I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE[] = "i2p.streaming.maxWindowSize"; + const char I2CP_PARAM_STREAMING_DONT_SIGN[] = "i2p.streaming.dontSign"; + const int DEFAULT_DONT_SIGN = false; + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, @@ -95,8 +114,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 +132,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 +152,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 +168,21 @@ 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; }; + static bool GetBoolParamValue (std::string_view value); 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 HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) = 0; virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; - + private: void UpdateLeaseSet (); @@ -169,31 +191,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 +237,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,15 +255,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 (const i2p::data::IdentHash& dest, int port = 0); // sync - std::shared_ptr CreateStream (std::shared_ptr dest, int port = 0); // sync - 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); @@ -257,48 +271,60 @@ 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; } + bool IsStreamingDontSign () const { return m_IsStreamingDontSign; } + int GetStreamingMaxWindowSize () const { return m_StreamingMaxWindowSize; } // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); + i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true, + i2p::datagram::DatagramVersion version = i2p::datagram::eDatagramV1); // 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, i2p::garlic::ECIESX25519AEADRatchetSession * from) 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, int port); + 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; - bool m_IsStreamingAnswerPings; + std::map > m_EncryptionKeys; // last is most preferable + i2p::data::CryptoKeyType m_PreferredCryptoType; + + int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams, m_StreamingMaxWindowSize; + bool m_IsStreamingAnswerPings, m_IsStreamingDontSign; 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..fea10cd9 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 delayed NSR's might 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..5fa947b7 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,42 @@ 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)); } - + const i2p::data::IdentHash * GetDestinationPtr () const { return m_Destination ? m_Destination.get () : nullptr; }; // for pongs 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 +226,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 +274,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..55d7711d 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,12 +1,12 @@ /* -* 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 * * See full license text in LICENSE file at top of project tree */ -#include +#include #include "Log.h" #include "Crypto.h" #include "Ed25519.h" @@ -134,22 +134,27 @@ namespace crypto { BN_CTX * bnCtx = BN_CTX_new (); // calculate r - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key - SHA512_Update (&ctx, buf, len); // data + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestInit_ex (ctx, EVP_sha512(), NULL); + EVP_DigestUpdate (ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key + EVP_DigestUpdate (ctx, buf, len); // data uint8_t digest[64]; - SHA512_Final (digest, &ctx); + unsigned int dl = 64; + EVP_DigestFinal_ex (ctx, digest, &dl); + EVP_MD_CTX_destroy (ctx); BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors // calculate R uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors // calculate S - SHA512_Init (&ctx); - SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); + ctx = EVP_MD_CTX_create (); + EVP_DigestInit_ex (ctx, EVP_sha512(), NULL); + EVP_DigestUpdate (ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R + EVP_DigestUpdate (ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + EVP_DigestUpdate (ctx, buf, len); // data + dl = 64; + EVP_DigestFinal_ex (ctx, digest, &dl); + EVP_MD_CTX_destroy (ctx); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key @@ -169,13 +174,15 @@ namespace crypto uint8_t T[80]; RAND_bytes (T, 80); // calculate r = H*(T || publickey || data) - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, T, 80); - SHA512_Update (&ctx, publicKeyEncoded, 32); - SHA512_Update (&ctx, buf, len); // data + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestInit_ex (ctx, EVP_sha512(), NULL); + EVP_DigestUpdate (ctx, T, 80); + EVP_DigestUpdate (ctx, publicKeyEncoded, 32); + EVP_DigestUpdate (ctx, buf, len); // data uint8_t digest[64]; - SHA512_Final (digest, &ctx); + unsigned int dl = 64; + EVP_DigestFinal_ex (ctx, digest, &dl); + EVP_MD_CTX_destroy (ctx); BIGNUM * r = DecodeBN<64> (digest); BN_mod (r, r, l, bnCtx); // % l EncodeBN (r, digest, 32); @@ -183,11 +190,14 @@ namespace crypto uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // calculate S - SHA512_Init (&ctx); - SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); + ctx = EVP_MD_CTX_create (); + EVP_DigestInit_ex (ctx, EVP_sha512(), NULL); + EVP_DigestUpdate (ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R + EVP_DigestUpdate (ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + EVP_DigestUpdate (ctx, buf, len); // data + dl = 64; + EVP_DigestFinal_ex (ctx, digest, &dl); + EVP_MD_CTX_destroy (ctx); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l BIGNUM * a = DecodeBN (privateKey); @@ -413,7 +423,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 +467,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..3a4e8890 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,29 @@ 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 group = EC_KEY_get0_group (ecKey); - if (group) + auto pkey = X509_get_pubkey (cert); + if (pkey) + { + int curve = 0; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + char groupName[20]; + if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1) + curve = OBJ_txt2nid (groupName); + else + curve = -1; +#endif + if (!curve || curve == NID_X9_62_prime256v1) + { + if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second) { - 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"); + EVP_PKEY_free (pkey); + LogPrint (eLogError, "Family: Duplicated family name ", cn); } - EC_KEY_free (ecKey); } - break; - } - default: - LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } } - EVP_PKEY_free (pkey); - if (verifier && cn) - m_SigningKeys.emplace (cn, std::make_pair(verifier, m_SigningKeys.size () + 1)); } SSL_free (ssl); } @@ -121,23 +103,37 @@ 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 == 64) + { + ECDSA_SIG * sig = ECDSA_SIG_new(); + ECDSA_SIG_set0 (sig, BN_bin2bn (signatureBuf, 32, NULL), BN_bin2bn (signatureBuf + 32, 32, NULL)); + uint8_t sign[72]; + uint8_t * s = sign; + auto l = i2d_ECDSA_SIG (sig, &s); + ECDSA_SIG_free(sig); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit (ctx, NULL, EVP_sha256(), NULL, it->second.first); + auto ret = EVP_DigestVerify (ctx, sign, l, buf, len) == 1; + EVP_MD_CTX_destroy (ctx); + return ret; + } + } // TODO: process key return true; } @@ -160,34 +156,40 @@ namespace data { SSL * ssl = SSL_new (ctx); EVP_PKEY * pkey = SSL_get_privatekey (ssl); - EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); - if (ecKey) + int curve = 0; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + char groupName[20]; + if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1) + curve = OBJ_txt2nid (groupName); + else + curve = -1; +#endif + if (!curve || curve == NID_X9_62_prime256v1) { - 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 signingPrivateKey[32], buf[50], signature[64]; - i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32); - i2p::crypto::ECDSAP256Signer signer (signingPrivateKey); - size_t len = family.length (); - memcpy (buf, family.c_str (), len); - 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; - } - else - LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); - } - } + uint8_t buf[100], sign[72], signature[64]; + size_t len = family.length (); + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + + size_t l = 72; + EVP_MD_CTX * mdctx = EVP_MD_CTX_create (); + EVP_DigestSignInit (mdctx, NULL, EVP_sha256(), NULL, pkey); + EVP_DigestSign (mdctx, sign, &l, buf, len); + EVP_MD_CTX_destroy (mdctx); + + const uint8_t * s1 = sign; + ECDSA_SIG * sig1 = d2i_ECDSA_SIG (NULL, &s1, l); + const BIGNUM * r, * s; + ECDSA_SIG_get0 (sig1, &r, &s); + i2p::crypto::bn2buf (r, signature, 32); + i2p::crypto::bn2buf (s, signature + 32, 32); + ECDSA_SIG_free(sig1); + sig = ByteStreamToBase64 (signature, 64); + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + SSL_free (ssl); } else 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..a61359f1 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 * @@ -28,7 +28,7 @@ namespace garlic { GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, bool attachLeaseSet): m_Owner (owner), m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), - m_LeaseSetUpdateMsgID (0) + m_LeaseSetUpdateMsgID (0), m_IsWithJava (false), m_NumSentPackets (0) { } @@ -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..1fdffdae 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> { @@ -87,9 +87,9 @@ namespace garlic { std::shared_ptr outboundTunnel; std::shared_ptr remoteLease; + // for streaming only int rtt; // RTT uint32_t updateTime; // seconds since epoch - int numTimesUsed; }; class GarlicDestination; @@ -111,13 +111,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; @@ -130,6 +131,12 @@ namespace garlic std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); + bool IsWithJava () const { return m_IsWithJava; } + void SetIsWithJava (bool isWithJava) { m_IsWithJava = isWithJava; } + + int NumSentPackets () const { return m_NumSentPackets; } + void SetNumSentPackets (int numSentPackets) { m_NumSentPackets = numSentPackets; } + GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } @@ -152,6 +159,8 @@ namespace garlic uint64_t m_LeaseSetSubmissionTime; // in milliseconds std::shared_ptr m_SharedRoutingPath; + bool m_IsWithJava; // based on choked value from streaming + int m_NumSentPackets; // for limit number of sent messages in streaming public: @@ -206,6 +215,7 @@ namespace garlic std::map > m_UnconfirmedTagsMsgs; // msgID->tags i2p::crypto::CBCEncryption m_Encryption; + i2p::data::Tag<16> m_IV; public: @@ -221,7 +231,7 @@ namespace garlic struct ECIESX25519AEADRatchetIndexTagset { int index; - ReceiveRatchetTagSetPtr tagset; + ReceiveRatchetTagSetPtr tagset; // null if used }; class GarlicDestination: public i2p::data::LocalDestination @@ -236,11 +246,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 +266,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 +308,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/Gost.cpp b/libi2pd/Gost.cpp index 2dafc9ae..6bce3428 100644 --- a/libi2pd/Gost.cpp +++ b/libi2pd/Gost.cpp @@ -27,7 +27,7 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); m_Group = EC_GROUP_new_curve_GFp (p, a, b, ctx); EC_POINT * P = EC_POINT_new (m_Group); - EC_POINT_set_affine_coordinates_GFp (m_Group, P, x, y, ctx); + EC_POINT_set_affine_coordinates (m_Group, P, x, y, ctx); EC_GROUP_set_generator (m_Group, P, q, nullptr); EC_GROUP_set_curve_name (m_Group, NID_id_GostR3410_2001); EC_POINT_free(P); @@ -50,13 +50,13 @@ namespace crypto bool GOSTR3410Curve::GetXY (const EC_POINT * p, BIGNUM * x, BIGNUM * y) const { - return EC_POINT_get_affine_coordinates_GFp (m_Group, p, x, y, nullptr); + return EC_POINT_get_affine_coordinates (m_Group, p, x, y, nullptr); } EC_POINT * GOSTR3410Curve::CreatePoint (const BIGNUM * x, const BIGNUM * y) const { EC_POINT * p = EC_POINT_new (m_Group); - EC_POINT_set_affine_coordinates_GFp (m_Group, p, x, y, nullptr); + EC_POINT_set_affine_coordinates (m_Group, p, x, y, nullptr); return p; } @@ -112,7 +112,7 @@ namespace crypto BN_CTX_start (ctx); EC_POINT * C = EC_POINT_new (m_Group); // C = k*P = (rx, ry) EC_POINT * Q = nullptr; - if (EC_POINT_set_compressed_coordinates_GFp (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) + if (EC_POINT_set_compressed_coordinates (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) { EC_POINT * S = EC_POINT_new (m_Group); // S = s*P EC_POINT_mul (m_Group, S, s, nullptr, nullptr, ctx); diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 35676f5c..4be8684c 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -139,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); diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 7cc87df8..8c7c8491 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 = 1, 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,25 +130,32 @@ 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 */ @@ -147,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; } } @@ -183,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(); @@ -212,10 +239,18 @@ 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; @@ -233,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); } @@ -252,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; @@ -266,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])) @@ -287,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) @@ -342,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) @@ -350,7 +389,7 @@ namespace http return ""; } - size_t HTTPReq::GetNumHeaders (const std::string& name) const + size_t HTTPReq::GetNumHeaders (std::string_view name) const { size_t num = 0; for (auto& it : headers) @@ -393,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; @@ -406,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() { @@ -459,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; @@ -488,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 f9d5d009..c65c1ce4 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 { @@ -36,15 +35,16 @@ namespace http bool hasquery; std::string query; std::string frag; + bool ipv6; - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), 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 @@ -68,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); @@ -91,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(); @@ -101,8 +101,8 @@ namespace http void UpdateHeader (const std::string& name, const std::string& value); void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; - std::string GetHeader (const std::string& name) const; - size_t GetNumHeaders (const std::string& name) const; + std::string GetHeader (std::string_view name) const; + size_t GetNumHeaders (std::string_view name) const; size_t GetNumHeaders () const { return headers.size (); }; }; @@ -127,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 @@ -152,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 @@ -160,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 4d1ab6d4..e97a3596 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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,308 +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); - if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) - retCode = 30; - } - 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]; // 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 - 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); - if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) - 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, "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); @@ -708,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, @@ -746,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) @@ -798,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: { @@ -812,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: { @@ -825,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"); } } } @@ -866,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 a388dc52..39aed10f 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.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 #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,17 @@ 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_LOCAL_EXPIRATION_TIMEOUT_TRANSIT = 4000000; // 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 +164,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 +178,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 +260,6 @@ namespace tunnel SetSize (len - offset - I2NP_HEADER_SIZE); SetChks (0); } - void ToNTCP2 () { uint8_t * ntcp2 = GetNTCP2Header (); @@ -251,6 +270,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 +284,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 +292,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 +317,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 +329,8 @@ namespace tunnel private: - std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; + std::list > m_TunnelMsgs, m_TunnelGatewayMsgs; }; - - const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000; - 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 bf9c26c5..240862ae 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.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,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; } @@ -122,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"); } @@ -134,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 @@ -187,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) @@ -195,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; } @@ -212,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; } @@ -230,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 { @@ -249,8 +293,8 @@ namespace data m_ExtendedLen = 0; SHA256(buf, GetFullLen (), m_IdentHash); - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return GetFullLen (); } @@ -261,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; @@ -295,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; @@ -309,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; @@ -325,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; } @@ -338,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; } @@ -362,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: @@ -373,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) @@ -429,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); }; @@ -450,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 (); + m_SigningPrivateKey.resize (keyLen); + memcpy (m_SigningPrivateKey.data (), keys.signingPrivateKey, keyLen); m_OfflineSignature.resize (0); m_TransientSignatureLen = 0; m_TransientSigningPrivateKeyLen = 0; @@ -470,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; @@ -493,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 @@ -509,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; @@ -530,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); } @@ -551,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 ()) { @@ -562,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 @@ -609,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); } } @@ -650,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"); } @@ -663,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() @@ -690,15 +736,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)cryptoType); }; @@ -711,8 +756,10 @@ namespace data { 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]; if (isDestination) @@ -720,7 +767,7 @@ namespace data else GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); // identity - keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType); + keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey.data (), type, cryptoType); keys.CreateSigner (); return keys; @@ -745,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); @@ -761,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 @@ -775,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: @@ -799,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; @@ -820,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; @@ -833,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 d5a2da21..c95ce000 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.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,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,7 +175,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; std::shared_ptr CreateDecryptor (const uint8_t * key) const; @@ -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 d23fdcb8..3001bdfb 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 @@ -35,7 +36,7 @@ namespace data ReadFromBuffer (); } - void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature) + void LeaseSet::Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature) { SetBuffer (buf, len); ReadFromBuffer (false, verifySignature); @@ -50,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) { @@ -76,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; } @@ -280,28 +281,29 @@ namespace data LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen)); } - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, + bool storeLeases, std::shared_ptr dest, CryptoKeyType preferredCrypto): LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) { SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBufferEncrypted (buf, len, nullptr, nullptr); + ReadFromBufferEncrypted (buf, len, nullptr, dest, nullptr); else - ReadFromBuffer (buf, len); + ReadFromBuffer (buf, len, dest); } LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, - const uint8_t * secret, CryptoKeyType preferredCrypto): + std::shared_ptr dest, const uint8_t * secret, CryptoKeyType preferredCrypto): LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto) { - ReadFromBufferEncrypted (buf, len, key, secret); + ReadFromBufferEncrypted (buf, len, key, dest, secret); } - void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature) + void LeaseSet2::Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature) { SetBuffer (buf, len); if (GetStoreType () != NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBuffer (buf, len, false, verifySignature); + ReadFromBuffer (buf, len, dest, false, verifySignature); // TODO: implement encrypted } @@ -311,13 +313,14 @@ namespace data return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp; } - void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature) + void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, std::shared_ptr dest, + bool readIdentity, bool verifySignature) { // 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 @@ -349,7 +352,7 @@ namespace data switch (m_StoreType) { case NETDB_STORE_TYPE_STANDARD_LEASESET2: - s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset); + s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset, dest); break; case NETDB_STORE_TYPE_META_LEASESET2: s = ReadMetaLS2TypeSpecificPart (buf + offset, len - offset); @@ -366,6 +369,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)); @@ -389,7 +394,8 @@ namespace data return verified; } - size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len) + size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len, + std::shared_ptr dest) { size_t offset = 0; // properties @@ -397,6 +403,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++; @@ -408,14 +415,23 @@ 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) && + (!dest || dest->SupportsEncryptionType (keyType))) + { + 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; } @@ -423,6 +439,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 (); @@ -433,6 +459,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 (); @@ -471,7 +502,8 @@ namespace data return offset; } - void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret) + void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, + std::shared_ptr key, std::shared_ptr dest, const uint8_t * secret) { size_t offset = 0; // blinded key @@ -574,7 +606,7 @@ namespace data m_StoreType = innerPlainText[0]; SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); // parse and verify Layer 2 - ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); + ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1, dest); } else LogPrint (eLogError, "LeaseSet2: Unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); @@ -726,25 +758,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 } @@ -805,8 +853,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 (); @@ -815,7 +863,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; @@ -835,8 +883,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 ()) @@ -851,35 +898,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..46f40cb5 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 { @@ -70,7 +76,7 @@ namespace data LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); virtual ~LeaseSet () { delete[] m_EncryptionKey; delete[] m_Buffer; }; - virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true); + virtual void Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature); virtual bool IsNewer (const uint8_t * buf, size_t len) const; void PopulateLeases (); // from buffer @@ -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,38 +154,43 @@ 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 - uint8_t GetStoreType () const { return m_StoreType; }; - uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; + 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, + std::shared_ptr dest = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, + std::shared_ptr dest = nullptr, 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 override { return m_StoreType; }; + uint32_t GetPublishedTimestamp () const override { return m_PublishedTimestamp; }; bool IsPublic () const { return m_IsPublic; }; - bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; }; - std::shared_ptr GetTransientVerifier () const { return m_TransientVerifier; }; - void Update (const uint8_t * buf, size_t len, bool verifySignature); - bool IsNewer (const uint8_t * buf, size_t len) const; + bool IsPublishedEncrypted () const override { return m_IsPublishedEncrypted; }; + std::shared_ptr GetTransientVerifier () const override { return m_TransientVerifier; }; + void Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature) override; + bool IsNewer (const uint8_t * buf, size_t len) const override; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted) const; - CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const override; + CryptoKeyType GetEncryptionType () const override { return m_EncryptionType; }; private: - void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true); - void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret); - size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len); + void ReadFromBuffer (const uint8_t * buf, size_t len, std::shared_ptr dest, + bool readIdentity = true, bool verifySignature = true); + void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, + std::shared_ptr dest, const uint8_t * secret); + size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len, std::shared_ptr dest); size_t ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len); template bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset); - uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const; + uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const override; uint64_t ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const; size_t ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const; // subcredential is subcredential + timestamp, return length of autData without flag 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 +256,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 65b00e10..76e85d4a 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -20,11 +20,12 @@ namespace log { */ static const char *g_LogLevelStr[eNumLogLevels] = { - "none", // eLogNone - "error", // eLogError - "warn", // eLogWarning - "info", // eLogInfo - "debug" // eLogDebug + "none", // eLogNone + "critical", // eLogCritical + "error", // eLogError + "warn", // eLogWarning + "info", // eLogInfo + "debug" // eLogDebug }; /** @@ -32,10 +33,11 @@ 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[] = { "\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 */ @@ -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 813ea6d5..bc18f5a6 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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::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::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds - // sign and encrypt options, use m_H as AD - uint8_t nonce[12]; - memset (nonce, 0, 12); // set nonce to zero - i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt - + // 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,13 +324,17 @@ 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 + 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,9 +400,22 @@ 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 (); - m_SendQueueSize = 0; - 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"); + } } } @@ -391,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 ()); } @@ -424,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; @@ -450,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)); } @@ -474,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 ()); @@ -482,38 +528,48 @@ 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) + m_Establisher->CreateEphemeralKey (); + 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 + ReadSomethingAndTerminate (); // probing resistance + } + void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -522,13 +578,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)); } @@ -542,31 +610,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) @@ -577,17 +659,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)); @@ -639,6 +731,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 ()); @@ -646,92 +739,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 = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () : - (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ()); - if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) - { - LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); - Terminate (); - return; - } - if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host && - (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? - memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet - memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address - { - LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); - Terminate (); - return; - } - 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 @@ -757,14 +908,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); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_Establisher->CreateEphemeralKey (); + SetLastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()); boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -807,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 { @@ -840,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); @@ -876,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; @@ -903,8 +1061,20 @@ namespace transport break; case eNTCP2BlkRouterInfo: { - LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); - i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size)); + LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); + if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1) + { + auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1); + if (newRi) + { + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) + // peer's RouterInfo update + SetRemoteIdentity (newRi->GetIdentity ()); + } + } + else + LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size); break; } case eNTCP2BlkI2NPMessage: @@ -1021,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 @@ -1045,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; @@ -1069,43 +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 (); - m_SendQueueSize = m_SendQueue.size (); + 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 @@ -1115,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) { @@ -1130,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 @@ -1141,7 +1352,8 @@ namespace transport void NTCP2Session::SendRouterInfo () { if (!IsEstablished ()) return; - auto riLen = i2p::context.GetRouterInfo ().GetBufferLen (); + 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 // DateTime block @@ -1152,7 +1364,7 @@ namespace transport m_NextSendBuffer[9] = eNTCP2BlkRouterInfo; htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size m_NextSendBuffer[12] = 0; // flag - memcpy (m_NextSendBuffer + 13, i2p::context.GetRouterInfo ().GetBuffer (), riLen); + memcpy (m_NextSendBuffer + 13, riBuffer->data (), riLen); // TODO: eliminate extra copy // padding block auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); payloadLen += paddingSize; @@ -1184,20 +1396,60 @@ 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::ReadSomethingAndTerminate () { - m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs)); + size_t len = m_Server.GetRng ()() % NTCP2_SESSION_REQUEST_MAX_SIZE; + if (len > 0 && m_Establisher) + boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, len), boost::asio::transfer_all (), + [s = shared_from_this()](const boost::system::error_code& ecode, size_t bytes_transferred) + { + s->Terminate (); + }); + else + boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + + void NTCP2Session::SendI2NPMessages (std::list >& 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 (std::move (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) { @@ -1205,18 +1457,25 @@ namespace transport GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } - m_SendQueueSize = m_SendQueue.size (); + 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) { } @@ -1227,6 +1486,7 @@ namespace transport void NTCP2Server::Start () { + m_EstablisherService.Start (); if (!IsRunning ()) { StartIOService (); @@ -1234,14 +1494,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); } @@ -1266,7 +1525,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; } @@ -1309,7 +1568,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; } @@ -1322,6 +1581,7 @@ namespace transport void NTCP2Server::Stop () { + m_EstablisherService.Stop (); { // we have to copy it because Terminate changes m_NTCP2Sessions auto ntcpSessions = m_NTCP2Sessions; @@ -1360,6 +1620,7 @@ namespace transport { // replace by new session auto s = it->second; + s->MoveSendQueue (session); m_NTCP2Sessions.erase (it); s->Terminate (); } @@ -1376,7 +1637,11 @@ namespace transport 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) @@ -1394,8 +1659,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)) { @@ -1450,33 +1716,31 @@ 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 (!i2p::util::net::IsInReservedRange(ep.address ())) + if (!i2p::transport::transports.IsInReservedRange(ep.address ())) { - if (conn) + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) { - 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"); + 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 ()); @@ -1507,27 +1771,24 @@ 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 (!i2p::util::net::IsInReservedRange(ep.address ()) || + if (!i2p::transport::transports.IsInReservedRange(ep.address ()) || i2p::util::net::IsYggdrasilAddress (ep.address ())) { - if (conn) + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) { - 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"); + 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 ()); @@ -1558,7 +1819,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)); } @@ -1620,7 +1882,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)) { @@ -1665,47 +1927,18 @@ namespace transport case eSocksProxy: { // TODO: support username/password auth etc - static const uint8_t buff[3] = {SOCKS5_VER, 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: @@ -1733,7 +1966,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) { @@ -1747,13 +1980,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 @@ -1763,7 +1995,6 @@ namespace transport LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response"); timer->cancel(); conn->Terminate(); - delete readbuff; } }); break; @@ -1773,71 +2004,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] = SOCKS5_VER; - (*buff)[1] = SOCKS5_CMD_CONNECT; - (*buff)[2] = 0x00; - - auto& ep = conn->GetRemoteEndpoint (); - if(ep.address ().is_v4 ()) - { - (*buff)[3] = SOCKS5_ATYP_IPV4; - 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] = SOCKS5_ATYP_IPV6; - 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 (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size - boost::asio::transfer_all(), - [timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred) - { - if (e) - LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message()); - else if (!(*readbuff)[1]) // succeeded - { - boost::system::error_code ec; - size_t moreBytes = conn->GetSocket ().available(ec); - if (moreBytes) // read remaining portion of reply if ipv6 received - boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec); - timer->cancel(); - conn->ClientLogin(); - return; - } - else - LogPrint(eLogError, "NTCP2: Proxy reply error ", (int)(*readbuff)[1]); - 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)); @@ -1851,5 +2017,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 ba1380c3..b50d9087 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.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,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 @@ -143,14 +146,16 @@ namespace transport void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; bool IsEstablished () const override { return m_IsEstablished; }; + i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; bool IsTerminated () const { return m_IsTerminated; }; void ClientLogin (); // Alice void ServerLogin (); // Bob void SendLocalRouterInfo (bool update) override; // after handshake or by update - void SendI2NPMessages (const std::vector >& msgs) override; - + 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,8 @@ namespace transport void SendRouterInfo (); void SendTermination (NTCP2TerminationReason reason); void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void PostI2NPMessages (std::vector > msgs); + void ReadSomethingAndTerminate (); + void PostI2NPMessages (); private: @@ -224,13 +234,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 +264,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 +298,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); @@ -284,8 +315,13 @@ namespace transport uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; + std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; - + std::mt19937 m_Rng; + EstablisherService m_EstablisherService; + i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; + i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; + public: // for HTTP/I2PControl diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 34ad5efc..cab40e43 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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,41 +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) SaveProfiles (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); - m_Floodfills.clear (); + m_Floodfills.Clear (); if (m_Thread) { m_IsRunning = false; @@ -98,137 +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 || ts + 15 < lastManageRequest) // 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 || ts + 60 < lastSave) // 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 || - ts + i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT < lastDestinationCleanup) + 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) || - ts + i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT < lastProfilesCleanup) + if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) { - if (m_PersistProfiles) PersistProfiles (); - 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 || - ts + NETDB_PUBLISH_INTERVAL < lastPublish) - { - // 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 || ts + 30 < lastExploratory) // 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) { @@ -237,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; @@ -261,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; } @@ -275,61 +253,85 @@ namespace data { bool wasFloodfill = r->IsFloodfill (); { - std::unique_lock l(m_RouterInfosMutex); + std::lock_guard l(m_RouterInfosMutex); if (!r->Update (buf, len)) { updated = false; - m_Requests.RequestComplete (ident, r); + m_Requests->RequestComplete (ident, r); return r; - } - if (r->IsUnreachable ()) + } + if (r->IsUnreachable () || + i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ()) { - // delete router as invalid after update + // delete router as invalid or from future after update m_RouterInfos.erase (ident); if (wasFloodfill) - { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.remove (r); - } - m_Requests.RequestComplete (ident, nullptr); - return nullptr; - } + { + std::lock_guard l(m_FloodfillsMutex); + m_Floodfills.Remove (r->GetIdentHash ()); + } + m_Requests->RequestComplete (ident, nullptr); + return nullptr; + } } - LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + 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 () && !r->GetProfile ()->IsUnreachable () && - i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp ()) + 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 @@ -342,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) @@ -359,11 +361,12 @@ namespace data { if(it->second->GetExpirationTime() < expires) { - it->second->Update (buf, len, false); // signature is verified already - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + it->second->Update (buf, len, nullptr, false); // signature is verified already + 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 @@ -374,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; } @@ -389,16 +393,16 @@ namespace data auto leaseSet = std::make_shared (storeType, buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { - std::unique_lock lock(m_LeaseSetsMutex); + 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 ()) { - if (leaseSet->IsPublic () && !leaseSet->IsExpired () && - i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ()) + 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; } @@ -416,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; @@ -426,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; @@ -445,16 +449,33 @@ namespace data void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable) { - auto it = m_RouterInfos.find (ident); - if (it != m_RouterInfos.end ()) + auto r = FindRouter (ident); + if (r) { - it->second->SetUnreachable (unreachable); - if (unreachable) - { - auto profile = it->second->GetProfile (); - if (profile) - profile->Unreachable (); - } + 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); } } @@ -466,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(); @@ -517,7 +516,7 @@ namespace data } // send them off - i2p::transport::transports.SendMessages(ih, requests); + i2p::transport::transports.SendMessages(ih, std::move (requests)); } bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts) @@ -530,7 +529,7 @@ namespace data if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) { if (r->IsFloodfill () && r->IsEligibleFloodfill ()) - m_Floodfills.push_back (r); + m_Floodfills.Insert (r); } } else @@ -543,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); } @@ -559,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); } @@ -571,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? @@ -614,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; @@ -622,77 +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 (); - bool isLowRate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () < NETDB_MIN_TUNNEL_CREATION_SUCCESS_RATE; + 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 { - if (it.second->GetBuffer ()) - { + 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 - it.second->SaveToFile (m_Storage.Path(ident)); - it.second->SetUnreachable (false); - it.second->DeleteBuffer (); - } - it.second->SetUpdated (false); + 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; } - if (it.second->GetProfile ()->IsUnreachable ()) - it.second->SetUnreachable (true); + 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 || isLowRate || - (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) - it.second->SetUnreachable (false); - // find & mark expired routers - if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & RouterInfo::eSSU2V4)) - // non-reachable router, but reachable by ipv4 SSU2 means introducers + if (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); - else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ()) + // 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 ()) { - LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds"); - it.second->SetUnreachable (true); - } - - if (it.second->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"); @@ -701,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 (!it->second || it->second->IsUnreachable ()) it = m_RouterInfos.erase (it); else - { + { it->second->DropProfile (); 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) @@ -813,19 +826,36 @@ namespace data LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped"); return; } - auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; - 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; } @@ -846,23 +876,31 @@ namespace data LogPrint (eLogError, "NetDb: Database store message is too long ", len); return; } - if (!m->from) // unsolicited LS must be received directly + 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 ", int(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) @@ -894,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 (); @@ -986,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; @@ -1014,31 +978,28 @@ 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 && !router->IsUnreachable ()) { @@ -1051,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); } } @@ -1109,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 @@ -1245,19 +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::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 @@ -1267,7 +1178,7 @@ namespace data }); } - std::shared_ptr NetDb::GetRandomSSU2Introducer (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 @@ -1277,17 +1188,23 @@ namespace data }); } - 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) + }); } @@ -1295,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 @@ -1354,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 () && !it->GetProfile ()->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; } @@ -1441,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 () diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index f0315582..df831be8 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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,19 +40,25 @@ namespace data { const int NETDB_MIN_ROUTERS = 90; const int NETDB_MIN_FLOODFILLS = 5; - const int NETDB_MIN_TUNNEL_CREATION_SUCCESS_RATE = 8; // in percents + const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline + const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1800; + 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_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes - const int NETDB_PUBLISH_INTERVAL = 60 * 40; - const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds - const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; - const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 - const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + 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; @@ -80,37 +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 GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const; - std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::set& excluded) const; - std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; + std::shared_ptr 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 */ @@ -123,29 +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 (); }; + 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 (); }; - boost::shared_ptr NewRouterInfoAddresses () + RouterInfo::AddressesPtr NewRouterInfoAddresses () { - return boost::shared_ptr(m_RouterInfoAddressVectorsPool.AcquireMt (), + return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (), std::bind ::*)(RouterInfo::Addresses *)> (&i2p::util::MemoryPoolMt::ReleaseMt, - &m_RouterInfoAddressVectorsPool, std::placeholders::_1)); + &m_RouterInfoAddressVectorsPool, std::placeholders::_1)}; }; std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; - - uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; + 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 (); @@ -157,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; @@ -164,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; @@ -175,24 +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 311d1c86..fe7f9905 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,12 +10,14 @@ #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 @@ -25,23 +27,21 @@ namespace data static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); static std::unordered_map > g_Profiles; static std::mutex g_ProfilesMutex; - - static boost::posix_time::ptime GetTime () - { - return boost::posix_time::second_clock::local_time(); - } + static std::list)> > > g_PostponedUpdates; + static std::mutex g_PostponedUpdatesMutex; RouterProfile::RouterProfile (): - m_LastUpdateTime (GetTime ()), m_IsUpdated (false), - m_LastDeclineTime (0), m_LastUnreachableTime (0), - m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), - m_NumTimesTaken (0), m_NumTimesRejected (0) + 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) { } void RouterProfile::UpdateTime () { - m_LastUpdateTime = GetTime (); + m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); m_IsUpdated = true; } @@ -55,9 +55,12 @@ 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); @@ -77,6 +80,7 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { + m_IsUpdated = false; std::string ident = identHash.ToBase64 (); std::string path = g_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; @@ -99,10 +103,22 @@ 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 @@ -123,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) { @@ -148,25 +166,38 @@ namespace data } 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 () + void RouterProfile::Unreachable (bool unreachable) { - m_LastUnreachableTime = i2p::util::GetSecondsSinceEpoch (); + 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 @@ -178,10 +209,9 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } - bool RouterProfile::IsDeclinedRecently () + bool RouterProfile::IsDeclinedRecently (uint64_t ts) { if (!m_LastDeclineTime) return false; - auto ts = i2p::util::GetSecondsSinceEpoch (); if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL || ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime) m_LastDeclineTime = 0; @@ -190,7 +220,10 @@ namespace data bool RouterProfile::IsBad () { - if (IsDeclinedRecently () || IsUnreachable ()) return true; + 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)) { @@ -214,92 +247,176 @@ namespace data return (bool)m_LastUnreachableTime; } + bool RouterProfile::IsUseful() const + { + return IsReal () || m_NumTunnelsNonReplied >= PEER_PROFILE_USEFUL_THRESHOLD; + } + std::shared_ptr GetRouterProfile (const IdentHash& identHash) { { 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 = std::make_shared (); + } + } + auto profile = netdb.NewRouterProfile (); profile->Load (identHash); // if possible - std::unique_lock l(g_ProfilesMutex); + std::lock_guard l(g_ProfilesMutex); g_Profiles.emplace (identHash, profile); return profile; } + bool IsRouterBanned (const IdentHash& identHash) + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second->IsUnreachable (); + return false; + } + + bool IsRouterDuplicated (const IdentHash& identHash) + { + std::lock_guard l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second->IsDuplicated (); + return false; + } + void InitProfilesStorage () { g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); } - - void PersistProfiles () + + static void SaveProfilesToDisk (std::list > >&& profiles) { - auto ts = GetTime (); - std::list > > tmp; - { - std::unique_lock l(g_ProfilesMutex); - for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) - { - if ((ts - it->second->GetLastUpdateTime ()).total_seconds () > PEER_PROFILE_PERSIST_INTERVAL) - { - if (it->second->IsUpdated ()) - tmp.push_back (std::make_pair (it->first, it->second)); - it = g_Profiles.erase (it); - } - else - it++; - } - } - for (auto& it: tmp) + 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 SaveProfiles () { std::unordered_map > tmp; { - std::unique_lock l(g_ProfilesMutex); - tmp = g_Profiles; - g_Profiles.clear (); + std::lock_guard l(g_ProfilesMutex); + std::swap (tmp, g_Profiles); } - auto ts = GetTime (); + auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto& it: tmp) - if (it.second->IsUpdated () && (ts - it.second->GetLastUpdateTime ()).total_seconds () < PEER_PROFILE_EXPIRATION_TIMEOUT*3600) + 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); + 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 >= PEER_PROFILE_EXPIRATION_TIMEOUT) + { + LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); + i2p::fs::Remove(path); + } + } } - void DeleteObsoleteProfiles () + std::future DeleteObsoleteProfiles () { { - auto ts = GetTime (); - std::unique_lock l(g_ProfilesMutex); + 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 ()).total_seconds () >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) + { + if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) it = g_Profiles.erase (it); else it++; } } - - struct stat st; - std::time_t now = std::time(nullptr); - std::vector files; - g_ProfilesStorage.Traverse(files); - for (const auto& path: files) { - if (stat(path.c_str(), &st) != 0) { - LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); - continue; - } - if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) { - LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); - i2p::fs::Remove(path); - } - } + 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 752d6190..59995b3f 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,9 @@ #define PROFILING_H__ #include -#include +#include +#include +#include #include "Identity.h" namespace i2p @@ -21,41 +23,66 @@ 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 = 36; // in hours (1.5 days) - const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 6 * 3600; // in seconds (6 hours) - const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3600; // in seconds (1 hour) - const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes) - const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes) - const int PEER_PROFILE_UNREACHABLE_INTERVAL = 2*3600; // on seconds (2 hours) - + 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 (); + void Unreachable (bool unreachable); + void Connected (); + void Duplicated (); - boost::posix_time::ptime GetLastUpdateTime () const { return m_LastUpdateTime; }; + 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: @@ -64,13 +91,13 @@ namespace data bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; bool IsLowPartcipationRate () const; bool IsLowReplyRate () const; - bool IsDeclinedRecently (); + bool IsDeclinedRecently (uint64_t ts); private: - boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono bool m_IsUpdated; - uint64_t m_LastDeclineTime, m_LastUnreachableTime; // in seconds + uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, + m_LastAccessTime, m_LastPersistTime; // in seconds // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -78,13 +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 (); - void PersistProfiles (); + 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..23dae8ff 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.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,9 @@ #include #include #include +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +#include +#endif #include #include "Crypto.h" @@ -26,6 +29,7 @@ #include "HTTP.h" #include "util.h" #include "Config.h" +#include "Socks5.h" namespace i2p { @@ -153,7 +157,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 +174,7 @@ namespace data } else { - LogPrint (eLogError, "Reseed: Can't open file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open file ", filename); return 0; } } @@ -278,7 +282,7 @@ namespace data if (verify) // not verified { - LogPrint (eLogError, "Reseed: SU3 verification failed"); + LogPrint (eLogCritical, "Reseed: SU3 verification failed"); return 0; } @@ -320,7 +324,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; @@ -479,20 +483,36 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); - const BIGNUM * n, * e, * d; + EVP_PKEY * pubKey = X509_get_pubkey (cert); + const BIGNUM * n = nullptr; +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + BIGNUM * n1 = BN_new (); + if (EVP_PKEY_get_bn_param (pubKey, OSSL_PKEY_PARAM_RSA_N, &n1) > 0) + n = n1; +#else + const RSA * key = EVP_PKEY_get0_RSA (pubKey); + const BIGNUM * e, * d; RSA_get0_key(key, &n, &e, &d); - PublicKey value; - i2p::crypto::bn2buf (n, value, 512); - if (cn) - m_SigningKeys[cn] = value; +#endif + if (n) + { + PublicKey value; + i2p::crypto::bn2buf (n, value, 512); + if (cn) + m_SigningKeys[cn] = value; + else + LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); + } else - LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); + LogPrint (eLogError, "Reseed: Can't extract RSA key from ", filename); +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + BN_free (n1); +#endif } 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 +554,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 +581,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 +617,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 +634,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 +656,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 +761,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 72b723ea..cfb92f00 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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, @@ -102,7 +142,7 @@ namespace i2p { boost::asio::ip::address addr; if (!host.empty ()) - addr = boost::asio::ip::address::from_string (host); + 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); @@ -123,7 +163,7 @@ namespace i2p { boost::asio::ip::address addr; if (!host.empty ()) - addr = boost::asio::ip::address::from_string (host); + 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); @@ -154,7 +194,7 @@ namespace i2p ntcp2Host = host; boost::asio::ip::address addr; if (!ntcp2Host.empty ()) - addr = boost::asio::ip::address::from_string (ntcp2Host); + 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); @@ -173,7 +213,7 @@ namespace i2p { boost::asio::ip::address addr; if (!host.empty ()) - addr = boost::asio::ip::address::from_string (host); + 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); @@ -198,6 +238,7 @@ namespace i2p routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + m_RouterInfo.SetUnreachable (false); } uint16_t RouterContext::SelectRandomPort () const @@ -214,8 +255,36 @@ namespace i2p 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 (); } @@ -245,10 +314,33 @@ namespace i2p fk.write ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); } + void RouterContext::SetTesting (bool testing) + { + 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; switch (m_Status) { @@ -258,9 +350,12 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; - case eRouterStatusTesting: - m_Error = eRouterErrorNone; - 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) & ~i2p::data::RouterInfo::eReachable); + break; default: ; } @@ -269,8 +364,11 @@ namespace i2p 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; switch (m_StatusV6) { @@ -280,9 +378,6 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (false, true); // ipv6 break; - case eRouterStatusTesting: - m_ErrorV6 = eRouterErrorNone; - break; default: ; } @@ -491,6 +586,12 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + { + if (m_RouterInfo.UpdateSSU2Introducer (h, v4, iTag, iExp)) + UpdateRouterInfo (); + } + void RouterContext::ClearSSU2Introducers (bool v4) { auto addr = m_RouterInfo.GetSSU2Address (v4); @@ -505,10 +606,10 @@ namespace i2p { 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); @@ -545,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 (); @@ -564,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; } @@ -577,13 +676,16 @@ 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'); } + + + LogPrint(eLogInfo, "RouterContext: Set bandwidth ", limit, ". kb/s"); m_BandwidthLimit = limit; // set precise limit } @@ -730,7 +832,7 @@ namespace i2p i2p::config::GetOption("host", ntcp2Host); if (!ntcp2Host.empty () && ntcp2Port) { - auto addr = boost::asio::ip::address::from_string (ntcp2Host); + auto addr = boost::asio::ip::make_address (ntcp2Host); if (addr.is_v6 ()) { m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); @@ -759,7 +861,7 @@ namespace i2p std::string host; i2p::config::GetOption("host", host); if (!host.empty ()) { - auto addr = boost::asio::ip::address::from_string (host); + auto addr = boost::asio::ip::make_address (host); if (addr.is_v6 ()) { m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); @@ -828,7 +930,7 @@ namespace i2p std::string host; i2p::config::GetOption("host", host); if (!host.empty ()) { - auto addr = boost::asio::ip::address::from_string (host); + auto addr = boost::asio::ip::make_address (host); if (addr.is_v4 ()) { m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); @@ -858,7 +960,7 @@ namespace i2p std::string host; i2p::config::GetOption("host", host); if (!host.empty ()) { - auto addr = boost::asio::ip::address::from_string (host); + auto addr = boost::asio::ip::make_address (host); if (addr.is_v4 ()) { m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); @@ -1058,6 +1160,8 @@ namespace i2p UpdateSSU2Keys (); updated = true; } + if (m_RouterInfo.UpdateCongestion (i2p::data::RouterInfo::eLowCongestion)) + updated = true; if (updated) UpdateRouterInfo (); @@ -1080,18 +1184,28 @@ 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 == eI2NPGarlic) + if (typeID == eI2NPTunnelTest) { - // TODO: implement - LogPrint (eLogWarning, "Router: garlic message in garlic clove. Dropped"); - return false; + // 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; @@ -1101,7 +1215,14 @@ namespace i2p 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 ()) @@ -1118,28 +1239,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 @@ -1209,4 +1354,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 9fbbb178..754c49da 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,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,16 +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, - eRouterStatusUnknown = 3, - eRouterStatusProxy = 4, - eRouterStatusMesh = 5 + 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 @@ -70,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 () @@ -87,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; }; @@ -102,10 +132,14 @@ 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); RouterError GetError () const { return m_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); RouterError GetErrorV6 () const { return m_ErrorV6; }; @@ -121,6 +155,7 @@ namespace garlic void PublishSSU2Address (int port, bool publish, bool v4, bool v6); bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4); void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4); + void UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); void ClearSSU2Introducers (bool v4); bool IsUnreachable () const; void SetUnreachable (bool v4, bool v6); @@ -134,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 (); }; @@ -141,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: @@ -179,11 +218,27 @@ namespace garlic 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; @@ -192,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 afa8cbe0..4af32f57 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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) { } @@ -95,14 +100,14 @@ 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 (); // skip identity size_t identityLen = m_RouterIdentity->GetFullLen (); // read new RI - std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen)); - ReadFromStream (str); + ReadFromBuffer (buf + identityLen, len - identityLen); if (!m_IsUnreachable) UpdateBuffer (buf, len); // save buffer // don't delete buffer until saved to the file @@ -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,49 +186,43 @@ 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 = NewAddresses (); - uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); + uint8_t numAddresses = buf[offset]; offset++; for (int i = 0; i < numAddresses; i++) { + if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date uint8_t supportedTransports = 0; auto address = NewAddress (); - uint8_t cost; // ignore - s.read ((char *)&cost, sizeof (cost)); - s.read ((char *)&address->date, sizeof (address->date)); - bool isHost = false, isStaticKey = false, isV2 = false; - char transportStyle[6]; - ReadString (transportStyle, 6, s); - if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 + 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 (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2 + else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 { address->transportStyle = eTransportSSU2; address->ssu.reset (new SSUExt ()); @@ -231,30 +232,28 @@ 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); + address->host = boost::asio::ip::make_address (value, ecode); if (!ecode && !address->host.is_unspecified ()) { - if (!i2p::util::net::IsInReservedRange (address->host) || + if (!i2p::transport::transports.IsInReservedRange (address->host) || i2p::util::net::IsYggdrasilAddress (address->host)) isHost = true; else @@ -262,56 +261,59 @@ namespace data address->transportStyle = eTransportUnknown; } } - else if (!strcmp (key, "port")) + else if (key == "port") { - try - { - address->port = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); } - else if (!strcmp (key, "mtu")) + else if (key == "mtu") { if (address->ssu) { - try - { - address->ssu->mtu = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ()); } else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); } - else if (!strcmp (key, "caps")) + else if (key == "caps") address->caps = ExtractAddressCaps (value); - else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key + else if (key == "s") // ntcp2 or ssu2 static key { - 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); + { + 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') { @@ -321,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 ()) { @@ -336,54 +336,38 @@ namespace data address->ssu->introducers.resize (index + 1); } Introducer& introducer = address->ssu->introducers.at (index); - if (!strcmp (key, "ihost")) - introducer.isH = false; // SSU1 - else if (!strcmp (key, "iport")) - introducer.isH = false; // SSU1 - else if (!strcmp (key, "itag")) + auto key1 = key.substr(0, key.length () - 1); + if (key1 == "itag") { - try - { - introducer.iTag = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ()); } - else if (!strcmp (key, "ih")) + else if (key1 == "ih") + Base64ToByteStream (value, introducer.iH, 32); + else if (key1 == "iexp") { - Base64ToByteStream (value, strlen (value), introducer.iH, 32); - introducer.isH = true; - } - else if (!strcmp (key, "iexp")) - { - try - { - introducer.iExp = boost::lexical_cast(value); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ()); - } + auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); + if (res.ec != std::errc()) + LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); } } - if (!s) return; - } + } if (address->transportStyle == eTransportNTCP2) { if (isStaticKey) { - 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; @@ -394,32 +378,23 @@ namespace data } } } - else if (address->transportStyle == eTransportSSU2 && isV2) + else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey) { if (address->IsV4 ()) supportedTransports |= eSSU2V4; if (address->IsV6 ()) supportedTransports |= eSSU2V6; 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->ssu && !address->ssu->introducers.empty ()) + else 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 && it.isH) - numValid++; - else - it.iTag = 0; - } - if (numValid) + UpdateIntroducers (address, ts); + if (!address->ssu->introducers.empty ()) // still has something m_ReachableTransports |= supportedTransports; - else - address->ssu->introducers.resize (0); } } if (supportedTransports) @@ -433,105 +408,124 @@ namespace data m_SupportedTransports |= supportedTransports; } } + m_ReachableTransports |= m_PublishedTransports; // update addresses -#if (BOOST_VERSION >= 105300) +#ifdef __cpp_lib_atomic_shared_ptr + m_Addresses = addresses; +#else boost::atomic_store (&m_Addresses, addresses); -#else - m_Addresses = addresses; // race condition #endif // read peers - uint8_t numPeers; - 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; @@ -542,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; @@ -570,11 +571,25 @@ namespace data 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; @@ -595,6 +610,19 @@ namespace data 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 @@ -603,35 +631,44 @@ namespace data 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 }; } + 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
(); @@ -645,12 +682,12 @@ namespace data if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; - (*m_Addresses)[eNTCP2V4Idx] = addr; + (*GetAddresses ())[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eNTCP2V6; - (*m_Addresses)[eNTCP2V6Idx] = addr; + (*GetAddresses ())[eNTCP2V6Idx] = addr; } } @@ -661,16 +698,22 @@ namespace data addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP2; - addr->caps = 0; 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; - (*m_Addresses)[eNTCP2V4Idx] = addr; + (*addresses)[eNTCP2V4Idx] = addr; } if (addr->IsV6 ()) { @@ -678,30 +721,31 @@ namespace data { m_SupportedTransports |= eNTCP2V6Mesh; m_ReachableTransports |= eNTCP2V6Mesh; - (*m_Addresses)[eNTCP2V6MeshIdx] = addr; + (*addresses)[eNTCP2V6MeshIdx] = addr; } else { m_SupportedTransports |= eNTCP2V6; m_ReachableTransports |= eNTCP2V6; - (*m_Addresses)[eNTCP2V6Idx] = addr; + (*addresses)[eNTCP2V6Idx] = addr; } } } void RouterInfo::RemoveNTCP2Address (bool v4) { + auto addresses = GetAddresses (); if (v4) { - if ((*m_Addresses)[eNTCP2V6Idx]) - (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eNTCP2V4Idx].reset (); + if ((*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); } else { - if ((*m_Addresses)[eNTCP2V4Idx]) - (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eNTCP2V6Idx].reset (); + if ((*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eNTCP2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -717,15 +761,16 @@ namespace data addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; - (*m_Addresses)[eSSU2V4Idx] = addr; + (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; - (*m_Addresses)[eSSU2V6Idx] = addr; + (*addresses)[eSSU2V6Idx] = addr; } } @@ -737,39 +782,48 @@ namespace data addr->host = host; addr->port = port; addr->published = true; - addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); + if (!host.is_unspecified ()) + addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; + else + { + addr->caps = 0; + if (host.is_v4 ()) addr->caps |= eV4; + if (host.is_v6 ()) addr->caps |= eV6; + } + auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; m_ReachableTransports |= eSSU2V4; - (*m_Addresses)[eSSU2V4Idx] = addr; + (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; m_ReachableTransports |= eSSU2V6; - (*m_Addresses)[eSSU2V6Idx] = addr; + (*addresses)[eSSU2V6Idx] = addr; } } void RouterInfo::RemoveSSU2Address (bool v4) { + auto addresses = GetAddresses (); if (v4) { - if ((*m_Addresses)[eSSU2V6Idx]) - (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eSSU2V4Idx].reset (); + if ((*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } else { - if ((*m_Addresses)[eSSU2V4Idx]) - (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eSSU2V6Idx].reset (); + if ((*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -810,17 +864,18 @@ namespace data { if (IsV6 ()) { - if ((*m_Addresses)[eNTCP2V6Idx]) + auto addresses = GetAddresses (); + if ((*addresses)[eNTCP2V6Idx]) { - if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx]) - (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eNTCP2V6Idx].reset (); + if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx]) + (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eNTCP2V6Idx].reset (); } - if ((*m_Addresses)[eSSU2V6Idx]) + if ((*addresses)[eSSU2V6Idx]) { - if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx]) - (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*m_Addresses)[eSSU2V6Idx].reset (); + if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx]) + (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*addresses)[eSSU2V6Idx].reset (); } UpdateSupportedTransports (); } @@ -830,17 +885,18 @@ namespace data { if (IsV4 ()) { - if ((*m_Addresses)[eNTCP2V4Idx]) + auto addresses = GetAddresses (); + if ((*addresses)[eNTCP2V4Idx]) { - if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx]) - (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eNTCP2V4Idx].reset (); + if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx]) + (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eNTCP2V4Idx].reset (); } - if ((*m_Addresses)[eSSU2V4Idx]) + if ((*addresses)[eSSU2V4Idx]) { - if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx]) - (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*m_Addresses)[eSSU2V4Idx].reset (); + if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx]) + (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*addresses)[eSSU2V4Idx].reset (); } UpdateSupportedTransports (); } @@ -861,7 +917,7 @@ namespace data { m_SupportedTransports &= ~eNTCP2V6Mesh; m_ReachableTransports &= ~eNTCP2V6Mesh; - (*m_Addresses)[eNTCP2V6MeshIdx].reset (); + (*GetAddresses ())[eNTCP2V6MeshIdx].reset (); } } @@ -890,12 +946,12 @@ namespace data return nullptr; } - boost::shared_ptr RouterInfo::GetAddresses () const + RouterInfo::AddressesPtr RouterInfo::GetAddresses () const { -#if (BOOST_VERSION >= 105300) - return boost::atomic_load (&m_Addresses); -#else +#ifdef __cpp_lib_atomic_shared_ptr return m_Addresses; +#else + return boost::atomic_load (&m_Addresses); #endif } @@ -903,10 +959,10 @@ namespace data std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator -#if (BOOST_VERSION >= 105300) +#ifdef __cpp_lib_atomic_shared_ptr + AddressesPtr addresses = m_Addresses; +#else auto addresses = boost::atomic_load (&m_Addresses); -#else - auto addresses = m_Addresses; #endif for (const auto& address : *addresses) if (address && filter (address)) return address; @@ -945,9 +1001,13 @@ namespace data 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 @@ -959,11 +1019,30 @@ namespace data bool RouterInfo::IsEligibleFloodfill () const { - // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA - return IsReachableBy (eNTCP2V4 | eSSU2V4) && 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::IsPublished (bool v4) const + { + 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; @@ -980,7 +1059,7 @@ namespace data void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { - for (auto& addr: *m_Addresses) + for (auto& addr: *GetAddresses ()) { if (addr && !addr->published) { @@ -994,7 +1073,7 @@ namespace data { m_SupportedTransports = 0; m_ReachableTransports = 0; - for (const auto& addr: *m_Addresses) + for (const auto& addr: *GetAddresses ()) { if (!addr) continue; uint8_t transports = 0; @@ -1019,15 +1098,47 @@ namespace data } } + 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 (); @@ -1038,16 +1149,55 @@ namespace data return netdb.NewRouterInfoAddress (); } - boost::shared_ptr RouterInfo::NewAddresses () const + 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 (); @@ -1085,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 @@ -1093,15 +1243,40 @@ 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 (); @@ -1138,44 +1313,37 @@ namespace data s.write ((const char *)&cost, sizeof (cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; - bool isPublished = false; + bool isPublished = address.published && !address.host.is_unspecified () && address.port; if (address.transportStyle == eTransportNTCP2) { - if (address.IsNTCP2 ()) + WriteString ("NTCP2", s); + // caps + if (!isPublished) { - 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 << ';'; - } + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + 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 << ';'; } - else - continue; // don't write NTCP address } 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_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 ()) @@ -1211,11 +1379,12 @@ 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++; @@ -1223,11 +1392,10 @@ namespace data i = 0; for (const auto& introducer: address.ssu->introducers) { - WriteString ("ih" + 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.iH, 32, value, 64); - value[l] = 0; + auto value = ByteStreamToBase64 (introducer.iH, 32); WriteString (value, properties); properties << ';'; i++; @@ -1235,9 +1403,10 @@ namespace data 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++; } @@ -1251,7 +1420,7 @@ namespace data { WriteString ("mtu", properties); properties << '='; - WriteString (boost::lexical_cast(address.ssu->mtu), properties); + WriteString (std::to_string(address.ssu->mtu), properties); properties << ';'; } } @@ -1259,7 +1428,7 @@ namespace data { WriteString ("port", properties); properties << '='; - WriteString (boost::lexical_cast(address.port), properties); + WriteString (std::to_string(address.port), properties); properties << ';'; } if (address.IsNTCP2 () || address.IsSSU2 ()) @@ -1294,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) @@ -1312,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 (); @@ -1329,11 +1514,16 @@ namespace data return std::make_shared
(); } - boost::shared_ptr LocalRouterInfo::NewAddresses () const + RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const { - return boost::make_shared (); + return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); } + std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const + { + return std::make_shared (buf, len); + } + bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) { auto addresses = GetAddresses (); @@ -1368,5 +1558,23 @@ namespace data } return false; } + + bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + { + auto addresses = GetAddresses (); + if (!addresses) return false; + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) + { + for (auto& it: addr->ssu->introducers) + if (h == it.iH) + { + it.iTag = iTag; + it.iExp = iExp; + return true; + } + } + return false; + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 1024cda8..cb3ae499 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,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,12 +44,20 @@ 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_SSU2_TESTING = 'B'; @@ -56,6 +69,9 @@ namespace data 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: @@ -93,6 +109,14 @@ namespace data eUnreachable = 0x20 }; + enum Congestion + { + eLowCongestion = 0, + eMediumCongestion, + eHighCongestion, + eRejectAll + }; + enum AddressCaps { eV4 = 0x01, @@ -110,11 +134,10 @@ namespace data struct Introducer { - Introducer (): iTag (0), iExp (0), isH (false) {}; + Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; IdentHash iH; uint32_t iTag; uint32_t iExp; - bool isH; // TODO: remove later }; struct SSUExt @@ -170,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::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 (); @@ -186,9 +221,9 @@ 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 () {}; - boost::shared_ptr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr + AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2V4Address () const; std::shared_ptr GetNTCP2V6Address () const; std::shared_ptr GetPublishedNTCP2V4Address () const; @@ -208,8 +243,10 @@ namespace data 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 IsNTCP2 (bool v4only = true) const; bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; }; @@ -228,33 +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 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 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 ? 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 DropProfile () { m_Profile = nullptr; }; + bool HasProfile () const { return (bool)m_Profile; }; bool Update (const uint8_t * buf, size_t len); - void DeleteBuffer () { m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; /** return true if we are in a router family and the signature is valid */ @@ -271,39 +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 (); CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; 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 boost::shared_ptr NewAddresses () 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 @@ -311,17 +378,19 @@ namespace data public: LocalRouterInfo () = default; - LocalRouterInfo (const std::string& fullPath): RouterInfo (fullPath) {}; 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: @@ -330,7 +399,8 @@ namespace data void WriteString (const std::string& str, std::ostream& s) const; std::shared_ptr NewBuffer () const override; std::shared_ptr
NewAddress () const override; - boost::shared_ptr NewAddresses () const override; + RouterInfo::AddressesPtr NewAddresses () const override; + std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 3773f03d..4540b4d2 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,7 +24,9 @@ namespace transport m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()), m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()), m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()), - m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false) + m_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0), + m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL), m_IsForcedFirewalled4 (false), + m_IsForcedFirewalled6 (false), m_IsThroughProxy (false) { } @@ -78,8 +80,10 @@ namespace transport if (address->IsV4 ()) { found = true; + i2p::config::GetOption ("ssu2.firewalled4", m_IsForcedFirewalled4); + 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); @@ -89,9 +93,11 @@ namespace transport if (address->IsV6 ()) { found = true; + i2p::config::GetOption ("ssu2.firewalled6", m_IsForcedFirewalled6); + 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); }); @@ -99,7 +105,7 @@ 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) @@ -149,8 +155,14 @@ namespace transport m_SessionsByRouterHash.clear (); m_PendingOutgoingSessions.clear (); m_Relays.clear (); + m_PeerTests.clear (); m_Introducers.clear (); m_IntroducersV6.clear (); + m_ConnectedRecently.clear (); + m_RequestedPeerTests.clear (); + + m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue); + m_ReceivedPacketsQueue.clear (); } void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) @@ -207,6 +219,67 @@ namespace transport 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) { boost::asio::ip::udp::socket& socket = localEndpoint.address ().is_v6 () ? m_SocketV6 : m_SocketV4; @@ -217,15 +290,49 @@ namespace transport 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; } @@ -243,10 +350,12 @@ namespace transport 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 @@ -255,22 +364,32 @@ namespace transport // 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; } @@ -281,10 +400,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 @@ -303,7 +422,7 @@ namespace transport else { auto ep = socket.local_endpoint (); - socket.close (); + LogPrint (eLogCritical, "SSU2: Reopening socket in HandleReceivedFrom: code ", ecode.value(), ": ", ecode.message ()); OpenSocket (ep); Receive (socket); } @@ -311,40 +430,75 @@ namespace transport } } - void SSU2Server::HandleReceivedPacket (Packet * packet) - { - if (packet) - { - if (m_IsThroughProxy) - ProcessNextPacketFromProxy (packet->buf, packet->len); - else - 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) + void SSU2Server::HandleReceivedPackets (std::list&& packets) { + if (packets.empty ()) return; if (m_IsThroughProxy) - for (auto& packet: packets) - ProcessNextPacketFromProxy (packet->buf, packet->len); + for (auto it: packets) + ProcessNextPacketFromProxy (it->buf, it->len); else - for (auto& packet: packets) - ProcessNextPacket (packet->buf, packet->len, packet->from); + for (auto it: packets) + ProcessNextPacket (it->buf, it->len, it->from); m_PacketsPool.ReleaseMt (packets); 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) @@ -352,15 +506,28 @@ 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) { if (session) @@ -368,16 +535,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)); + } } } } @@ -385,21 +562,30 @@ namespace transport bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) { if (!session) return false; - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; } - std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) const + std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) { + std::lock_guard l(m_SessionsByRouterHashMutex); auto it = m_SessionsByRouterHash.find (ident); if (it != m_SessionsByRouterHash.end ()) - return it->second; + { + if (!it->second.expired ()) + { + auto s = it->second.lock (); + if (s && s->GetState () != eSSU2SessionStateTerminated) + return s; + } + m_SessionsByRouterHash.erase (it); + } return nullptr; } std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); auto it = m_PendingOutgoingSessions.find (ep); if (it != m_PendingOutgoingSessions.end ()) return it->second; @@ -408,22 +594,20 @@ namespace transport void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); m_PendingOutgoingSessions.erase (ep); } - 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++; @@ -432,7 +616,7 @@ namespace transport 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--; @@ -455,14 +639,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; @@ -510,6 +731,9 @@ namespace transport m_LastSession->SetRemoteEndpoint (senderEndpoint); m_LastSession->ProcessPeerTest (buf, len); break; + 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) @@ -532,13 +756,13 @@ namespace transport if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent && it1->second->ProcessSessionCreated (buf, len)) { - std::unique_lock l(m_PendingOutgoingSessionsMutex); + std::lock_guard l(m_PendingOutgoingSessionsMutex); m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint } else it1->second->ProcessRetry (buf, len); } - else if (!i2p::util::net::IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) + else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) { // assume new incoming session auto session = std::make_shared (*this); @@ -558,20 +782,32 @@ namespace transport 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, @@ -582,74 +818,99 @@ namespace transport 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; + } + } 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; } @@ -664,82 +925,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.iH); - if (it1 != m_SessionsByRouterHash.end ()) + if (it.iTag && ts < it.iExp) { - it1->second->Introduce (session, it.iTag); - return; - } - } - // we have to start a new session to an introducer - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::shared_ptr r; - uint32_t relayTag = 0; - if (!address->ssu->introducers.empty ()) - { - std::vector indices; - for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indices.push_back(i); - if (indices.size () > 1) - std::shuffle (indices.begin(), indices.end(), std::mt19937(std::random_device()())); - - for (auto i: indices) - { - const auto& introducer = address->ssu->introducers[indices[i]]; - if (introducer.iTag && ts < introducer.iExp) + auto s = FindSession (it.iH); + if (s) { - r = i2p::data::netdb.FindRouter (introducer.iH); - if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) - { - relayTag = introducer.iTag; - if (relayTag) break; + 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 + std::vector newRouters; + std::shared_ptr r; + std::shared_ptr addr; + uint32_t relayTag = 0; + if (!indices.empty ()) + { + if (indices.size () > 1) + std::shuffle (indices.begin(), indices.end(), m_Rng); + + for (auto ind: indices) + { + const auto& introducer = address->ssu->introducers[ind]; + // introducer is not expired, because in indices + r = i2p::data::netdb.FindRouter (introducer.iH); + if (r) + { + if (r->IsPublishedOn (i2p::context.GetRouterInfo ().GetCompatibleTransports (false) & // outgoing + (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))) + { + relayTag = introducer.iTag; + 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 + { + // 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) + if (relayTag && addr) { // introducer and tag found connect to it through SSU2 - auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); - if (addr) + auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); + if (!s) { - 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 (); - } - 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); }); - } - } + 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.iH); + for (auto& it: newRouters) + i2p::data::netdb.RequestDestination (it); + session->Done (); // don't wait for connect timeout } } @@ -748,14 +1046,16 @@ namespace transport if (!router) return false; auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address (); if (!addr) return false; - auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); - if (it != m_SessionsByRouterHash.end ()) + auto session = FindSession (router->GetIdentHash ()); + if (session) { - auto s = it->second; - if (it->second->IsEstablished ()) - GetService ().post ([s]() { s->SendPeerTest (); }); + 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 - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); + session->SetOnEstablished ([session]() { session->SendPeerTest (); }); return true; } else @@ -765,7 +1065,8 @@ namespace transport 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)); } @@ -775,17 +1076,20 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) + { - if (it->second->IsTerminationTimeoutExpired (ts)) + std::lock_guard l(m_PendingOutgoingSessionsMutex); + for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) { - //it->second->Terminate (); - std::unique_lock l(m_PendingOutgoingSessionsMutex); - it = m_PendingOutgoingSessions.erase (it); + if (it->second->IsTerminationTimeoutExpired (ts)) + { + //it->second->Terminate (); + it = m_PendingOutgoingSessions.erase (it); + } + else + it++; } - else - it++; - } + } for (auto it: m_Sessions) { @@ -803,14 +1107,6 @@ namespace transport it.second->CleanUp (ts); } - for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) - { - if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) - it = m_SessionsByRouterHash.erase (it); - else - it++; - } - ScheduleTermination (); } } @@ -829,12 +1125,23 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Relays.begin (); it != m_Relays.begin ();) { - if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) + if (it->second.expired ()) it = m_Relays.erase (it); else it++; } + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT || it->second.first.expired ()) + { + LogPrint (eLogInfo, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds or session invalid. Deleted"); + it = m_PeerTests.erase (it); + } + else + it++; + } + for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) { if (ts > it->second.second) @@ -851,6 +1158,33 @@ namespace transport 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 (); m_IncompleteMessagesPool.CleanUp (); @@ -861,8 +1195,9 @@ namespace transport void SSU2Server::ScheduleResend (bool more) { - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT : - (SSU2_RESEND_CHECK_TIMEOUT + rand () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE))); + 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)); } @@ -875,7 +1210,8 @@ namespace transport auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_Sessions) { - resentPacketsNum += 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) @@ -918,122 +1254,149 @@ namespace transport } uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - m_IncomingTokens.emplace (ep, std::make_pair (token, ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)); + if (!token) token = 1; // token can't be zero + m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)); return token; } std::pair SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) { - m_IncomingTokens.erase (ep); // drop previous uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - auto ret = std::make_pair (token, 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 ()) && + 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); - } + 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 ()) - { - session = it1->second; - excluded.insert (it); - } - if (session && session->IsEstablished ()) - { - if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) + std::shared_ptr session = FindSession (ident); + if (session) + excluded.insert (ident); + if (session) + { + 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) + for (const auto& it : impliedList) { - auto it1 = m_SessionsByRouterHash.find (it); - if (it1 != m_SessionsByRouterHash.end ()) + auto session = FindSession (it.first); + if (session) { - auto session = it1->second; - if (session->IsEstablished ()) - { - session->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); - } - } + if (std::find_if (newList.begin (), newList.end (), + [&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ()) + { + sessions.push_back (session); + numOldSessions++; + } } } + impliedList.clear (); } for (const auto& it : sessions) { + uint32_t tag = it->GetRelayTag (); + uint32_t 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.iTag = tag; introducer.iH = it->GetRemoteIdentity ()->GetIdentHash (); - introducer.isH = true; - introducer.iExp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; + introducer.iExp = exp; excluded.insert (it->GetRemoteIdentity ()->GetIdentHash ()); if (i2p::context.AddSSU2Introducer (introducer, v4)) { 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) @@ -1052,13 +1415,15 @@ 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)); } @@ -1071,7 +1436,8 @@ 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)); } @@ -1081,7 +1447,8 @@ namespace transport { 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)); } @@ -1094,7 +1461,8 @@ 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)); } @@ -1107,7 +1475,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 (); @@ -1130,7 +1498,7 @@ namespace transport } else { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + if (i2p::context.GetTestingV6 ()) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimerV6 (); @@ -1154,6 +1522,23 @@ namespace transport } } + bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) + { + return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); + } + + void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) + { + m_ChaCha20 (msg, msgLen, key, nonce, out); + } + void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { @@ -1402,7 +1787,7 @@ namespace transport bool SSU2Server::SetProxy (const std::string& address, uint16_t port) { boost::system::error_code ecode; - auto addr = boost::asio::ip::address::from_string (address, ecode); + auto addr = boost::asio::ip::make_address (address, ecode); if (!ecode && !addr.is_unspecified () && port) { m_IsThroughProxy = true; diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 2e652786..3be9f2d1 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,27 +10,46 @@ #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 = 25; // in seconds + 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 = 400; // in milliseconds - const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds - const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds + 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 size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K - const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K + const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024; +#if defined(__OpenBSD__) + const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 2 * 1024 * 1024; +#else + const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024; +#endif 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; // in 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 { @@ -40,13 +59,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 (); }; }; @@ -58,28 +77,46 @@ 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 IsForcedFirewalled (bool v4) const { return v4 ? m_IsForcedFirewalled4 : m_IsForcedFirewalled6; } + bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true); + void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts); + std::mt19937& GetRng () { return m_Rng; } + bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, + const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); + bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; + void AdjustTimeOffset (int64_t offset, std::shared_ptr from); - void AddSession (std::shared_ptr session); + 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, @@ -107,10 +144,12 @@ namespace transport 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); @@ -120,9 +159,10 @@ namespace transport 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); @@ -145,12 +185,14 @@ namespace transport boost::asio::ip::udp::socket m_SocketV4, m_SocketV6; boost::asio::ip::address m_AddressV4, m_AddressV6; std::unordered_map > m_Sessions; - std::unordered_map > m_SessionsByRouterHash; + std::unordered_map > m_SessionsByRouterHash; + mutable std::mutex m_SessionsByRouterHashMutex; std::map > m_PendingOutgoingSessions; mutable std::mutex m_PendingOutgoingSessionsMutex; std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) - std::map > m_Relays; // we are introducer, relay tag -> session - std::list m_Introducers, m_IntroducersV6; // introducers we are connected to + std::unordered_map > m_Relays; // we are introducer, relay tag -> session + std::unordered_map, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob + std::list > m_Introducers, m_IntroducersV6; // introducers we are connected to i2p::util::MemoryPoolMt m_PacketsPool; i2p::util::MemoryPool m_SentPacketsPool; i2p::util::MemoryPool m_IncompleteMessagesPool; @@ -160,7 +202,19 @@ namespace transport 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; + bool m_IsForcedFirewalled4, m_IsForcedFirewalled6; + // proxy bool m_IsThroughProxy; uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE]; diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp new file mode 100644 index 00000000..dc626b16 --- /dev/null +++ b/libi2pd/SSU2OutOfSession.cpp @@ -0,0 +1,348 @@ +/* +* 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 + if (GetServer ().IsForcedFirewalled (GetRemoteEndpoint ().address().is_v4())) + // we assume that msg 5 was not received if forced firewalled + return; + 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 f2b002cd..8f58ec90 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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; } @@ -79,30 +80,40 @@ namespace transport } SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter, - std::shared_ptr addr): + std::shared_ptr addr, bool noise): TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), - m_Server (server), m_Address (addr), m_RemoteTransports (0), - m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), + 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_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 + 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) { - m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); + if (noise) + m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); if (in_RemoteRouter && m_Address) { // outgoing - InitNoiseXKState1 (*m_NoiseState, m_Address->s); + if (noise) + InitNoiseXKState1 (*m_NoiseState, m_Address->s); m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port); m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false); + 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 ()); } } @@ -114,6 +125,8 @@ namespace transport { 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) @@ -136,7 +149,7 @@ namespace transport void SSU2Session::HandleConnectTimer (const boost::system::error_code& ecode) { - if (!ecode) + if (!ecode && m_State != eSSU2SessionStateTerminated) { // timeout expired if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer @@ -153,40 +166,47 @@ namespace transport if (!session || !relayTag) return false; // find local address to introduce auto localAddress = session->FindLocalAddress (); - if (!localAddress) return false; + if (!localAddress || localAddress->host.is_unspecified () || !localAddress->port) + { + // can't introduce invalid endpoint + LogPrint (eLogWarning, "SSU2: Can't find local address to introduce"); + return false; + } // create nonce uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); // payload - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 0; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + uint8_t * payload = packet->payload; payload[0] = eSSU2BlkRelayRequest; payload[3] = 0; // flag htobe32buf (payload + 4, nonce); htobe32buf (payload + 8, relayTag); - htobe32buf (payload + 12, ts); + htobe32buf (payload + 12, ts/1000); payload[16] = 2; // ver size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); if (!asz) return false; payload[17] = asz; - payloadSize += asz + 18; - 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; } @@ -200,15 +220,29 @@ 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 ()); + } } } @@ -219,11 +253,9 @@ namespace transport RAND_bytes ((uint8_t *)&nonce, 4); auto ts = i2p::util::GetMillisecondsSinceEpoch (); // session for message 5 - auto session = std::make_shared (m_Server); - session->SetState (eSSU2SessionStatePeerTest); - m_PeerTests.emplace (nonce, std::make_pair (session, ts/1000)); - session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_DestConnID = ~session->m_SourceConnID; + auto session = std::make_shared (m_Server, + htobe64 (((uint64_t)nonce << 32) | nonce), 0); + m_Server.AddRequestedPeerTest (nonce, session, ts/1000); m_Server.AddSession (session); // peer test block auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); @@ -243,8 +275,8 @@ namespace transport 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); } } @@ -257,19 +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 (); - m_SendQueueSize = 0; + 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"); } } @@ -279,8 +320,10 @@ namespace transport { m_TerminationReason = reason; SendTermination (); + m_State = eSSU2SessionStateClosing; } - m_State = eSSU2SessionStateClosing; + else + Done (); } void SSU2Session::Established () @@ -292,7 +335,11 @@ namespace transport m_SentHandshakePacket.reset (nullptr); m_ConnectTimer.cancel (); SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT); + SendQueue (); transports.PeerConnected (shared_from_this ()); + + LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established"); if (m_OnEstablished) { m_OnEstablished (); @@ -302,7 +349,7 @@ namespace transport void SSU2Session::Done () { - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::Terminate, shared_from_this ())); } void SSU2Session::SendLocalRouterInfo (bool update) @@ -310,11 +357,11 @@ namespace transport if (update || !IsOutgoing ()) { auto s = shared_from_this (); - m_Server.GetService ().post ([s]() + boost::asio::post (m_Server.GetService (), [s]() { if (!s->IsEstablished ()) return; uint8_t payload[SSU2_MAX_PACKET_SIZE]; - 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) @@ -328,37 +375,92 @@ namespace transport } - void SSU2Session::SendI2NPMessages (const std::vector >& msgs) + void SSU2Session::SendI2NPMessages (std::list >& msgs) { - m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), 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 (std::vector > msgs) + void SSU2Session::PostI2NPMessages () { if (m_State == eSSU2SessionStateTerminated) return; - for (auto it: msgs) - m_SendQueue.push_back (std::move (it)); - SendQueue (); - - if (m_SendQueue.size () > 0) // windows is full + std::list > msgs; { - if (m_SendQueue.size () <= SSU2_MAX_OUTGOING_QUEUE_SIZE) - Resend (i2p::util::GetMillisecondsSinceEpoch ()); - else + 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, ")"); } } - m_SendQueueSize = m_SendQueue.size (); + 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; @@ -366,8 +468,10 @@ namespace transport while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) { auto msg = m_SendQueue.front (); - if (!msg) + if (!msg || msg->IsExpired (ts) || msg->GetEnqueueTime() + I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_TRANSIT < mts) { + // drop null or expired message + if (msg) msg->Drop (); m_SendQueue.pop_front (); continue; } @@ -460,7 +564,7 @@ namespace transport 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; @@ -472,7 +576,7 @@ 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; @@ -492,6 +596,8 @@ namespace transport 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) { @@ -504,20 +610,21 @@ namespace transport 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 (); - m_SendQueueSize = 0; + SetSendQueueSize (0); RequestTermination (eSSU2TerminationReasonTimeout); 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); @@ -528,11 +635,8 @@ 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 (); @@ -579,7 +683,7 @@ namespace transport } const uint8_t nonce[12] = {0}; uint64_t headerX[2]; - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); break; } @@ -608,7 +712,7 @@ namespace transport * 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 @@ -623,10 +727,14 @@ namespace transport 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 @@ -636,18 +744,19 @@ 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 @@ -667,7 +776,7 @@ namespace transport } const uint8_t nonce[12] = {0}; uint8_t headerX[48]; - i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); + m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); memcpy (&m_DestConnID, headerX, 8); uint64_t token; memcpy (&token, headerX + 8, 8); @@ -721,7 +830,7 @@ namespace transport 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 @@ -760,16 +869,17 @@ 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); } @@ -790,9 +900,10 @@ namespace transport 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); @@ -806,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 @@ -832,19 +945,19 @@ namespace transport // 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 } @@ -855,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 @@ -863,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); @@ -872,7 +985,7 @@ 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; } @@ -920,6 +1033,12 @@ 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 uint8_t numFragments = header.h.flags[0] & 0x0F; if (numFragments > 1) @@ -989,6 +1108,7 @@ namespace transport 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 @@ -1041,6 +1161,39 @@ namespace transport LogPrint (eLogError, "SSU2: SessionConfirmed malformed RouterInfo block"); return false; } + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + if (ts > ri->GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes + { + 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 + 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)) { @@ -1051,21 +1204,37 @@ namespace transport (!m_RemoteEndpoint.address ().is_v6 () || memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address { - LogPrint (eLogError, "SSU2: Host mismatch between published address ", m_Address->host, - " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); - return false; - } - // update RouterInfo in netdb - ri = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now - if (!ri) - { - LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); + 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 (); @@ -1114,7 +1283,7 @@ namespace transport header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); memset (nonce, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); + m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); // send if (m_Server.AddPendingOutgoingSession (shared_from_this ())) m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); @@ -1136,7 +1305,7 @@ namespace transport uint8_t nonce[12] = {0}; uint8_t h[32]; memcpy (h, header.buf, 16); - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); memcpy (&m_DestConnID, h + 16, 8); // decrypt CreateNonce (be32toh (header.h.packetNum), nonce); @@ -1188,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); } @@ -1212,7 +1381,7 @@ namespace transport } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); uint64_t token = headerX[1]; if (token) m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); @@ -1240,47 +1409,7 @@ namespace transport 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::GetMillisecondsSinceEpoch () + 500)/1000); - 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 @@ -1301,7 +1430,7 @@ namespace transport } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); m_DestConnID = headerX[0]; // decrypt and handle payload uint8_t * payload = buf + 32; @@ -1316,83 +1445,17 @@ namespace transport return false; } HandlePayload (payload, len - 48); + m_IsDataReceived = false; // connect to Charlie ConnectAfterIntroduction (); return true; } - void SSU2Session::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey) - { - Header header; - uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; - // fill packet - header.h.connID = m_DestConnID; // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2PeerTest; - header.h.flags[0] = 2; // ver - header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID - header.h.flags[2] = 0; // flag - memcpy (h, header.buf, 16); - memcpy (h + 16, &m_SourceConnID, 8); // source id - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - size_t payloadSize = 7; - if (msg == 6 || msg == 7) - payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, - msg, eSSU2PeerTestCodeAccept, nullptr, signedData, signedDataLen); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // encrypt - uint8_t n[12]; - CreateNonce (be32toh (header.h.packetNum), n); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); - payloadSize += 16; - header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); - memset (n, 0, 12); - i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); - // send - m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); - } - bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len) { - // we are Alice or Charlie - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); - header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); - if (header.h.type != eSSU2PeerTest) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); - return false; - } - if (len < 48) - { - LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); - return false; - } - uint8_t nonce[12] = {0}; - uint64_t headerX[2]; // sourceConnID, token - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); - m_DestConnID = headerX[0]; - // decrypt and handle payload - uint8_t * payload = buf + 32; - CreateNonce (be32toh (header.h.packetNum), nonce); - uint8_t h[32]; - memcpy (h, header.buf, 16); - memcpy (h + 16, &headerX, 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, - i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) - { - LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); - return false; - } - HandlePayload (payload, len - 48); - return true; + LogPrint (eLogWarning, "SSU2: Unexpected peer test message for this session type"); + return false; } uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags) @@ -1411,13 +1474,12 @@ namespace transport uint8_t nonce[12]; CreateNonce (m_SendPacketNum, nonce); uint8_t payload[SSU2_MAX_PACKET_SIZE]; - i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE, true); + m_Server.AEADChaCha20Poly1305Encrypt (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8)); header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4)); m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint); m_SendPacketNum++; - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumSentBytes += len + 32; + UpdateNumSentBytes (len + 32); return m_SendPacketNum - 1; } @@ -1436,11 +1498,11 @@ namespace transport ResendHandshakePacket (); // assume we receive return; } - if (from != m_RemoteEndpoint && !i2p::util::net::IsInReservedRange (from.address ())) + 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); - m_RemoteEndpoint = from; - SendPathChallenge (); + SendPathChallenge (from); } if (len < 32) { @@ -1452,14 +1514,14 @@ namespace transport uint32_t packetNum = be32toh (header.h.packetNum); uint8_t nonce[12]; CreateNonce (packetNum, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16, - m_KeyDataReceive, nonce, payload, payloadSize, false)) + if (!m_Server.AEADChaCha20Poly1305Decrypt (buf + 16, payloadSize, header.buf, 16, + m_KeyDataReceive, nonce, payload, payloadSize)) { LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); return; } - 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); } @@ -1474,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; @@ -1489,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"); @@ -1520,29 +1577,37 @@ namespace transport break; case eSSU2BlkTermination: { - uint8_t rsn = buf[11]; // reason - LogPrint (eLogDebug, "SSU2: Termination reason=", (int)rsn); - if (IsEstablished () && rsn != eSSU2TerminationReasonTerminationReceived) - RequestTermination (eSSU2TerminationReasonTerminationReceived); - else if (m_State != eSSU2SessionStateTerminated) + if (size >= 9) { - if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived) - m_State = eSSU2SessionStateClosingConfirmed; - Done (); + 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]); @@ -1566,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: @@ -1591,10 +1660,11 @@ namespace transport LogPrint (eLogDebug, "SSU2: Path response"); if (m_PathChallenge) { - i2p::data::IdentHash hash; - SHA256 (buf + offset, size, hash); - if (hash == *m_PathChallenge) + if (buf64toh (buf + offset) == m_PathChallenge->first) + { + m_RemoteEndpoint = m_PathChallenge->second; m_PathChallenge.reset (nullptr); + } } break; } @@ -1623,16 +1693,18 @@ namespace transport break; case eSSU2SessionStateSessionCreatedReceived: case eSSU2SessionStateTokenReceived: - if ((m_RemoteEndpoint.address ().is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) || - (m_RemoteEndpoint.address ().is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusTesting)) + 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) { @@ -1645,6 +1717,38 @@ namespace transport }; } + 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 + } + } + } + } + void SSU2Session::HandleAck (const uint8_t * buf, size_t len) { if (m_State == eSSU2SessionStateSessionConfirmedSent) @@ -1660,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; @@ -1688,8 +1793,15 @@ namespace transport if (ts > it1->second->sendTime) { auto rtt = ts - it1->second->sendTime; - m_RTT = std::round ((m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1.0)); + 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; } @@ -1712,7 +1824,7 @@ namespace transport 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 @@ -1722,14 +1834,14 @@ namespace transport 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); @@ -1766,8 +1878,8 @@ namespace transport 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); @@ -1854,77 +1966,99 @@ namespace transport void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) { // we are Bob + if (len < 9) return; + auto mts = i2p::util::GetMillisecondsSinceEpoch (); + uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t relayTag = bufbe32toh (buf + 5); // relay tag 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); + 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 && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; - if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); - - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) - 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); + 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 (eLogInfo, "SSU2: Relay request nonce ", nonce, " already exists. Ignore"); } 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"); + LogPrint (eLogWarning, "SSU2: RelayIntro invalid endpoint"); code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; } } @@ -1946,7 +2080,7 @@ namespace transport auto vec = std::make_shared >(len); memcpy (vec->data (), buf, len); auto s = shared_from_this (); - m_Server.GetService ().post ([s, vec, attempts]() + boost::asio::post (m_Server.GetService (), [s, vec, attempts]() { LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1); s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1); @@ -1959,15 +2093,35 @@ namespace transport 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) { @@ -1988,16 +2142,25 @@ namespace transport auto it = m_RelaySessions.find (nonce); if (it != m_RelaySessions.end ()) { - if (it->second.first && it->second.first->IsEstablished ()) + auto relaySession = it->second.first; + m_RelaySessions.erase (it); + if (relaySession && relaySession->IsEstablished ()) { // we are Bob, message from Charlie - 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 { @@ -2005,25 +2168,31 @@ namespace transport if (!buf[1]) // status code accepted? { // verify signature - uint8_t csz = buf[11]; - SignedData s; + uint8_t csz = (len >= 12) ? buf[11] : 0; + if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len) + { + LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len); + relaySession->Done (); + return; + } + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint - if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz)) + if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz)) { - if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet + if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet { // update Charlie's endpoint - if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint)) + if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) { // update token uint64_t token; memcpy (&token, buf + len - 8, 8); - m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint, + m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); // connect to Charlie, HolePunch will be ignored - it->second.first->ConnectAfterIntroduction (); + relaySession->ConnectAfterIntroduction (); } else LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); @@ -2032,23 +2201,23 @@ namespace transport else { LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); - it->second.first->Done (); + relaySession->Done (); } } else { - LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1]); - 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 @@ -2060,52 +2229,62 @@ namespace transport { 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 ())); - 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 + 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); - 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 @@ -2129,18 +2308,22 @@ namespace transport { boost::asio::ip::udp::endpoint ep; std::shared_ptr addr; - if (ExtractEndpoint (buf + offset + 10, asz, ep)) + if (ExtractEndpoint (buf + offset + 10, asz, ep) && !ep.address ().is_unspecified () && ep.port ()) addr = r->GetSSU2Address (ep.address ().is_v4 ()); - if (addr && m_Server.IsSupported (ep.address ())) + if (addr && m_Server.IsSupported (ep.address ()) && + i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ())) { - // send msg 5 to Alice - auto session = std::make_shared (m_Server, r, addr); - session->SetState (eSSU2SessionStatePeerTest); - session->m_RemoteEndpoint = ep; // might be different - session->m_DestConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_SourceConnID = ~session->m_DestConnID; - m_Server.AddSession (session); - session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr->i); + if (!m_Server.IsConnectedRecently (ep)) // no alive hole punch + { + // send msg 5 to Alice + auto session = std::make_shared (m_Server, + 0, htobe64 (((uint64_t)nonce << 32) | nonce)); + session->m_RemoteEndpoint = ep; // might be different + m_Server.AddSession (session); + session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr); + } + else + code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; } else code = eSSU2PeerTestCodeCharlieUnsupportedAddress; @@ -2157,84 +2340,112 @@ namespace transport else code = eSSU2PeerTestCodeCharlieAliceIsUnknown; // send msg 3 back to Bob - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 3, + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 3, code, nullptr, newSignedData.data (), newSignedData.size ()); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + m_SentPackets.emplace (packetNum, packet); break; } case 3: // Bob from Charlie { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end () && it->second.first) - { - uint8_t payload[SSU2_MAX_PACKET_SIZE]; + auto aliceSession = m_Server.GetPeerTest (nonce); + if (aliceSession && aliceSession->IsEstablished ()) + { + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Charlie's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; - size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!payloadSize && r) - it->second.first->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (payloadSize + len + 16 > m_MaxPayloadSize) + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) + aliceSession->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + if (packet->payloadSize + len + 16 > m_MaxPayloadSize) { // doesn't fit one message, send RouterInfo in separate message - it->second.first->SendData (payload, payloadSize); - payloadSize = 0; + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + packet = m_Server.GetSentPacketsPool ().AcquireShared (); } // PeerTest to Alice - payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4, + packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize, 4, (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - it->second.first->SendData (payload, payloadSize); - m_PeerTests.erase (it); - } + if (packet->payloadSize < m_MaxPayloadSize) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = aliceSession->SendData (packet->payload, packet->payloadSize); + packet->sendTime = ts; + aliceSession->m_SentPackets.emplace (packetNum, packet); + } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 3 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 3 nonce ", nonce); break; } case 4: // Alice from Bob { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) + auto session = m_Server.GetRequestedPeerTest (nonce); + if (session) { if (buf[1] == eSSU2PeerTestCodeAccept) { if (GetRouterStatus () == eRouterStatusUnknown) - 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 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) + if (addr && addr->IsPeerTesting ()) { - it->second.first->m_Address = addr; - if (it->second.first->m_State == eSSU2SessionStatePeerTestReceived) + if (session->GetMsgNumReceived () >= 5) { - // msg 5 already received. send msg 6 - SetRouterStatus (eRouterStatusOK); - it->second.first->m_State = eSSU2SessionStatePeerTest; - it->second.first->SendPeerTest (6, buf + offset, len - offset, addr->i); + // msg 5 already received 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) + session->m_Address = addr; + if (GetTestingState ()) { - SetRouterStatus (eRouterStatusFirewalled); - if (m_Address->IsV4 ()) - m_Server.RescheduleIntroducersUpdateTimer (); - else - m_Server.RescheduleIntroducersUpdateTimerV6 (); + // 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 ()), @@ -2242,64 +2453,35 @@ namespace transport } 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"); - if (it->second.first) - it->second.first->Done (); + session->Done (); } } else { LogPrint (eLogInfo, "SSU2: Peer test 4 error code ", (int)buf[1], " from ", i2p::data::GetIdentHashAbbreviation (buf[1] < 64 ? GetRemoteIdentity ()->GetIdentHash () : i2p::data::IdentHash (buf + 3))); - if (GetRouterStatus () == eRouterStatusTesting) + if (GetTestingState () && GetRouterStatus () != eRouterStatusFirewalled) SetRouterStatus (eRouterStatusUnknown); - it->second.first->Done (); + session->Done (); } - m_PeerTests.erase (it); } else - LogPrint (eLogWarning, "SSU2: Unknown peer test 4 nonce ", nonce); + LogPrint (eLogDebug, "SSU2: Unknown peer test 4 nonce ", nonce); break; } - case 5: // Alice from Charlie 1 - if (htobe64 (((uint64_t)nonce << 32) | nonce) == m_SourceConnID) - { - if (m_Address) - { - SetRouterStatus (eRouterStatusOK); - SendPeerTest (6, buf + offset, len - offset, m_Address->i); - } - else - // we received msg 5 before msg 4 - m_State = eSSU2SessionStatePeerTestReceived; - } - else - LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", m_SourceConnID); - break; - case 6: // Charlie from Alice - if (m_Address) - SendPeerTest (7, buf + offset, len - offset, m_Address->i); - else - LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); - m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce)); - break; - case 7: // Alice from Charlie 2 - if (m_Address->IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2 - m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce)); - break; default: LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); } @@ -2312,7 +2494,7 @@ namespace transport if (!msg->IsExpired ()) { // m_LastActivityTimestamp is updated in ProcessData before - if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)m_LastActivityTimestamp).second) + if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)GetLastActivityTimestamp ()).second) m_Handler.PutNextMessage (std::move (msg)); else LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received"); @@ -2373,20 +2555,24 @@ 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) { int mtu = addr->ssu->mtu; if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE; + 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); @@ -2417,6 +2603,31 @@ namespace transport } } + 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; @@ -2429,41 +2640,48 @@ 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, SSU2_MAX_NUM_ACNT); // no gaps + m_NumRanges = 0; + } else { auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num @@ -2476,93 +2694,102 @@ namespace transport it++; } // ranges - uint32_t lastNum = ackThrough - acnt; - if (acnt > SSU2_MAX_NUM_ACNT) - { - auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); - acnt = SSU2_MAX_NUM_ACNT; - if (d.quot > maxNumRanges) + if (!m_NumRanges) + { + int maxNumRanges = (len - 8) >> 1; + if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; + int numRanges = 0; + uint32_t lastNum = ackThrough - acnt; + if (acnt > SSU2_MAX_NUM_ACNT) { - d.quot = maxNumRanges; - d.rem = 0; - } - // Acks only ranges for acnt - for (int i = 0; i < d.quot; i++) - { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 - numRanges++; - } - if (d.rem > 0) - { - buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem; - numRanges++; - } - } - int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; - while (it != m_OutOfSequencePackets.rend () && - numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) - { - if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) - { - // NACKs only ranges - if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs - while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); + acnt = SSU2_MAX_NUM_ACNT; + if (d.quot > maxNumRanges) { - buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 - lastNum -= SSU2_MAX_NUM_ACNT; + d.quot = maxNumRanges; + d.rem = 0; + } + // Acks only ranges for acnt + for (int i = 0; i < d.quot; i++) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 + numRanges++; + } + if (d.rem > 0) + { + m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = d.rem; numRanges++; - numPackets += SSU2_MAX_NUM_ACNT; } } - // NACKs and Acks ranges - buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs - numPackets += buf[8 + numRanges*2]; - lastNum = *it; it++; - int numAcks = 1; - while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; + while (it != m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) { - numAcks++; lastNum--; - it++; - } - while (numAcks > SSU2_MAX_NUM_ACNT) - { - // Acks only ranges - buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 - numAcks -= SSU2_MAX_NUM_ACNT; - numRanges++; - numPackets += SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2] = 0; // NACKs 0 - if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; - } - if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks - numPackets += numAcks; - numRanges++; - } - if (it == m_OutOfSequencePackets.rend () && - numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) - { - // add range between out-of-sequence and received - int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; - if (nacks > 0) - { - if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; - buf[8 + numRanges*2] = nacks; - buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + // NACKs only ranges + if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + m_Ranges[numRanges*2] = SSU2_MAX_NUM_ACNT; m_Ranges[numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + } + } + // NACKs and Acks ranges + m_Ranges[numRanges*2] = lastNum - (*it) - 1; // NACKs + numPackets += m_Ranges[numRanges*2]; + lastNum = *it; it++; + int numAcks = 1; + while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + while (numAcks > SSU2_MAX_NUM_ACNT) + { + // Acks only ranges + m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 + numAcks -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = 0; // NACKs 0 + if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; + } + if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numPackets += numAcks; numRanges++; } - } + if (it == m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) + { + // add range between out-of-sequence and received + int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; + if (nacks > 0) + { + if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; + m_Ranges[numRanges*2] = nacks; + m_Ranges[numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + numRanges++; + } + } + m_NumRanges = numRanges; + } + if (m_NumRanges) + memcpy (buf + 8, m_Ranges, m_NumRanges*2); } } buf[7] = (uint8_t)acnt; // acnt - htobe16buf (buf + 1, 5 + numRanges*2); - return 8 + numRanges*2; + htobe16buf (buf + 1, 5 + m_NumRanges*2); + return 8 + m_NumRanges*2; } size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) { if (len < 3 || len < minSize) return 0; - size_t paddingSize = rand () & 0x0F; // 0 - 15 + size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 if (paddingSize + 3 > len) paddingSize = len - 3; else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; buf[0] = eSSU2BlkPadding; @@ -2664,7 +2891,7 @@ namespace transport LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); return 0; } - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash @@ -2726,7 +2953,7 @@ namespace transport size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); signedData[9] = asz; // signature - SignedData s; + SignedData<128> s; s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint @@ -2754,22 +2981,18 @@ namespace transport i2p::data::GzipInflator inflator; uint8_t uncompressed[i2p::data::MAX_RI_BUFFER_SIZE]; size_t uncompressedSize = inflator.Inflate (buf + 2, size - 2, uncompressed, i2p::data::MAX_RI_BUFFER_SIZE); - if (uncompressedSize && uncompressedSize < i2p::data::MAX_RI_BUFFER_SIZE) + if (uncompressedSize && uncompressedSize <= i2p::data::MAX_RI_BUFFER_SIZE) ri = std::make_shared(uncompressed, uncompressedSize); else LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize); } - else + else if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 2) ri = std::make_shared(buf + 2, size - 2); + else + LogPrint (eLogInfo, "SSU2: RouterInfo is too long ", size); return ri; } - void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); - } - bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) { if (packetNum <= m_ReceivePacketNum) return false; // duplicate @@ -2794,11 +3017,17 @@ namespace transport } m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it); } + m_NumRanges = 0; // recalculate ranges when create next Ack } m_ReceivePacketNum = packetNum; } else + { + if (m_NumRanges && (m_OutOfSequencePackets.empty () || + packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1)) + m_NumRanges = 0; // reset ranges if received packet is not next m_OutOfSequencePackets.insert (packetNum); + } return true; } @@ -2807,13 +3036,13 @@ namespace transport uint8_t payload[SSU2_MAX_PACKET_SIZE]; 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); @@ -2829,35 +3058,67 @@ 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 () + 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]; - payload[0] = eSSU2BlkPathChallenge; - size_t len = rand () % (m_MaxPayloadSize - 3); - htobe16buf (payload + 1, len); - if (len > 0) - { - RAND_bytes (payload + 3, len); - i2p::data::IdentHash * hash = new i2p::data::IdentHash (); - SHA256 (payload + 3, len, *hash); - m_PathChallenge.reset (hash); - } - len += 3; - if (len < m_MaxPayloadSize) - len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len); - SendData (payload, len); + 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) @@ -2872,7 +3133,7 @@ namespace transport else ++it; } - if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > m_LastActivityTimestamp + SSU2_DECAY_INTERVAL) + if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > GetLastActivityTimestamp () + SSU2_DECAY_INTERVAL) // decay m_ReceivedI2NPMsgIDs.clear (); else @@ -2920,22 +3181,12 @@ namespace transport { 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); } @@ -2944,14 +3195,20 @@ namespace transport { bool sent = SendQueue (); // if we have something to send if (sent) - m_SendQueueSize = m_SendQueue.size (); + 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 363cb982..ee26255f 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2023, The PurpleI2P Project +* Copyright (c) 2022-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 @@ -36,8 +37,8 @@ namespace transport const size_t SSU2_MAX_PACKET_SIZE = 1500; const size_t SSU2_MIN_PACKET_SIZE = 1280; const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds - const int SSU2_RESEND_INTERVAL = 300; // 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 @@ -45,14 +46,17 @@ namespace transport 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 = 510; // 2*255 ack + nack + 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 = 250; + 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; @@ -110,8 +114,8 @@ namespace transport eSSU2SessionStateTerminated, eSSU2SessionStateFailed, eSSU2SessionStateIntroduced, + eSSU2SessionStateHolePunch, eSSU2SessionStatePeerTest, - eSSU2SessionStatePeerTestReceived, // 5 before 4 eSSU2SessionStateTokenRequestReceived }; @@ -204,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 @@ -252,29 +261,54 @@ 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; }; - size_t Resend (uint64_t ts); // return number or resent packets + 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); + 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 (); @@ -292,46 +326,41 @@ namespace transport 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 SendPathChallenge (); + void SendPathChallenge (const boost::asio::ip::udp::endpoint& to); - void HandlePayload (const uint8_t * buf, size_t len); void HandleDateTime (const uint8_t * buf, size_t len); + void HandleRouterInfo (const uint8_t * buf, size_t len); void HandleAck (const uint8_t * buf, size_t len); 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); void HandleRelayRequest (const uint8_t * buf, size_t len); void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0); void HandleRelayResponse (const uint8_t * buf, size_t len); - void HandlePeerTest (const uint8_t * buf, size_t len); + virtual void HandlePeerTest (const uint8_t * buf, size_t len); void HandleI2NPMsg (std::shared_ptr&& msg); - size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r); + size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr riBuffer); size_t CreateAckBlock (uint8_t * buf, size_t len); - size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg); size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg); size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID); size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen); size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4); - size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice size_t CreateTerminationBlock (uint8_t * buf, size_t len); - + private: SSU2Server& m_Server; @@ -341,7 +370,8 @@ 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]; @@ -349,27 +379,40 @@ namespace transport std::set m_OutOfSequencePackets; // packet nums > receive packet num std::map > m_SentPackets; // packetNum -> packet std::unordered_map > m_IncompleteMessages; // msgID -> I2NP - std::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice - std::map, uint64_t > > m_PeerTests; // same as for relay sessions + std::unordered_map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice std::list > m_SendQueue; i2p::I2NPMessagesHandler m_Handler; + 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::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/Signature.cpp b/libi2pd/Signature.cpp index ebc188a9..cad7d484 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,11 @@ */ #include +#include +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +#include +#include +#endif #include "Log.h" #include "Signature.h" @@ -14,59 +19,320 @@ namespace i2p { namespace crypto { -#if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier () +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + DSAVerifier::DSAVerifier (): + m_PublicKey (nullptr) { - m_MDCtx = EVP_MD_CTX_create (); } - EDDSA25519Verifier::~EDDSA25519Verifier () + DSAVerifier::~DSAVerifier () { - EVP_MD_CTX_destroy (m_MDCtx); + if (m_PublicKey) + EVP_PKEY_free (m_PublicKey); } - void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) + void DSAVerifier::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); + 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 + { + // 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 sign[DSA_SIGNATURE_LENGTH + 8]; + uint8_t * s = sign; + auto l = i2d_DSA_SIG (sig, &s); + DSA_SIG_free(sig); + // verify + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit (ctx, NULL, EVP_sha1(), NULL, m_PublicKey); + auto ret = EVP_DigestVerify (ctx, sign, l, buf, len) == 1; + EVP_MD_CTX_destroy (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 sign[DSA_SIGNATURE_LENGTH + 8]; + size_t l = DSA_SIGNATURE_LENGTH + 8; + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestSignInit (ctx, NULL, EVP_sha1(), NULL, m_PrivateKey); + EVP_DigestSign (ctx, sign, &l, buf, len); + EVP_MD_CTX_destroy (ctx); + // decode r and s + 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); + } + + 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); - } - - bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + EVP_PKEY_free (paramskey); + EVP_PKEY_CTX_free (ctx); + } +#else + + DSAVerifier::DSAVerifier () { - return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); + m_PublicKey = CreateDSA (); } -#else - EDDSA25519Verifier::EDDSA25519Verifier () + 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) == 1; + 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_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + ECDSAVerifier::ECDSAVerifier (int curve, size_t keyLen, const EVP_MD * hash): + m_Curve(curve), m_KeyLen (keyLen), m_Hash (hash), m_PublicKey (nullptr) + { + } + + ECDSAVerifier::~ECDSAVerifier () + { + if (m_PublicKey) + EVP_PKEY_free (m_PublicKey); + } + + void ECDSAVerifier::SetPublicKey (const uint8_t * signingKey) + { + if (m_PublicKey) + { + EVP_PKEY_free (m_PublicKey); + m_PublicKey = nullptr; + } + auto plen = GetPublicKeyLen (); + std::vector pub(plen + 1); + pub[0] = POINT_CONVERSION_UNCOMPRESSED; + memcpy (pub.data() + 1, signingKey, plen); // 0x04|x|y + OSSL_PARAM_BLD * paramBld = OSSL_PARAM_BLD_new (); + OSSL_PARAM_BLD_push_utf8_string (paramBld, OSSL_PKEY_PARAM_GROUP_NAME, OBJ_nid2ln(m_Curve), 0); + OSSL_PARAM_BLD_push_octet_string (paramBld, OSSL_PKEY_PARAM_PUB_KEY, pub.data (), pub.size ()); + OSSL_PARAM * params = OSSL_PARAM_BLD_to_param(paramBld); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL); + if (ctx) + { + if (EVP_PKEY_fromdata_init (ctx) <= 0 || + EVP_PKEY_fromdata (ctx, &m_PublicKey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + LogPrint (eLogError, "ECDSA can't create PKEY from params"); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "ECDSA can't create PKEY context"); + + OSSL_PARAM_free (params); + OSSL_PARAM_BLD_free (paramBld); + } + + bool ECDSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + // signature + ECDSA_SIG * sig = ECDSA_SIG_new(); + ECDSA_SIG_set0 (sig, BN_bin2bn (signature, GetSignatureLen ()/2, NULL), + BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL)); + // to DER format + std::vector sign(GetSignatureLen () + 8); + uint8_t * s = sign.data (); + auto l = i2d_ECDSA_SIG (sig, &s); + ECDSA_SIG_free(sig); + // verify + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestVerifyInit (ctx, NULL, m_Hash, NULL, m_PublicKey); + auto ret = EVP_DigestVerify (ctx, sign.data (), l, buf, len) == 1; + EVP_MD_CTX_destroy (ctx); + return ret; + } + + ECDSASigner::ECDSASigner (int curve, size_t keyLen, const EVP_MD * hash, const uint8_t * signingPrivateKey): + m_KeyLen (keyLen), m_Hash(hash), m_PrivateKey (nullptr) + { + BIGNUM * priv = BN_bin2bn (signingPrivateKey, keyLen/2, NULL); + OSSL_PARAM_BLD * paramBld = OSSL_PARAM_BLD_new (); + OSSL_PARAM_BLD_push_utf8_string (paramBld, OSSL_PKEY_PARAM_GROUP_NAME, OBJ_nid2ln(curve), 0); + OSSL_PARAM_BLD_push_BN (paramBld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + OSSL_PARAM * params = OSSL_PARAM_BLD_to_param(paramBld); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL); + if (ctx) + { + if (EVP_PKEY_fromdata_init (ctx) <= 0 || + EVP_PKEY_fromdata (ctx, &m_PrivateKey, EVP_PKEY_KEYPAIR, params) <= 0) + LogPrint (eLogError, "ECDSA can't create PKEY from params"); + EVP_PKEY_CTX_free (ctx); + } + else + LogPrint (eLogError, "ECDSA can't create PKEY context"); + + OSSL_PARAM_free (params); + OSSL_PARAM_BLD_free (paramBld); + BN_free (priv); + } + + ECDSASigner::~ECDSASigner () + { + if (m_PrivateKey) + EVP_PKEY_free (m_PrivateKey); + } + + void ECDSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + std::vector sign(m_KeyLen + 8); + size_t l = sign.size (); + EVP_MD_CTX * ctx = EVP_MD_CTX_create (); + EVP_DigestSignInit (ctx, NULL, m_Hash, NULL, m_PrivateKey); + EVP_DigestSign (ctx, sign.data(), &l, buf, len); + EVP_MD_CTX_destroy (ctx); + // decode r and s + const uint8_t * s1 = sign.data (); + ECDSA_SIG * sig = d2i_ECDSA_SIG (NULL, &s1, l); + const BIGNUM * r, * s; + ECDSA_SIG_get0 (sig, &r, &s); + bn2buf (r, signature, m_KeyLen/2); + bn2buf (s, signature + m_KeyLen/2, m_KeyLen/2); + ECDSA_SIG_free(sig); + } + + void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + EVP_PKEY * pkey = EVP_EC_gen (OBJ_nid2ln(curve)); + // private + BIGNUM * priv = BN_new (); + EVP_PKEY_get_bn_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv); + bn2buf (priv, signingPrivateKey, keyLen/2); + BN_free (priv); + // public + BIGNUM * x = BN_new (), * y = BN_new (); + EVP_PKEY_get_bn_param (pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x); + EVP_PKEY_get_bn_param (pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y); + bn2buf (x, signingPublicKey, keyLen/2); + bn2buf (y, signingPublicKey + keyLen/2, keyLen/2); + BN_free (x); BN_free (y); + EVP_PKEY_free (pkey); + } + +#endif + + EDDSA25519Verifier::EDDSA25519Verifier (): + m_Pkey (nullptr) { } EDDSA25519Verifier::~EDDSA25519Verifier () { + EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); - BN_CTX * ctx = BN_CTX_new (); - m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); - BN_CTX_free (ctx); + 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 { - uint8_t digest[64]; - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); - - return GetEd25519 ()->Verify (m_PublicKey, digest, signature); + 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) == 1; + EVP_MD_CTX_destroy (ctx); + return ret; + } + else + LogPrint (eLogError, "EdDSA verification key is not set"); + return false; } -#endif EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { @@ -97,44 +363,219 @@ namespace crypto GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } -#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) == 1; + 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..43f706bd 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,164 @@ 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) + void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey); + + // ECDSA + constexpr size_t ECDSAP256_KEY_LENGTH = 64; + constexpr size_t ECDSAP384_KEY_LENGTH = 96; + constexpr size_t ECDSAP521_KEY_LENGTH = 132; + +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 + class ECDSAVerifier: public Verifier { - 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); + public: + + ECDSAVerifier (int curve, size_t keyLen, const EVP_MD * hash); + ~ECDSAVerifier (); + + void SetPublicKey (const uint8_t * signingKey); + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + + size_t GetPublicKeyLen () const { return m_KeyLen; }; + size_t GetSignatureLen () const { return m_KeyLen; }; // signature length = key length + + private: + + int m_Curve; + size_t m_KeyLen; + const EVP_MD * m_Hash; + EVP_PKEY * m_PublicKey; + }; + + class ECDSASigner: public Signer + { + public: + + ECDSASigner (int curve, size_t keyLen, const EVP_MD * hash, const uint8_t * signingPrivateKey); + ~ECDSASigner (); + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + + private: + + size_t m_KeyLen; + const EVP_MD * m_Hash; + EVP_PKEY * m_PrivateKey; + }; + + void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey); + +// ECDSA_SHA256_P256 + class ECDSAP256Verifier: public ECDSAVerifier + { + public: + + ECDSAP256Verifier (): ECDSAVerifier (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, EVP_sha256()) {}; + }; + + class ECDSAP256Signer: public ECDSASigner + { + public: + + ECDSAP256Signer (const uint8_t * signingPrivateKey): + ECDSASigner (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, EVP_sha256(), signingPrivateKey) {}; + }; + + inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); } +// ECDSA_SHA384_P384 + class ECDSAP384Verifier: public ECDSAVerifier + { + public: + + ECDSAP384Verifier (): ECDSAVerifier (NID_secp384r1, ECDSAP384_KEY_LENGTH, EVP_sha384()) {}; + }; + + class ECDSAP384Signer: public ECDSASigner + { + public: + + ECDSAP384Signer (const uint8_t * signingPrivateKey): + ECDSASigner (NID_secp384r1, ECDSAP384_KEY_LENGTH, EVP_sha384(), signingPrivateKey) {}; + }; + + inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); + } + +// ECDSA_SHA512_P521 + class ECDSAP521Verifier: public ECDSAVerifier + { + public: + + ECDSAP521Verifier (): ECDSAVerifier (NID_secp521r1, ECDSAP521_KEY_LENGTH, EVP_sha512()) {}; + }; + + class ECDSAP521Signer: public ECDSASigner + { + public: + + ECDSAP521Signer (const uint8_t * signingPrivateKey): + ECDSASigner (NID_secp521r1, ECDSAP521_KEY_LENGTH, EVP_sha512(), signingPrivateKey) {}; + }; + + inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); + } + +#else + struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) @@ -161,7 +231,6 @@ namespace crypto enum { hashLen = 64 }; }; - // EcDSA template class ECDSAVerifier: public Verifier { @@ -194,7 +263,7 @@ namespace crypto auto s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); ECDSA_SIG_set0(sig, r, s); // ECDSA verification - int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); + int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey) == 1; ECDSA_SIG_free(sig); return ret; } @@ -257,7 +326,6 @@ namespace crypto } // ECDSA_SHA256_P256 - const size_t ECDSAP256_KEY_LENGTH = 64; typedef ECDSAVerifier ECDSAP256Verifier; typedef ECDSASigner ECDSAP256Signer; @@ -267,7 +335,6 @@ namespace crypto } // ECDSA_SHA384_P384 - const size_t ECDSAP384_KEY_LENGTH = 96; typedef ECDSAVerifier ECDSAP384Verifier; typedef ECDSASigner ECDSAP384Signer; @@ -277,7 +344,6 @@ namespace crypto } // ECDSA_SHA512_P521 - const size_t ECDSAP521_KEY_LENGTH = 132; typedef ECDSAVerifier ECDSAP521Verifier; typedef ECDSASigner ECDSAP521Signer; @@ -285,7 +351,8 @@ namespace crypto { CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } - + +#endif // EdDSA class EDDSA25519Verifier: public Verifier @@ -302,15 +369,23 @@ namespace crypto size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: + + EVP_PKEY * m_Pkey; + + protected: -#if OPENSSL_EDDSA - EVP_MD_CTX * m_MDCtx; -#else - EDDSAPoint m_PublicKey; - uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; -#endif + EVP_PKEY * GetPkey () const { return m_Pkey; }; }; +#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: @@ -328,7 +403,6 @@ namespace crypto uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; -#if OPENSSL_EDDSA class EDDSA25519Signer: public Signer { public: @@ -339,20 +413,30 @@ 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 - typedef EDDSA25519SignerCompat EDDSA25519Signer; - -#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 EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL); EVP_PKEY_keygen_init (pctx); @@ -363,11 +447,6 @@ namespace crypto len = EDDSA25519_PRIVATE_KEY_LENGTH; EVP_PKEY_get_raw_private_key (pkey, signingPrivateKey, &len); EVP_PKEY_free (pkey); -#else - RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); - EDDSA25519Signer signer (signingPrivateKey); - memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); -#endif } @@ -530,6 +609,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 62c4acc7..25fb7b5d 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,76 @@ 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_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_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (INITIAL_WINDOW_SIZE), + 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 (true), m_IsChoking2 (false), m_IsClientChoked (false), m_IsClientChoked2 (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), + m_IsBufferEmpty (false), m_IsJavaClient (false), m_DontSign (local.GetOwner ()->IsStreamingDontSign ()), + m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), + m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), + m_NumReceivedBytes (0), m_Port (port), m_RTT (INITIAL_RTT), m_MinRTT (INITIAL_RTT), + m_SlowRTT (INITIAL_RTT), m_FastRTT (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), + m_MaxWindowSize (local.GetOwner ()->GetStreamingMaxWindowSize ()), m_LastWindowDropSize (0), + m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), + m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), + m_Jitter (0), m_MinPacingTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), + m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), + m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_LastACKRequestTime (0), m_LastACKSendTime (0), + m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed + m_NumResendAttempts (0), m_NumPacketsToSend (0), m_JitterAccum (0), m_JitterDiv (1), 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 (INITIAL_WINDOW_SIZE), + 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 (true), m_IsChoking2 (false), m_IsClientChoked (false), m_IsClientChoked2 (false), + m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), + m_IsBufferEmpty (false), m_IsJavaClient (false), m_DontSign (local.GetOwner ()->IsStreamingDontSign ()), + 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_MinRTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_FastRTT (INITIAL_RTT), + m_WindowSize (INITIAL_WINDOW_SIZE), m_MaxWindowSize (local.GetOwner ()->GetStreamingMaxWindowSize ()), + m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), + m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),m_PrevRTTSample (INITIAL_RTT), m_Jitter (0), + m_MinPacingTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), + m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), + m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_LastACKRequestTime (0), + m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed + m_NumResendAttempts (0), m_NumPacketsToSend (0), m_JitterAccum (0), m_JitterDiv (1), 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,23 +159,32 @@ 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 ()); } void Stream::CleanUp () - { + { + if (m_RoutingSession && !m_SentPackets.empty ()) // free up space in shared window { - std::unique_lock l(m_SendBufferMutex); - m_SendBuffer.CleanUp (); + int numPackets = m_SentPackets.size (); + int numSentPackets = m_RoutingSession->NumSentPackets (); + numSentPackets -= numPackets; + if (numSentPackets < 0) numSentPackets = 0; + m_RoutingSession->SetNumSentPackets (numSentPackets); } + + 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 +197,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->from && 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 +251,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 +262,7 @@ namespace stream m_SavedPackets.erase (it++); ProcessPacket (savedPacket); + if (m_Status == eStreamStatusTerminated) return; } else break; @@ -179,16 +273,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 } @@ -197,33 +287,75 @@ namespace stream if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate - LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); - SendQuickAck (); // resend ack for previous message again + LogPrint (eLogInfo, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); + 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 { - LogPrint (eLogWarning, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); - // save message and wait for missing message again - SavePacket (packet); - if (m_LastReceivedSequenceNumber >= 0) + LogPrint (eLogInfo, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); + if ((receivedSeqn - m_LastReceivedSequenceNumber) >= m_MaxWindowSize*3) { - // send NACKs for missing messages ASAP - if (m_IsAckSendScheduled) + m_LocalDestination.DeletePacket (packet); + m_IsChoking2 = true; + } + else if (m_SavedPackets.empty () && (receivedSeqn - m_LastReceivedSequenceNumber) >= 256) + { + m_LocalDestination.DeletePacket (packet); + m_IsChoking2 = true; + } + else if (!m_SavedPackets.empty ()) + { + uint8_t numNacks = 0; + auto lastSavedSeq = 0; + auto nextSeqn = m_LastReceivedSequenceNumber + 1; + for (auto it: m_SavedPackets) { - m_IsAckSendScheduled = false; - m_AckSendTimer.cancel (); + auto seqn = it->GetSeqn (); + if ((int)seqn > lastSavedSeq) lastSavedSeq = seqn; + for (uint32_t i = nextSeqn; i < seqn; i++) numNacks++; + nextSeqn = seqn + 1; } - SendQuickAck (); + + if (numNacks + (receivedSeqn - lastSavedSeq) >= 256) + { + m_LocalDestination.DeletePacket (packet); + m_IsChoking2 = true; + } + else + // save message and wait for missing message again + SavePacket (packet); } else + // save message and wait for missing message again + SavePacket (packet); + if (m_LastReceivedSequenceNumber >= 0) { + 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,24 +409,62 @@ 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 (delayRequested == 65535) + { + m_IsClientChoked2 = true; + m_DropWindowDelaySequenceNumber = m_SequenceNumber-1; + } + else if (!m_IsClientChoked) + { + LogPrint (eLogDebug, "Streaming: Client choked, set min. window size"); + if (delayRequested == DELAY_CHOKING_JAVA) // java detected + { + LogPrint (eLogDebug, "Streaming: limit window size for java client"); + m_MaxWindowSize = 64; + m_IsJavaClient = true; + if (m_RoutingSession) m_RoutingSession->SetIsWithJava (true); + } + m_WindowDropTargetSize = MIN_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsClientChoked = true; + m_IsWinDropped = false; + m_DropWindowDelaySequenceNumber = m_SequenceNumber-1; + m_IsFirstRttSample = true; + UpdatePacingTime (); + } + } } optionData += 2; } + bool verified = true; if (flags & PACKET_FLAG_FROM_INCLUDED) { + verified = false; if (m_RemoteLeaseSet) m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); if (!m_RemoteIdentity) m_RemoteIdentity = std::make_shared(optionData, optionSize); @@ -305,7 +475,26 @@ namespace stream } optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) - LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + { + LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase32 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + if (packet->from) // try to obtain LeaseSet if came from ratchets session + m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + } + if (packet->from && m_RemoteLeaseSet) + { + // stream came from ratchets session and static key must match one from LeaseSet + uint8_t staticKey[32]; + m_RemoteLeaseSet->Encrypt (nullptr, staticKey); + if (memcmp (packet->from->GetRemoteStaticKey (), staticKey, 32)) + { + LogPrint (eLogError, "Streaming: Remote LeaseSet static key mismatch for stream from ", + m_RemoteIdentity->GetIdentHash ().ToBase32 ()); + return false; + } + verified = true; + if (!(flags & PACKET_FLAG_SIGNATURE_INCLUDED)) + m_DontSign = true; // don't sign if the remote didn't sign + } } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) @@ -315,6 +504,30 @@ namespace stream optionData += 2; } + if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET)) + { + verified = false; + if (packet->from) + { + if (!m_RemoteLeaseSet && m_RemoteIdentity) + m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (m_RemoteLeaseSet) + { + uint8_t staticKey[32]; + m_RemoteLeaseSet->Encrypt (nullptr, staticKey); + if (memcmp (packet->from->GetRemoteStaticKey (), staticKey, 32)) + { + LogPrint (eLogError, "Streaming: Remote LeaseSet static key mismatch for stream from ", + m_RemoteIdentity->GetIdentHash ().ToBase32 ()); + return false; + } + verified = true; + } + else // invalid stream, safe to close + verified = true; + } + } + if (flags & PACKET_FLAG_OFFLINE_SIGNATURE) { if (!m_RemoteIdentity) @@ -322,55 +535,95 @@ namespace stream LogPrint (eLogInfo, "Streaming: offline signature without identity"); return false; } - // if we have it in LeaseSet already we don't need to parse it again - if (m_RemoteLeaseSet) m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); - if (m_TransientVerifier) + if (verified) { - // skip option data - optionData += 6; // timestamp and key type - optionData += m_TransientVerifier->GetPublicKeyLen (); // public key + // skip offline signature + optionData += 4; // timestamp + uint16_t keyType = bufbe16toh (optionData); optionData += 2; // key type + std::unique_ptr transientVerifier (i2p::data::IdentityEx::CreateVerifier (keyType)); + if (!transientVerifier) + { + LogPrint (eLogInfo, "Streaming: Unknown offline signature key type ", (int)keyType); + return false; + } + optionData += transientVerifier->GetPublicKeyLen (); // public key optionData += m_RemoteIdentity->GetSignatureLen (); // signature } else - { - // transient key - size_t offset = 0; - m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); - optionData += offset; - if (!m_TransientVerifier) + { + // if we have it in LeaseSet already we don't need to parse it again + if (m_RemoteLeaseSet) m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); + if (m_TransientVerifier) { - LogPrint (eLogError, "Streaming: offline signature failed"); - return false; + // skip option data + optionData += 6; // timestamp and key type + optionData += m_TransientVerifier->GetPublicKeyLen (); // public key + optionData += m_RemoteIdentity->GetSignatureLen (); // signature } - } + else + { + // transient key + size_t offset = 0; + m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); + optionData += offset; + if (!m_TransientVerifier) + { + LogPrint (eLogError, "Streaming: offline signature failed"); + return false; + } + } + } } if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - uint8_t signature[256]; 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 (!verified) // packet was not verified through session + { + // verify actual signature + 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 (!verified) + { + LogPrint (eLogError, "Streaming: Missing signature, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + return false; + } + if (immediateAckRequested) + SendQuickAck (); return true; } @@ -384,7 +637,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 +654,17 @@ 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; + int ackPacketsCounter = 0; + m_IsNAcked = false; + m_IsResendNeeded = false; int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) { @@ -418,6 +677,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 +690,150 @@ 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 = std::round ((m_RTT*seqn + rtt)/(seqn + 1.0)); - 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})); + ackPacketsCounter++; + if (m_WindowIncCounter < m_MaxWindowSize && !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_MinRTT = m_RTT; + m_SlowRTT = rttSample; + m_FastRTT = rttSample; + m_PrevRTTSample = rttSample; + m_Jitter = rttSample / 5; // 20% + m_Jitter += 3; // for low-latency connections + m_JitterAccum = m_Jitter; + m_JitterDiv = 1; + 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_FastRTT = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_FastRTT; + // calculate jitter + double jitter = 0; + if (rttSample > m_PrevRTTSample) + jitter = rttSample - m_PrevRTTSample; + else if (rttSample < m_PrevRTTSample) + jitter = m_PrevRTTSample - rttSample; + if (jitter) + { + jitter += 3; // for low-latency connections + m_JitterAccum += jitter; + m_Jitter = m_JitterAccum / m_JitterDiv; + m_JitterDiv++; + } + if (m_MinRTT > m_RTT) + { + m_MinRTT = m_RTT; + m_FastRTT = m_MinRTT + m_Jitter; + m_SlowRTT = m_MinRTT + m_Jitter; + } + } + if (m_IsBufferEmpty || m_FastRTT >= m_MinRTT + m_Jitter*3 || m_RTT >= m_MinRTT + m_Jitter*3 || m_SlowRTT >= m_MinRTT + m_Jitter*3 || m_RTT > m_FastRTT) + { + incCounter = 0; + m_WindowIncCounter = 0; + } + m_WindowIncCounter = m_WindowIncCounter + incCounter; + // + // delay-based CC + if ((m_SlowRTT > m_MinRTT + m_Jitter*6) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast + { + 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_SentPackets.empty ())) + m_IsClientChoked = false; + if (m_IsClientChoked2 && (ackThrough >= m_DropWindowDelaySequenceNumber || m_SentPackets.empty ())) + m_IsClientChoked2 = 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) { + if (m_RoutingSession) + { + int numSentPackets = m_RoutingSession->NumSentPackets (); + numSentPackets -= ackPacketsCounter; + if (numSentPackets < 0) numSentPackets = 0; + m_RoutingSession->SetNumSentPackets (numSentPackets); + } m_NumResendAttempts = 0; + m_IsTimeOutResend = false; SendBuffer (); } if (m_Status == eStreamStatusClosed) @@ -502,7 +870,7 @@ namespace stream { // make sure that AsycReceive complete auto s = shared_from_this(); - m_Service.post ([s]() + boost::asio::post (m_Service, [s]() { s->m_ReceiveTimer.cancel (); }); @@ -524,71 +892,121 @@ 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; + if (m_RoutingSession) + { + m_IsJavaClient = m_RoutingSession->IsWithJava (); + if (m_IsJavaClient) m_MaxWindowSize = 64; + int numSentPackets = m_RoutingSession->NumSentPackets (); + int numPacketsToSend = m_MaxWindowSize - numSentPackets; + if (numPacketsToSend <= 0) // shared window is full + { + m_LastSendTime = ts; + return; + } + else if (numMsgs > numPacketsToSend) + numMsgs = 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); + // first SYN packet + if (m_DontSign) + { + // remote ident is useless without signature, don't include it + packet[size] = 0; size++; // NACK count + } else - htobe32buf (packet + size, m_LastReceivedSequenceNumber); - size += 4; // ack Through + { + 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 + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming); + m_MTU = (m_RoutingSession && m_RoutingSession->IsRatchets ()) ? STREAMING_MTU_RATCHETS : STREAMING_MTU; + } + uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; + if (!m_DontSign) flags |= PACKET_FLAG_SIGNATURE_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 (m_DontSign) + { + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + size += m_SendBuffer.Get (packet + size, m_MTU); // payload + } + else + { if (isOfflineSignature) { const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); @@ -601,21 +1019,36 @@ namespace stream 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 + if (m_IsJavaClient && (!m_LastACKRequestTime || ts - m_LastACKRequestTime > m_MinRTT / 10)) + { + m_LastACKRequestTime = ts; + htobe16buf (packet + size, PACKET_FLAG_DELAY_REQUESTED); + size += 2; // flags + htobe16buf (packet + size, 2); // 2 bytes delay interval + htobe16buf (packet + size + 2, 0); // set immediate ack interval + size += 2; } 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--; + size += 2; // options size + size += m_SendBuffer.Get(packet + size, m_MTU); // payload } + p->len = size; + packets.push_back (p); + numMsgs--; } + if (m_SendBuffer.GetSize() == 0) m_IsBufferEmpty = true; + else m_IsBufferEmpty = false; + int numPackets = packets.size (); if (packets.size () > 0) { if (m_SavedPackets.empty ()) // no NACKS @@ -624,13 +1057,20 @@ 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_RoutingSession) + { + int numSentPackets = m_RoutingSession->NumSentPackets (); + m_RoutingSession->SetNumSentPackets (numSentPackets + numPackets); + } if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) SendClose (); if (isEmpty) @@ -641,10 +1081,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) { @@ -664,6 +1147,7 @@ namespace stream htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; + bool choking = m_IsChoking2; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs @@ -672,10 +1156,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++) @@ -697,14 +1187,36 @@ 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 + if (m_IsChoking2) + htobe16buf (packet + size + 2, DELAY_CHOKING_2); // set choking2 + else + 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 + m_IsChoking2 = false; LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } @@ -717,28 +1229,34 @@ namespace stream size += 4; // sendStreamID memset (packet + size, 0, 14); size += 14; // all zeroes - uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_FROM_INCLUDED; + uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_FROM_INCLUDED; + if (!m_DontSign) flags |= PACKET_FLAG_SIGNATURE_INCLUDED; 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 - 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 - m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + if (m_DontSign) + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + else + { + if (isOfflineSignature) + { + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature + } + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + 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 + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + } p.len = size; SendPackets (std::vector { &p }); LogPrint (eLogDebug, "Streaming: Ping of ", p.len, " bytes sent"); @@ -792,18 +1310,41 @@ namespace stream size++; // NACK count packet[size] = 0; size++; // resend delay - htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); + uint16_t flags = PACKET_FLAG_CLOSE; + if (!m_DontSign) flags |= PACKET_FLAG_SIGNATURE_INCLUDED; + bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); + if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; + htobe16buf (packet + size, flags); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); - htobe16buf (packet + size, signatureLen); // signature only - size += 2; // options size - uint8_t * signature = packet + size; - memset (packet + size, 0, signatureLen); - size += signatureLen; // signature - m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + if (m_DontSign) + { + memset (packet + size, 0, 2); // no options + size += 2; // options size + } + else + { + if (isOfflineSignature) + { + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature + } + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + htobe16buf (packet + size, signatureLen); // signature only + size += 2; // options size + uint8_t * signature = packet + size; + memset (packet + size, 0, signatureLen); + size += signatureLen; // signature + 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)); + if (m_RoutingSession) + { + int numSentPackets = m_RoutingSession->NumSentPackets (); + m_RoutingSession->SetNumSentPackets (numSentPackets + 1); + } LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } @@ -835,6 +1376,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); @@ -850,6 +1392,7 @@ namespace stream { if (!m_RemoteLeaseSet) { + CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { @@ -858,7 +1401,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 @@ -868,30 +1419,59 @@ 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) @@ -905,8 +1485,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 { @@ -923,10 +1508,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; @@ -943,6 +1528,98 @@ 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 < m_MaxWindowSize || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) + { + float winSize = m_WindowSize; + if (m_WindowDropTargetSize) + winSize = m_WindowDropTargetSize; + float maxWinSize = m_MaxWindowSize; + 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 > m_MaxWindowSize) m_WindowDropTargetSize = m_MaxWindowSize; + 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 > m_MaxWindowSize) m_WindowSize = m_MaxWindowSize; + m_WindowIncCounter--; + if (m_WindowSize >= maxWinSize) + { + m_WindowSize = maxWinSize; + break; + } + } + } + else + break; + } + UpdatePacingTime (); + } + m_LastWindowIncTime = ts; + if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked || m_IsClientChoked2) // resend packets + ResendPacket (); + else if (m_WindowSize > int(m_SentPackets.size ())) // send packets + SendBuffer (); + } + else // pass + ScheduleSend (); + } + } + void Stream::ScheduleResend () { if (m_Status != eStreamStatusTerminated) @@ -960,63 +1637,149 @@ 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 && !m_IsClientChoked && !m_IsClientChoked2) + { + for (auto it : m_NACKedPackets) + { + if (ts >= it->sendTime + m_RTO) + { + if (ts < it->sendTime + m_RTO*3) + 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*3) + it->resent = true; + else + it->resent = false; it->sendTime = ts; packets.push_back (it); + if (m_IsClientChoked2 && it->GetSeqn () == m_DropWindowDelaySequenceNumber) + m_IsClientChoked2 = false; + 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"); + if (m_WindowDropTargetSize) + m_LastWindowDropSize = m_WindowDropTargetSize; + else + m_LastWindowDropSize = m_WindowSize; + m_WindowDropTargetSize = m_LastWindowDropSize * 0.5; // -50% 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 + UpdatePacingTime (); + } + } + else if (m_IsTimeOutResend) + { + 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; + } + else if (!m_IsClientChoked && !m_IsClientChoked2) + SendBuffer (); + m_IsSendTime = false; + if (m_IsTimeOutResend) ScheduleResend (); // ^ m_IsTimeOutResend = false + else if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked || m_IsClientChoked2) 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) @@ -1032,9 +1795,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 (); } @@ -1044,23 +1811,43 @@ 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 + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase32 (), m_RemoteLeaseSet ? " expired" : " not found"); + if (!m_IsIncoming) // outgoing + { + auto requestCallback = [s = shared_from_this ()](std::shared_ptr ls) + { + if (!ls && s->m_Status == eStreamStatusOpen) // LeaseSet not found + { + // close the socket without sending FIN or RST + s->m_Status = eStreamStatusClosed; + s->AsyncClose (); + } + }; + + if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) + { + m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( + std::make_shared(m_RemoteIdentity), requestCallback); + return; // we keep m_RemoteLeaseSet for possible next request + } + else + { + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash (), requestCallback); // 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 { @@ -1101,10 +1888,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]; } } @@ -1121,11 +1913,82 @@ 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 () + { + double rtt = m_MinRTT + m_Jitter*2; + if (m_WindowDropTargetSize) + m_PacingTime = std::round (rtt*1000/m_WindowDropTargetSize); + else + m_PacingTime = std::round (rtt*1000/m_WindowSize); + if (m_MinPacingTime && m_PacingTime < m_MinPacingTime) + m_PacingTime = m_MinPacingTime; + } + + void Stream::ProcessWindowDrop () + { + if (m_WindowDropTargetSize) + m_LastWindowDropSize = m_WindowDropTargetSize * ((m_MinRTT + m_Jitter*4) / m_FastRTT); + else + m_LastWindowDropSize = m_WindowSize * ((m_MinRTT + m_Jitter*4) / m_FastRTT); + m_WindowDropTargetSize = m_LastWindowDropSize * 0.5; // -50% 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 + 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_IsFirstRttSample = true; + m_IsFirstACK = true; + m_WindowIncCounter = 0; // disable window growth + m_DropWindowDelaySequenceNumber = m_SequenceNumber - int(m_SentPackets.size ()) + INITIAL_WINDOW_SIZE; + m_IsWinDropped = true; // don't drop window twice + 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_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), + m_PendingIncomingTimer (m_Owner->GetService ()), + m_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ()) { } @@ -1146,6 +2009,7 @@ namespace stream void StreamingDestination::Stop () { ResetAcceptor (); + ResetPongHandler (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); { @@ -1192,6 +2056,8 @@ namespace stream { // pong LogPrint (eLogInfo, "Streaming: Pong received rSID=", packet->GetReceiveStreamID ()); + if (m_PongHandler != nullptr) + m_PongHandler (packet->from ? packet->from->GetDestinationPtr () : nullptr); DeletePacket (packet); return; } @@ -1202,13 +2068,25 @@ namespace stream if (it1 != m_IncomingStreams.end ()) { // already pending - LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); + LogPrint(eLogInfo, "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 { @@ -1310,13 +2188,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; } } @@ -1326,7 +2207,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); @@ -1339,7 +2220,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) @@ -1356,9 +2237,19 @@ namespace stream m_Acceptor = nullptr; } + void StreamingDestination::SetPongHandler (const PongHandler& handler) + { + m_PongHandler = handler; + } + + void StreamingDestination::ResetPongHandler () + { + m_PongHandler = nullptr; + } + 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 ()) { @@ -1412,14 +2303,18 @@ namespace stream } } - void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) + void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) { // unzip it Packet * uncompressed = NewPacket (); uncompressed->offset = 0; uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE); if (uncompressed->len) + { + uncompressed->from = from; HandleNextPacket (uncompressed); + } else DeletePacket (uncompressed); } @@ -1428,7 +2323,7 @@ namespace stream 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; @@ -1452,5 +2347,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 0e1f7e88..d3bd314e 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.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 * @@ -19,6 +19,7 @@ #include #include #include "Base.h" +#include "Gzip.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" @@ -26,6 +27,7 @@ #include "Garlic.h" #include "Tunnel.h" #include "util.h" // MemoryPool +#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -50,29 +52,50 @@ 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 = 12; + const double RTT_EWMA_ALPHA = 0.1; + const double SLOWRTT_EWMA_ALPHA = 0.02; + const double PREV_SPEED_KEEP_TIME_COEFF = 0.2; // 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 uint16_t DELAY_CHOKING_JAVA = 61000; // in milliseconds + const uint16_t DELAY_CHOKING_2 = 65535; // 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 = 0; // 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; + i2p::garlic::ECIESX25519AEADRatchetSession * from; - Packet (): len (0), offset (0), sendTime (0) {}; + Packet (): len (0), offset (0), sendTime (0), resent (false), from (nullptr) {}; 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 +103,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 +158,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 +185,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 +196,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); @@ -187,7 +212,7 @@ namespace stream 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 (); @@ -223,38 +248,62 @@ 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_IsAckSendScheduled; + bool m_IsIncoming, m_IsAckSendScheduled, m_IsNAcked, m_IsFirstACK, m_IsResendNeeded, + m_IsFirstRttSample, m_IsSendTime, m_IsWinDropped, m_IsChoking2, m_IsClientChoked, + m_IsClientChoked2, m_IsTimeOutResend, m_IsImmediateAckRequested, + m_IsRemoteLeaseChangeInProgress, m_IsBufferEmpty, m_IsJavaClient, m_DontSign; 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_MinRTT, m_SlowRTT, m_FastRTT; + float m_WindowSize, m_MaxWindowSize, m_LastWindowDropSize, m_WindowDropTargetSize; + int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample; + double m_Jitter; + uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds + m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime, m_LastACKRequestTime; // milliseconds + uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed + int m_NumResendAttempts, m_NumPacketsToSend; + uint64_t m_JitterAccum; + int m_JitterDiv; size_t m_MTU; }; @@ -263,6 +312,7 @@ namespace stream public: typedef std::function)> Acceptor; + typedef std::function PongHandler; StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); ~StreamingDestination (); @@ -274,22 +324,26 @@ 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 - + void SetPongHandler (const PongHandler& handler); + void ResetPongHandler (); + 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); + void HandleDataMessagePayload (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from); 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: @@ -307,13 +361,15 @@ namespace stream std::unordered_map > m_IncomingStreams; // receiveStreamID->stream std::shared_ptr m_LastStream; Acceptor m_Acceptor; + PongHandler m_PongHandler; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; 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; @@ -329,7 +385,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); @@ -348,9 +404,9 @@ namespace stream } 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 72f181a2..30b7708d 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,10 +12,14 @@ #include #include #include +#include +#include #include "Base.h" -namespace i2p { -namespace data { +namespace i2p +{ +namespace data +{ template class Tag { @@ -58,26 +62,22 @@ 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 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 ff777257..6b224685 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.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,6 +14,7 @@ #include #include #include +#include // for boost 1.89 namespace i2p { @@ -24,7 +25,12 @@ namespace util uint32_t GetMinutesSinceEpoch (); uint32_t GetHoursSinceEpoch (); - void GetCurrentDate (char * date); // 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 @@ -47,7 +53,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 6c2c52a7..9e1924ac 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.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,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" @@ -38,31 +43,10 @@ namespace tunnel i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } - TransitTunnelParticipant::~TransitTunnelParticipant () + std::string TransitTunnel::GetNextPeerName () const { - } - - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) - { - EncryptTunnelMsg (tunnelMsg, tunnelMsg); - - m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); - tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); - m_TunnelDataMsgs.push_back (tunnelMsg); - } - - void TransitTunnelParticipant::FlushTunnelDataMsgs () - { - if (!m_TunnelDataMsgs.empty ()) - { - auto num = m_TunnelDataMsgs.size (); - if (num > 1) - LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); - i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); - m_TunnelDataMsgs.clear (); - } - } + return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()); + } void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { @@ -73,31 +57,124 @@ namespace tunnel { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } + + TransitTunnelParticipant::~TransitTunnelParticipant () + { + } + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + { + EncryptTunnelMsg (tunnelMsg, tunnelMsg); + + m_NumTransmittedBytes += tunnelMsg->GetLength (); + htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); + // update header, expiration and size remain the same + tunnelMsg->SetMsgID (i2p::tunnel::tunnels.GetRng ()()); // assign new msgID + tunnelMsg->UpdateChks (); // new checksum TODO: remove later + m_TunnelDataMsgs.push_back (tunnelMsg); + } + + void TransitTunnelParticipant::FlushTunnelDataMsgs () + { + if (!m_TunnelDataMsgs.empty ()) + { + auto num = m_TunnelDataMsgs.size (); + if (num > 1) + LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); + if (!m_Sender) m_Sender = std::make_unique(); + m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear + } + } + + std::string TransitTunnelParticipant::GetNextPeerName () const + { + if (m_Sender) + { + auto transport = m_Sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { - std::unique_lock l(m_SendMutex); + std::lock_guard l(m_SendMutex); m_Gateway.SendBuffer (); } + std::string TransitTunnelGateway::GetNextPeerName () const + { + const auto& sender = m_Gateway.GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return TransitTunnel::GetNextPeerName () + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + } + return TransitTunnel::GetNextPeerName (); + } + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + std::lock_guard l(m_HandleMutex); + if (!m_Endpoint) m_Endpoint = std::make_unique(false); // transit endpoint is always outbound + m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg); } + void TransitTunnelEndpoint::FlushTunnelDataMsgs () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->FlushI2NPMsgs (); + } + } + + void TransitTunnelEndpoint::Cleanup () + { + if (m_Endpoint) + { + std::lock_guard l(m_HandleMutex); + m_Endpoint->Cleanup (); + } + } + + std::string TransitTunnelEndpoint::GetNextPeerName () const + { + if (!m_Endpoint) return ""; + auto hash = m_Endpoint->GetCurrentHash (); + if (hash) + { + const auto& sender = m_Endpoint->GetSender (); + if (sender) + { + auto transport = sender->GetCurrentTransport (); + if (transport) + return i2p::data::GetIdentHashAbbreviation (*hash) + "-" + + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); + else + return i2p::data::GetIdentHashAbbreviation (*hash); + } + } + return ""; + } + std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, @@ -119,5 +196,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 60e2f450..34bcc79f 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.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,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" @@ -32,11 +33,13 @@ namespace tunnel const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; + virtual std::string GetNextPeerName () const; // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg); - 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::AESKey m_LayerKey, m_IVKey; @@ -54,14 +57,16 @@ namespace tunnel 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 @@ -72,12 +77,13 @@ namespace tunnel 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; @@ -91,23 +97,68 @@ namespace tunnel TransitTunnelEndpoint (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): - TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), - m_Endpoint (false) {}; // transit endpoint is always outbound - - void Cleanup () { m_Endpoint.Cleanup (); } - - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); - size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } + 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 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 83b6cacd..2cff0b1f 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,7 @@ #define TRANSPORT_SESSION_H__ #include -#include +#include #include #include #include @@ -28,55 +28,66 @@ namespace transport const size_t IPV6_HEADER_SIZE = 40; const size_t UDP_HEADER_SIZE = 8; + template class SignedData { public: - SignedData () {} + SignedData (): m_Size(0) {} SignedData (const SignedData& other) { - m_Stream << other.m_Stream.rdbuf (); + m_Size = other.m_Size; + memcpy (m_Buf, other.m_Buf, m_Size); } void Reset () { - m_Stream.str(""); + m_Size = 0; } - void Insert (const uint8_t * buf, size_t len) + size_t Insert (const uint8_t * buf, size_t len) { - m_Stream.write ((char *)buf, len); + if (m_Size + len > sz) len = sz - m_Size; + memcpy (m_Buf + m_Size, buf, len); + m_Size += len; + return len; } template void Insert (T t) { - m_Stream.write ((char *)&t, sizeof (T)); + Insert ((const uint8_t *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { - return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + return ident->Verify (m_Buf, m_Size, signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { - keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + keys.Sign (m_Buf, m_Size, signature); } private: - std::stringstream m_Stream; + uint8_t m_Buf[sz]; + size_t m_Size; }; + const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds + 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_SendQueueSize (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 (); @@ -100,10 +111,30 @@ 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 @@ -115,30 +146,58 @@ namespace transport 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, m_SendQueueSize; 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 - // SOCKS5 proxy - 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 size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10; - const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22; + 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 aa326a92..98dbcd94 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,11 +1,12 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * 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,27 +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_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), - m_X25519KeysPairSupplier (15), // 15 pre-generated keys + 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_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0), m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), - m_LastInBandwidth15sUpdateBytes (0), m_LastOutBandwidth15sUpdateBytes (0), m_LastTransitBandwidth15sUpdateBytes (0), - m_LastBandwidth15sUpdateTime (0) + m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), + m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) { } @@ -164,8 +182,8 @@ namespace transport { 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); @@ -200,10 +218,10 @@ namespace transport 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 (); @@ -225,10 +243,10 @@ namespace transport i2p::context.SetStatusV6 (eRouterStatusProxy); } else - LogPrint(eLogError, "Transports: Can't set SSU2 proxy ", ssu2proxy); + LogPrint(eLogCritical, "Transports: Can't set SSU2 proxy ", ssu2proxy); } else - LogPrint(eLogError, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); + LogPrint(eLogCritical, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); } } @@ -239,7 +257,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) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); @@ -265,7 +283,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) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); @@ -292,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); } @@ -306,12 +324,22 @@ namespace transport 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)); } } @@ -320,7 +348,6 @@ namespace transport { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); - m_Peers.clear (); if (m_SSU2Server) { @@ -345,6 +372,7 @@ namespace transport delete m_Thread; m_Thread = nullptr; } + m_Peers.clear (); } void Transports::Run () @@ -364,65 +392,85 @@ namespace transport } } + void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit) + { + 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) + { + LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000); + return; + } + in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta; + out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta; + transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta; + } + void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + m_TrafficSamplePtr++; + if (m_TrafficSamplePtr == TRAFFIC_SAMPLE_COUNT) + m_TrafficSamplePtr = 0; - // updated every second - m_InBandwidth = m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes; - m_OutBandwidth = m_TotalSentBytes - m_LastOutBandwidthUpdateBytes; - m_TransitBandwidth = m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes; + TrafficSample& sample = m_TrafficSamples[m_TrafficSamplePtr]; + sample.Timestamp = i2p::util::GetMillisecondsSinceEpoch(); + sample.TotalReceivedBytes = m_TotalReceivedBytes; + sample.TotalSentBytes = m_TotalSentBytes; + sample.TotalTransitTransmittedBytes = m_TotalTransitTransmittedBytes; - m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; - m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; - m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; - - // updated every 15 seconds - auto delta = ts - m_LastBandwidth15sUpdateTime; - if (delta > 15 * 1000) - { - m_InBandwidth15s = (m_TotalReceivedBytes - m_LastInBandwidth15sUpdateBytes) * 1000 / delta; - m_OutBandwidth15s = (m_TotalSentBytes - m_LastOutBandwidth15sUpdateBytes) * 1000 / delta; - m_TransitBandwidth15s = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidth15sUpdateBytes) * 1000 / delta; - - m_LastBandwidth15sUpdateTime = ts; - m_LastInBandwidth15sUpdateBytes = m_TotalReceivedBytes; - m_LastOutBandwidth15sUpdateBytes = m_TotalSentBytes; - m_LastTransitBandwidth15sUpdateBytes = 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::IsBandwidthExceeded () const + int Transports::GetCongestionLevel (bool longTerm) const { - auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes - auto bw = std::max (m_InBandwidth15s, m_OutBandwidth15s); - return bw > 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); } - bool Transports::IsTransitBandwidthExceeded () const - { - auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes - return m_TransitBandwidth > limit; - } - - 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 ()) { @@ -430,61 +478,105 @@ 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, {r, ts})).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.SetRouter (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.priority.empty ()) + if (peer->priority.empty ()) SetPriority (peer); - while (peer.numAttempts < (int)peer.priority.size ()) + while (peer->numAttempts < (int)peer->priority.size ()) { - auto tr = peer.priority[peer.numAttempts]; - peer.numAttempts++; + auto tr = peer->priority[peer->numAttempts]; + peer->numAttempts++; switch (tr) { case i2p::data::RouterInfo::eNTCP2V4: @@ -492,12 +584,12 @@ namespace transport { if (!m_NTCP2Server) continue; std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? - peer.router->GetPublishedNTCP2V6Address () : peer.router->GetPublishedNTCP2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address (); + if (address && IsInReservedRange(address->host)) address = nullptr; if (address) { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); + auto s = std::make_shared (*m_NTCP2Server, peer->router, address); if( m_NTCP2Server->UsingProxy()) m_NTCP2Server->ConnectWithProxy(s); else @@ -511,12 +603,12 @@ namespace transport { if (!m_SSU2Server) continue; std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSU2V6) ? - peer.router->GetSSU2V6Address () : peer.router->GetSSU2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + peer->router->GetSSU2V6Address () : peer->router->GetSSU2V4Address (); + if (address && IsInReservedRange(address->host)) address = nullptr; if (address && address->IsReachableSSU ()) { - if (m_SSU2Server->CreateSession (peer.router, address)) + if (m_SSU2Server->CreateSession (peer->router, address)) return true; } break; @@ -524,10 +616,10 @@ namespace transport case i2p::data::RouterInfo::eNTCP2V6Mesh: { if (!m_NTCP2Server) continue; - auto address = peer.router->GetYggdrasilAddress (); + auto address = peer->router->GetYggdrasilAddress (); if (address) { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); + auto s = std::make_shared (*m_NTCP2Server, peer->router, address); m_NTCP2Server->Connect (s); return true; } @@ -539,10 +631,18 @@ namespace transport } LogPrint (eLogInfo, "Transports: No compatible addresses available"); - if (peer.router->IsReachableFrom (i2p::context.GetRouterInfo ())) + if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them - peer.Done (); - std::unique_lock l(m_PeersMutex); + 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; } @@ -555,7 +655,7 @@ namespace transport return true; } - void Transports::SetPriority (Peer& peer) const + void Transports::SetPriority (std::shared_ptr peer) { static const std::vector ntcp2Priority = @@ -574,41 +674,107 @@ namespace transport i2p::data::RouterInfo::eNTCP2V4, i2p::data::RouterInfo::eNTCP2V6Mesh }; - if (!peer.router) return; + if (!peer || !peer->router) return; auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & - peer.router->GetCompatibleTransports (true); - peer.numAttempts = 0; - peer.priority.clear (); - bool ssu2 = rand () & 1; + 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; - for (auto transport: priority) - if (transport & compatibleTransports) - peer.priority.push_back (transport); + 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.SetRouter (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 () @@ -631,16 +797,35 @@ namespace transport 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 + int testDelay = 0; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 if (router) { - if (i2p::context.GetStatus () != eRouterStatusTesting) - i2p::context.SetStatus (eRouterStatusTesting); - m_SSU2Server->StartPeerTest (router, true); + if (!i2p::context.GetTesting ()) + { + i2p::context.SetTesting (true); + // send first peer test immediately + m_SSU2Server->StartPeerTest (router, true); + } + 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 ()); } } @@ -650,16 +835,35 @@ namespace transport 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 + int testDelay = 0; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 if (router) { - if (i2p::context.GetStatusV6 () != eRouterStatusTesting) - i2p::context.SetStatusV6 (eRouterStatusTesting); - m_SSU2Server->StartPeerTest (router, false); + if (!i2p::context.GetTestingV6 ()) + { + i2p::context.SetTestingV6 (true); + // send first peer test immediately + m_SSU2Server->StartPeerTest (router, false); + } + 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 ()); } } @@ -680,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; @@ -688,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 @@ -702,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 @@ -714,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); - auto it = m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts })).first; - it->second.sessions.push_back (session); + 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; @@ -733,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 (); } } } @@ -755,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) @@ -767,41 +1016,46 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - it->second.sessions.remove_if ( + it->second->sessions.remove_if ( [](std::shared_ptr session)->bool { return !session || !session->IsEstablished (); }); - if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + 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; } } - 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)); } } @@ -811,7 +1065,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { PeerTest (); - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } @@ -820,13 +1074,16 @@ namespace transport std::shared_ptr Transports::GetRandomPeer (Filter filter) const { if (m_Peers.empty()) return nullptr; + auto ts = i2p::util::GetSecondsSinceEpoch (); bool found = false; i2p::data::IdentHash ident; { uint16_t inds[3]; RAND_bytes ((uint8_t *)inds, sizeof (inds)); - std::unique_lock l(m_PeersMutex); - inds[0] %= m_Peers.size (); + 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, inds[0]); // try random peer @@ -858,9 +1115,11 @@ namespace transport it = it1; while (it != it2 && it != m_Peers.end ()) { - if (filter (it->second)) + if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) { ident = it->first; + it->second->lastSelectionTime = ts; found = true; break; } @@ -872,9 +1131,11 @@ namespace transport it = m_Peers.begin (); while (it != it1 && it != m_Peers.end ()) { - if (filter (it->second)) + if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) { ident = it->first; + it->second->lastSelectionTime = ts; found = true; break; } @@ -886,9 +1147,11 @@ namespace transport it = it2; while (it != m_Peers.end ()) { - if (filter (it->second)) + if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && + filter (it->second)) { ident = it->first; + it->second->lastSelectionTime = ts; found = true; break; } @@ -904,12 +1167,13 @@ namespace transport std::shared_ptr Transports::GetRandomPeer (bool isHighBandwidth) const { return GetRandomPeer ( - [isHighBandwidth](const Peer& peer)->bool + [isHighBandwidth](std::shared_ptr peer)->bool { - // connected and not overloaded - return !peer.router && !peer.sessions.empty () && - peer.sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && - (!isHighBandwidth || peer.isHighBandwidth); + // 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); }); } @@ -926,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); @@ -950,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) @@ -961,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; @@ -1003,6 +1283,11 @@ namespace transport } } + 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); @@ -1041,7 +1326,7 @@ namespace transport std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); if (!yggaddress.empty ()) { - yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); + yggaddr = boost::asio::ip::make_address (yggaddress).to_v6 (); if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || !i2p::util::net::IsLocalAddress (yggaddr)) { @@ -1086,7 +1371,7 @@ namespace transport if (ipv6) { std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); - auto addr = boost::asio::ip::address_v6::from_string (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 } @@ -1114,7 +1399,6 @@ namespace transport else i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish } - } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index dc97a952..fcd2cfc6 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,14 +11,17 @@ #include #include +#include #include #include #include +#include #include #include #include #include #include +#include #include #include "TransportSession.h" #include "SSU2.h" @@ -26,6 +29,7 @@ #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" +#include "util.h" namespace i2p { @@ -52,10 +56,11 @@ 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; }; @@ -64,42 +69,63 @@ namespace transport const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds const size_t PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE = 25; + 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; + bool isHighBandwidth, isEligible; Peer (std::shared_ptr r, uint64_t ts): numAttempts (0), router (r), creationTime (ts), nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL), - isHighBandwidth (false) + lastSelectionTime (0), isHighBandwidth (false), isEligible (false) { - if (router) - isHighBandwidth = router->IsHighBandwidth (); + 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; - if (router) - isHighBandwidth = router->IsHighBandwidth (); + 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: @@ -109,6 +135,7 @@ namespace transport void Start (bool enableNTCP2=true, bool enableSSU2=true); void Stop (); + bool IsRunning () const { return m_IsRunning; } bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } @@ -116,12 +143,12 @@ namespace transport 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); @@ -139,38 +166,40 @@ namespace transport uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; }; uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; }; uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; }; - bool IsBandwidthExceeded () const; - bool IsTransitBandwidthExceeded () const; + int GetCongestionLevel (bool longTerm) const; size_t GetNumPeers () const { return m_Peers.size (); }; 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); - void SetPriority (Peer& peer) const; + 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 DetectExternalIP (); @@ -182,37 +211,39 @@ namespace transport 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::io_context * m_Service; + boost::asio::executor_work_guard * m_Work; boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer; 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; + TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT]; + int m_TrafficSamplePtr; + // Bandwidth per second uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; - uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; - - // Bandwidth every 15 seconds + // Bandwidth during last 15 seconds uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s; - uint64_t m_LastInBandwidth15sUpdateBytes, m_LastOutBandwidth15sUpdateBytes, m_LastTransitBandwidth15sUpdateBytes; - uint64_t m_LastBandwidth15sUpdateTime; + // 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: diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 372c3ff9..f4aa01ce 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,11 +29,11 @@ namespace i2p { namespace tunnel { - Tunnel::Tunnel (std::shared_ptr config): + Tunnel::Tunnel (std::shared_ptr config): 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) { } @@ -44,7 +44,13 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; + bool insertPhonyRecord = m_Config->IsInbound() && numHops < MAX_NUM_RECORDS; + if (insertPhonyRecord) + { + m_Config->CreatePhonyHop (); + numHops++; + } + int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; @@ -52,8 +58,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; TunnelHopConfig * hop = m_Config->GetFirstHop (); @@ -61,7 +66,7 @@ namespace tunnel while (hop) { uint32_t msgID; - if (hop->next) // we set replyMsgID for last hop only + if (hop->next && hop->next->ident) // we set replyMsgID for last non-phony hop only RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; @@ -89,21 +94,30 @@ namespace tunnel } hop = hop->prev; } + // delete phony hop after encryption + if (insertPhonyRecord) m_Config->DeletePhonyHop (); + 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 +130,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 +138,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 +171,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 +187,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 +220,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 +272,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 +315,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 +360,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,24 +406,40 @@ namespace tunnel Tunnels tunnels; - Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), - m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal avarage - m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(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); @@ -390,7 +481,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) { @@ -404,10 +495,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; @@ -434,26 +527,16 @@ namespace tunnel } } - bool Tunnels::AddTransitTunnel (std::shared_ptr tunnel) - { - if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) - m_TransitTunnels.push_back (tunnel); - else - { - LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); - return false; - } - return true; - } - void Tunnels::Start () { 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) @@ -470,18 +553,22 @@ 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); + auto mts = i2p::util::GetMillisecondsSinceEpoch (); 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 || msg->IsExpired (mts)) continue; std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) @@ -509,29 +596,38 @@ 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 = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr; - if (msg) - { - prevTunnelID = tunnelID; - prevTunnel = tunnel; - numMsgs++; - } - 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()) @@ -543,7 +639,7 @@ namespace tunnel ManageTunnels (ts); lastTs = ts; } - if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL || // manage pools every 5 secondsts + if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL || // manage pools every 5 seconds ts + TUNNEL_POOLS_MANAGE_INTERVAL < lastPoolsTs) { ManageTunnelPools (ts); @@ -585,19 +681,95 @@ 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::HandleShortTunnelBuildMsg (std::shared_ptr msg) + { + 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); - ManageInboundTunnels (ts); - ManageOutboundTunnels (ts); - ManageTransitTunnels (ts); + std::vector > tunnelsToRecreate; + ManageInboundTunnels (ts, tunnelsToRecreate); + ManageOutboundTunnels (ts, tunnelsToRecreate); + // rec-create in random order + if (!tunnelsToRecreate.empty ()) + { + if (tunnelsToRecreate.size () > 1) + std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng); + for (auto& it: tunnelsToRecreate) + it->Recreate (); + } } void Tunnels::ManagePendingTunnels (uint64_t ts) @@ -628,11 +800,11 @@ namespace tunnel while (hop) { if (hop->ident) - { - auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); - if (profile) - profile->TunnelNonReplied (); - } + i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), + [](std::shared_ptr profile) + { + if (profile) profile->TunnelNonReplied (); + }); hop = hop->next; } } @@ -660,14 +832,15 @@ namespace tunnel } } - void Tunnels::ManageOutboundTunnels (uint64_t ts) + void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -683,10 +856,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool has been reconfigured and this is old if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateOutboundTunnel (tunnel); - } + toRecreate.push_back (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); @@ -697,11 +867,11 @@ namespace tunnel 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 ( @@ -711,19 +881,19 @@ namespace tunnel } } - void Tunnels::ManageInboundTunnels (uint64_t ts) + void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); - m_Tunnels.erase (tunnel->GetTunnelID ()); + RemoveTunnel (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); } else @@ -735,10 +905,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); // let it die if the tunnel pool was reconfigured and has different number of hops if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) - { - tunnel->SetRecreated (true); - pool->RecreateInboundTunnel (tunnel); - } + toRecreate.push_back (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) @@ -761,7 +928,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; @@ -773,7 +940,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; @@ -785,26 +952,6 @@ namespace tunnel } } - void Tunnels::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, "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); @@ -820,7 +967,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); } @@ -878,7 +1025,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 (); @@ -908,7 +1055,7 @@ namespace tunnel inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); - m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; + AddTunnel (inboundTunnel); return inboundTunnel; } @@ -942,21 +1089,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 @@ -970,5 +1108,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 b690d1eb..e9d78083 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include +#include #include "util.h" #include "Queue.h" #include "Crypto.h" @@ -39,8 +40,10 @@ 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 @@ -64,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 { @@ -77,24 +81,25 @@ namespace tunnel /** function for visiting a hops stored in a tunnel */ typedef std::function)> TunnelHopVisitor; - Tunnel (std::shared_ptr config); + Tunnel (std::shared_ptr config); ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); - std::shared_ptr GetTunnelConfig () const { return m_Config; } + std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; bool IsShortBuildMessage () const { return m_IsShortBuildMessage; }; 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; }; @@ -102,50 +107,51 @@ 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); private: - std::shared_ptr m_Config; + std::shared_ptr m_Config; std::vector m_Hops; bool m_IsShortBuildMessage; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null 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 { public: - OutboundTunnel (std::shared_ptr config): - Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; + OutboundTunnel (std::shared_ptr config): + 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: @@ -154,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); + InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; + 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; @@ -176,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: @@ -189,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: @@ -212,23 +226,30 @@ 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 (); - bool AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); 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; } + std::mt19937& GetRng () { return m_Rng; } + private: template @@ -239,12 +260,14 @@ 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 (uint64_t ts); - void ManageOutboundTunnels (uint64_t ts); - void ManageInboundTunnels (uint64_t ts); - void ManageTransitTunnels (uint64_t ts); + void ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate); + void ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate); void ManagePendingTunnels (uint64_t ts); template void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); @@ -275,36 +298,41 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; 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; + 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 (); }; + 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_TotalNumSuccesiveTunnelCreations + m_TotalNumFailedTunnelCreations; diff --git a/libi2pd/TunnelBase.cpp b/libi2pd/TunnelBase.cpp new file mode 100644 index 00000000..b5a4a0b3 --- /dev/null +++ b/libi2pd/TunnelBase.cpp @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2024, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include "Transports.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >&& msgs) + { + if (msgs.empty ()) return; + auto currentTransport = m_CurrentTransport.lock (); + if (!currentTransport) + { + // try to obtain transport from pending request or send thought transport is not complete + if (m_PendingTransport.valid ()) // pending request? + { + if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + { + // pending request complete + currentTransport = m_PendingTransport.get (); // take transports used in pending request + if (currentTransport) + { + if (currentTransport->IsEstablished ()) + m_CurrentTransport = currentTransport; + else + currentTransport = nullptr; + } + } + else // still pending + { + // send through transports, but don't update pending transport + i2p::transport::transports.SendMessages (to, std::move (msgs)); + return; + } + } + } + if (currentTransport) // session is good + // send to session directly + currentTransport->SendI2NPMessages (msgs); + else // no session yet + // send through transports + m_PendingTransport = i2p::transport::transports.SendMessages (to, std::move (msgs)); + + } + + void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to, + std::list >& msgs) + { + std::list > msgs1; + msgs.swap (msgs1); + SendMessagesTo (to, std::move (msgs1)); + } + + void TunnelTransportSender::Reset () + { + m_CurrentTransport.reset (); + if (m_PendingTransport.valid ()) + m_PendingTransport = std::future >(); + } +} +} diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index d58ec2d7..39d6e780 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,12 +11,19 @@ #include #include +#include +#include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { +namespace transport +{ + class TransportSession; +} + namespace tunnel { const size_t TUNNEL_DATA_MSG_SIZE = 1028; @@ -76,6 +83,25 @@ namespace tunnel return t1 < t2; } }; + + class TunnelTransportSender final + { + public: + + TunnelTransportSender () = default; + ~TunnelTransportSender () = default; + + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >&& msgs); + void SendMessagesTo (const i2p::data::IdentHash& to, std::list >& msgs); // send and clear + + std::shared_ptr GetCurrentTransport () const { return m_CurrentTransport.lock (); } + void Reset (); + + private: + + std::weak_ptr m_CurrentTransport; + std::future > m_PendingTransport; + }; } } diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index e19b515d..1ae8f3e9 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.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 * @@ -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) @@ -220,6 +219,42 @@ namespace tunnel return tag; } + void LongPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetIdentHash (), 16); + memcpy (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, i2p::transport::transports.GetNextX25519KeysPair ()->GetPublicKey (), 32); + RAND_bytes (record + 48, TUNNEL_BUILD_RECORD_SIZE - 48); + } + + void ShortPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetIdentHash (), 16); + memcpy (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, i2p::transport::transports.GetNextX25519KeysPair ()->GetPublicKey (), 32); + RAND_bytes (record + 48, SHORT_TUNNEL_BUILD_RECORD_SIZE - 48); + } + + TunnelConfig::TunnelConfig (const std::vector >& peers, + bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports): + m_IsShort (isShort), m_FarEndTransports (farEndTransports) + { + // inbound + CreatePeers (peers); + m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); + } + + TunnelConfig::TunnelConfig (const std::vector >& peers, + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, + i2p::data::RouterInfo::CompatibleTransports farEndTransports): + m_IsShort (isShort), m_FarEndTransports (farEndTransports) + { + // outbound + CreatePeers (peers); + m_FirstHop->isGateway = false; + m_LastHop->SetReplyHop (replyTunnelID, replyIdent); + } + void TunnelConfig::CreatePeers (const std::vector >& peers) { TunnelHopConfig * prev = nullptr; @@ -246,5 +281,35 @@ namespace tunnel } m_LastHop = prev; } + + void TunnelConfig::CreatePhonyHop () + { + if (m_LastHop && m_LastHop->ident) + { + TunnelHopConfig * hop = nullptr; + if (m_IsShort) + hop = new ShortPhonyTunnelHopConfig (); + else + hop = new LongPhonyTunnelHopConfig (); + if (hop) + { + hop->prev = m_LastHop; + m_LastHop->next = hop; + m_LastHop = hop; + } + } + } + + void TunnelConfig::DeletePhonyHop () + { + if (m_LastHop && !m_LastHop->ident) + { + if (m_LastHop->prev) m_LastHop->prev->next = nullptr; + else m_FirstHop = nullptr; + auto tmp = m_LastHop; + m_LastHop = m_LastHop->prev; + delete tmp; + } + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 9dcf2c02..a024f359 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.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 * @@ -77,28 +77,35 @@ namespace tunnel uint64_t GetGarlicKey (uint8_t * key) const override; }; + struct PhonyTunnelHopConfig: public ECIESTunnelHopConfig + { + PhonyTunnelHopConfig (): ECIESTunnelHopConfig (nullptr) {} + uint8_t GetRetCode (const uint8_t * records) const override { return 0; } + bool DecryptBuildResponseRecord (uint8_t * records) const override { return true; } + void DecryptRecord (uint8_t * records, int index) const override {} // do nothing + }; + + struct LongPhonyTunnelHopConfig: public PhonyTunnelHopConfig + { + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + }; + + struct ShortPhonyTunnelHopConfig: public PhonyTunnelHopConfig + { + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + }; + class TunnelConfig { public: TunnelConfig (const std::vector >& peers, - bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // inbound - m_IsShort (isShort), m_FarEndTransports (farEndTransports) - { - CreatePeers (peers); - m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); - } + bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports); // inbound TunnelConfig (const std::vector >& peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, - i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // outbound - m_IsShort (isShort), m_FarEndTransports (farEndTransports) - { - CreatePeers (peers); - m_FirstHop->isGateway = false; - m_LastHop->SetReplyHop (replyTunnelID, replyIdent); - } - + i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports); // outbound + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; @@ -181,6 +188,11 @@ namespace tunnel return peers; } + size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; }; + + void CreatePhonyHop (); + void DeletePhonyHop (); + 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 bafc1c2d..26367aa6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 () @@ -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 () @@ -296,10 +310,12 @@ 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; @@ -314,7 +330,7 @@ namespace tunnel } 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 () @@ -335,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) @@ -346,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) @@ -396,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; } } @@ -409,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; { @@ -429,54 +516,64 @@ 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; } @@ -496,9 +593,9 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - auto r = i2p::transport::transports.GetRandomPeer (!IsExploratory ()); - if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && - (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable + 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); @@ -508,7 +605,7 @@ 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"); @@ -520,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); } @@ -544,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; } @@ -554,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; } @@ -567,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]; @@ -638,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); @@ -703,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) { @@ -747,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(); @@ -767,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 7d952559..0ebfd1ac 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ #include #include #include +#include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" @@ -30,7 +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 = 2; + const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 3; + const int TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS = 3; class Tunnel; class InboundTunnel; @@ -56,11 +58,11 @@ namespace tunnel class TunnelPool: public std::enable_shared_from_this // per local destination { - typedef std::function(std::shared_ptr, bool)> SelectHopFunc; + 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; }; @@ -76,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; }; @@ -102,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; } @@ -112,9 +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 (); @@ -123,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: @@ -139,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 a298e4b4..7dc11157 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.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 * @@ -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 (); } @@ -67,11 +67,15 @@ namespace api 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 369f999e..0bd19d23 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,6 +8,7 @@ #include #include +#include #include #include @@ -15,7 +16,7 @@ #include "Log.h" #include "I2PEndian.h" -#if not defined (__FreeBSD__) +#if !defined (__FreeBSD__) && !defined(_MSC_VER) #include #endif @@ -27,6 +28,17 @@ # include #endif +#if defined(__HAIKU__) +#include +#include +#include +#include +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#include +#endif +#endif + #ifdef _WIN32 #include #include @@ -37,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)) @@ -75,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; @@ -98,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 { @@ -146,6 +172,14 @@ 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__) # if (!defined(MAC_OS_X_VERSION_10_6) || \ @@ -161,7 +195,23 @@ namespace util #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 } @@ -171,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; @@ -179,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); @@ -190,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); @@ -198,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); @@ -234,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; @@ -264,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; @@ -314,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)); @@ -339,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; @@ -347,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)); @@ -407,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; @@ -426,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); } } } @@ -442,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); @@ -452,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 } @@ -466,12 +513,6 @@ namespace net // Hurricane Electric return 1480; break; - case 0x2a06a003: - case 0x2a06a004: - case 0x2a06a005: - // route48 - return 1420; - break; default: ; } return 1500; @@ -512,7 +553,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); @@ -523,7 +564,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); @@ -531,12 +572,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; @@ -580,7 +620,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 } @@ -600,9 +640,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 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"), @@ -619,15 +660,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 std::array 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"), @@ -637,7 +680,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 e2037212..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 * @@ -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); diff --git a/libi2pd/version.h b/libi2pd/version.h index e1206d21..f88133e4 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 46 +#define I2PD_VERSION_MINOR 58 #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 57 +#define I2P_VERSION_MICRO 67 #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 652dfb8d..c7cd80c0 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& subscription, 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) { @@ -320,6 +379,7 @@ namespace client LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); StartLookups (); + ScheduleCacheUpdate (); } } @@ -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 ()) @@ -403,10 +477,20 @@ namespace client if (!addr) return false; - i2p::data::IdentityEx ident; - if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) - return true; - + 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; } @@ -424,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 ())); } @@ -435,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; @@ -482,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; @@ -517,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); } @@ -537,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; } @@ -561,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"); } } @@ -581,7 +674,7 @@ namespace client void AddressBook::LoadLocal () { if (!m_Storage) return; - std::map> localAddresses; + AddressBookStorage::Addresses localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { @@ -624,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) @@ -655,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 () @@ -679,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) { @@ -688,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 @@ -735,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 ('.'); @@ -765,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; } @@ -802,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) { } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 9b2c7e7e..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,16 +84,16 @@ namespace client class AddressBook { public: - + AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); - std::shared_ptr GetAddress (const std::string& address); + std::shared_ptr GetAddress (std::string_view address); std::shared_ptr GetFullAddress (const std::string& address); - std::shared_ptr FindAddress (const std::string& address); - void LookupAddress (const std::string& address); + std::shared_ptr FindAddress (std::string_view address); + void LookupAddress (std::string_view address); void InsertAddress (const std::string& address, const std::string& jump); // for jump links void InsertFullAddress (std::shared_ptr address); @@ -99,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 (); @@ -115,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; }; @@ -135,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 5f738380..08d3c3f0 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, 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) { @@ -325,19 +343,28 @@ namespace client } } - void BOBCommandSession::SendReplyOK (const char * msg) + void BOBCommandSession::SendReplyOK (std::string_view msg) { std::ostream os(&m_SendBuffer); os << "OK"; - if(msg) - { + if (!msg.empty ()) os << " " << msg; - } os << std::endl; Send (); - } - - void BOBCommandSession::SendReplyError (const char * msg) + } + + void BOBCommandSession::SendReplyOK (const std::vector& strings) + { + std::ostream os(&m_SendBuffer); + os << "OK"; + if (!strings.empty ()) os << " "; + for (auto& it: strings) + os << it; + os << std::endl; + Send (); + } + + void BOBCommandSession::SendReplyError (std::string_view msg) { std::ostream os(&m_SendBuffer); os << "ERROR " << msg << std::endl; @@ -348,23 +375,32 @@ namespace client { std::ostream os(&m_SendBuffer); os << "BOB 00.00.10" << std::endl; - SendReplyOK(); + SendReplyOK(std::string_view()); // empty string } - void BOBCommandSession::SendRaw (const char * data) + void BOBCommandSession::SendRaw (std::string_view data) { std::ostream os(&m_SendBuffer); 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 + const auto getProxyType = [](const i2p::client::I2PService* proxy) -> std::string { + if (!proxy) return "NONE"; + if (dynamic_cast(proxy)) return "SOCKS"; + if (dynamic_cast(proxy)) return "HTTPPROXY"; + return "UNKNOWN"; + }; + const auto isProxyRunning = [](const i2p::client::I2PService* proxy) -> bool { + return proxy != nullptr; + }; // tunnel info const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); @@ -373,10 +409,14 @@ 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; + + const i2p::client::I2PService* proxy = m_Owner.GetProxy(nickname); + const std::string proxyType = getProxyType(proxy); + const bool proxyStatus = isProxyRunning(proxy); // build line std::stringstream ss; @@ -385,7 +425,8 @@ namespace client << "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " " << "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " " << "INPORT: " << inport << " " << "INHOST: " << inhost << " " - << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost; + << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost << " " + << "PROXYTYPE: "<< proxyType << " " << "PROXYSTART: " << bool_str(proxyStatus); out = ss.str(); } @@ -425,7 +466,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 +477,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,15 +487,55 @@ 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); } - if (m_InPort) - m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); - if (m_OutPort && !m_OutHost.empty ()) - m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); - m_CurrentDestination->Start (); + if (!m_tunnelType.has_value()) + { + if (m_InPort) + m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); + if (m_OutPort && !m_OutHost.empty ()) + m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); + m_CurrentDestination->Start (); + } + else + { + switch (*m_tunnelType) + { + case TunnelType::SOCKS: + try + { + auto SocksProxy = std::make_unique(m_Nickname, m_InHost, m_InPort, + false, m_OutHost, m_OutPort, m_CurrentDestination->GetLocalDestination()); + SocksProxy->Start(); + m_Owner.SetProxy(m_Nickname, std::move(SocksProxy)); + } + catch (std::exception& e) + { + LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); + ThrowFatal ("Unable to start SOCKS Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ()); + } + break; + case TunnelType::HTTP_PROXY: + try + { + auto HttpProxy = std::make_unique(m_Nickname, m_InHost, m_InPort, + m_OutHost, true, true, m_CurrentDestination->GetLocalDestination()); + HttpProxy->Start(); + m_Owner.SetProxy(m_Nickname, std::move(HttpProxy)); + } + catch (std::exception& e) + { + LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what()); + ThrowFatal ("Unable to start HTTP Proxy at ", m_InHost, ":", m_InPort, ": ", e.what ()); + } + break; + default: + SendReplyError("Unsupported tunnel type."); + return; + } + } SendReplyOK ("Tunnel starting"); m_IsActive = true; } @@ -468,10 +549,15 @@ namespace client return; } auto dest = m_Owner.FindDestination (m_Nickname); + auto proxy = m_Owner.GetProxy (m_Nickname); if (dest) { dest->StopTunnels (); SendReplyOK ("Tunnel stopping"); + if (proxy) + { + m_Owner.RemoveProxy (m_Nickname); + } } else SendReplyError ("tunnel not found"); @@ -487,9 +573,7 @@ namespace client if (!dest) { m_Nickname = operand; - std::string msg ("Nickname set to "); - msg += m_Nickname; - SendReplyOK (msg.c_str ()); + SendReplyOK ({ "Nickname set to ", m_Nickname }); } else SendReplyError ("tunnel is active"); @@ -504,18 +588,17 @@ namespace client if(*operand) { m_CurrentDestination = m_Owner.FindDestination (operand); + auto proxy = m_Owner.GetProxy (operand); if (m_CurrentDestination) { m_Keys = m_CurrentDestination->GetKeys (); m_IsActive = m_CurrentDestination->IsRunning (); + if(proxy) + m_IsActive = true; m_Nickname = operand; } if (m_Nickname == operand) - { - std::string msg ("Nickname set to "); - msg += m_Nickname; - SendReplyOK (msg.c_str ()); - } + SendReplyOK ({"Nickname set to ", m_Nickname}); else SendReplyError ("no nickname has been set"); } @@ -526,7 +609,7 @@ namespace client void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); - i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; + i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (*operand) { @@ -548,14 +631,14 @@ namespace client m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); - SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); } void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); if (*operand && m_Keys.FromBase64 (operand)) - SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); else SendReplyError ("invalid keys"); } @@ -564,7 +647,7 @@ namespace client { LogPrint (eLogDebug, "BOB: getkeys"); if (m_Keys.GetPublic ()) // keys are set ? - SendReplyOK (m_Keys.ToBase64 ().c_str ()); + SendReplyOK (m_Keys.ToBase64 ()); else SendReplyError ("keys are not set"); } @@ -573,7 +656,7 @@ namespace client { LogPrint (eLogDebug, "BOB: getdest"); if (m_Keys.GetPublic ()) // keys are set ? - SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); else SendReplyError ("keys are not set"); } @@ -595,9 +678,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 +708,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,14 +749,20 @@ 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 auto leaseSet = localDestination->FindLeaseSet (addr->identHash); if (leaseSet) { - SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); + SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ()); return; } } @@ -676,7 +771,7 @@ namespace client auto requstCallback = [s](std::shared_ptr ls) { if (ls) - s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); + s->SendReplyOK (ls->GetIdentity ()->ToBase64 ()); else s->SendReplyError ("LeaseSet Not found"); }; @@ -702,7 +797,7 @@ namespace client } auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); if (ls) - SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); + SendReplyOK (ls->GetIdentity ()->ToBase64 ()); else SendReplyError ("Local LeaseSet Not found"); } @@ -710,6 +805,94 @@ namespace client SendReplyError ("empty lookup address"); } + void BOBCommandSession::PingCommandHandler (const char * operand, size_t len) + { + LogPrint (eLogDebug, "BOB: ping ", operand); + if (*operand) + { + auto addr = context.GetAddressBook ().GetAddress (operand); + if (!addr) + { + SendReplyError ("Address Not found"); + return; + } + 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 + auto leaseSet = localDestination->FindLeaseSet (addr->identHash); + if (leaseSet) + { + boost::asio::post (localDestination->GetService (), + std::bind (&BOBCommandSession::SendPing, shared_from_this (), leaseSet)); + return; + } + } + // request LeaseSet + if (addr->IsIdentHash ()) + localDestination->RequestDestination (addr->identHash, + std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1)); + else + localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, + std::bind (&BOBCommandSession::SendPing, shared_from_this (), std::placeholders::_1)); + } + else + SendReplyError ("empty ping address"); + } + + void BOBCommandSession::SendPing (std::shared_ptr ls) + { + if (!ls) + { + SendReplyError ("LeaseSet Not found"); + return; + } + auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? + m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + if (!localDestination) + { + SendReplyError ("No local destination"); + return; + } + auto streamingDestination = localDestination->GetStreamingDestination (); + if (!streamingDestination) + { + SendReplyError ("No streaming destination"); + return; + } + auto timer = std::make_shared(localDestination->GetService ()); + timer->expires_from_now (boost::posix_time::milliseconds(BOB_PING_TIMEOUT)); + timer->async_wait ([streamingDestination, s = shared_from_this ()](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogDebug, "BOB: Pong not received after ", BOB_PING_TIMEOUT, " millliseconds"); + streamingDestination->ResetPongHandler (); + s->SendReplyError ("timeout"); + } + }); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + streamingDestination->SetPongHandler ( + [streamingDestination, timer, ts, s = shared_from_this ()](const i2p::data::IdentHash * from) + { + int t = i2p::util::GetMillisecondsSinceEpoch () - ts; + if (t < 0) t = 0; + streamingDestination->ResetPongHandler (); + timer->cancel (); + if (from) + s->SendReplyOK ({"pong ", "from ", from->ToBase32(), ".b32.i2p: time=", std::to_string (t), " ms"}); + else + s->SendReplyOK ({"pong: time=", std::to_string (t), " ms"}); + }); + streamingDestination->SendPing (ls); + } + void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: clear"); @@ -727,7 +910,7 @@ namespace client for (const auto& it: destinations) { BuildStatusLine(false, it.second, statusLine); - SendRaw(statusLine.c_str()); + SendRaw(statusLine); if(m_Nickname.compare(it.second->GetNickname()) == 0) sentCurrent = true; } @@ -736,7 +919,7 @@ namespace client // add the current tunnel to the list. // this is for the incomplete tunnel which has not been started yet. BuildStatusLine(true, m_CurrentDestination, statusLine); - SendRaw(statusLine.c_str()); + SendRaw(statusLine); } SendReplyOK ("Listing done"); } @@ -747,14 +930,10 @@ namespace client const char * value = strchr (operand, '='); if (value) { - std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; - msg += operand; + SendReplyOK ({ "option ", operand, " set to ", value + 1 }); *(const_cast(value)) = '='; - msg += " set to "; - msg += value + 1; - SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); @@ -772,7 +951,7 @@ namespace client { // tunnel destination exists BuildStatusLine(false, dest, statusLine); - SendReplyOK(statusLine.c_str()); + SendReplyOK(statusLine); } else { @@ -780,7 +959,7 @@ namespace client { // tunnel is incomplete / has not been started yet BuildStatusLine(true, nullptr, statusLine); - SendReplyOK(statusLine.c_str()); + SendReplyOK(statusLine); } else { @@ -799,24 +978,44 @@ namespace client { ss << " " << x.first; } - const std::string &str = ss.str(); - SendReplyOK(str.c_str()); + SendReplyOK(ss.str ()); } else { auto it = helpStrings.find(operand); if (it != helpStrings.end ()) { - SendReplyOK(it->second.c_str()); + SendReplyOK(it->second); return; } SendReplyError("No such command"); } } + + void BOBCommandSession::SetTunnelTypeCommandHandler (const char * operand, size_t len) + { + std::string_view sv(operand, len); + LogPrint (eLogDebug, "BOB: settunneltype ", operand); + if (sv == "socks") + { + m_tunnelType = TunnelType::SOCKS; + SendReplyOK ("tunnel type set to SOCKS"); + } + else if (sv == "httpproxy") + { + m_tunnelType = TunnelType::HTTP_PROXY; + SendReplyOK ("tunnel type set to HTTP proxy"); + } + else + { + m_tunnelType.reset(); + SendReplyError ("no tunnel type has been set"); + } + } - 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; @@ -836,11 +1035,13 @@ namespace client m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler; + m_CommandHandlers[BOB_COMMAND_PING] = &BOBCommandSession::PingCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; m_CommandHandlers[BOB_COMMAND_HELP] = &BOBCommandSession::HelpCommandHandler; + m_CommandHandlers[BOB_COMMAND_SETTUNNELTYPE] = &BOBCommandSession::SetTunnelTypeCommandHandler; // command -> help string m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP; m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT; @@ -863,14 +1064,13 @@ namespace client m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION; m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS; m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP; + m_HelpStrings[BOB_COMMAND_SETTUNNELTYPE] = BOB_HELP_SETTUNNELTYPE; } BOBCommandChannel::~BOBCommandChannel () { if (IsRunning ()) Stop (); - for (const auto& it: m_Destinations) - delete it.second; } void BOBCommandChannel::Start () @@ -887,9 +1087,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,18 +1098,39 @@ 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 ()) return it->second; return nullptr; } + + void BOBCommandChannel::SetProxy (const std::string& name, std::unique_ptr proxy) + { + m_proxy[name] = std::move(proxy); + } + + const I2PService* BOBCommandChannel::GetProxy(const std::string& name) const + { + auto it = m_proxy.find(name); + if (it != m_proxy.end() && it->second) + return it->second.get(); + return nullptr; + } + + void BOBCommandChannel::RemoveProxy(const std::string& name) + { + auto it = m_proxy.find (name); + if (it != m_proxy.end ()) + { + m_proxy.erase (it); + } + } void BOBCommandChannel::Accept () { diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 2e6314e2..a3f6754f 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 * @@ -14,18 +14,25 @@ #include #include #include +#include +#include +#include #include #include "util.h" #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" #include "LeaseSet.h" +#include "SOCKS.h" +#include "HTTPProxy.h" namespace i2p { namespace client { const size_t BOB_COMMAND_BUFFER_SIZE = 1024; + const int BOB_PING_TIMEOUT = 8000; // in milliseconds + const char BOB_COMMAND_ZAP[] = "zap"; const char BOB_COMMAND_QUIT[] = "quit"; const char BOB_COMMAND_START[] = "start"; @@ -43,12 +50,15 @@ namespace client const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; const char BOB_COMMAND_LOOKUP_LOCAL[] = "lookuplocal"; + const char BOB_COMMAND_PING[] = "ping"; const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; const char BOB_COMMAND_STATUS[] = "status"; const char BOB_COMMAND_HELP[] = "help"; + const char BOB_COMMAND_SETTUNNELTYPE[] = "settunneltype"; + const char BOB_HELP_ZAP[] = "zap - Shuts down BOB."; const char BOB_HELP_QUIT[] = "quit - Quits this session with BOB."; const char BOB_HELP_START[] = "start - Starts the current nicknamed tunnel."; @@ -70,7 +80,25 @@ namespace client const char BOB_HELP_OPTION[] = "option = - Set an option. NOTE: Don't use any spaces."; const char BOB_HELP_STATUS[] = "status - Display status of a nicknamed tunnel."; const char BOB_HELP_HELP [] = "help - Get help on a command."; + const char BOB_HELP_SETTUNNELTYPE[] = "settunneltype - Sets socks or http proxy tunnel type."; + 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 +152,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 +177,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 +203,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; }; @@ -210,11 +238,13 @@ namespace client void QuietCommandHandler (const char * operand, size_t len); void LookupCommandHandler (const char * operand, size_t len); void LookupLocalCommandHandler (const char * operand, size_t len); + void PingCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); void StatusCommandHandler (const char * operand, size_t len); void HelpCommandHandler (const char * operand, size_t len); + void SetTunnelTypeCommandHandler (const char * operand, size_t len); private: @@ -224,12 +254,15 @@ namespace client void Send (); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void SendReplyOK (const char * msg = nullptr); - void SendReplyError (const char * msg); - void SendRaw (const char * data); + void SendReplyOK (std::string_view msg); + void SendReplyOK (const std::vector& strings); + void SendReplyError (std::string_view msg); + void SendRaw (std::string_view data); - void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); + void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); + void SendPing (std::shared_ptr ls); + private: BOBCommandChannel& m_Owner; @@ -237,10 +270,17 @@ 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; + + enum class TunnelType + { + SOCKS = 0, + HTTP_PROXY = 1 + }; + std::optional m_tunnelType; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); @@ -248,16 +288,19 @@ 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); + void SetProxy (const std::string& name, std::unique_ptr proxy); + const I2PService* GetProxy(const std::string& name) const; + void RemoveProxy(const std::string& name); private: @@ -267,9 +310,10 @@ 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; + std::map> m_proxy; public: diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 3881f2e1..b890fbc6 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,11 +265,15 @@ namespace client } } - bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { - static const std::string transient("transient"); +#if __cplusplus >= 202002L // C++20 + if (filename.starts_with ("transient")) +#else + std::string_view transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient +#endif { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); @@ -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,7 +301,7 @@ namespace client } else { - LogPrint (eLogError, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); + LogPrint (eLogInfo, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); @@ -335,7 +349,7 @@ namespace client } 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) { @@ -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,19 @@ 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_DONT_SIGN] = GetI2CPOption(section, I2CP_PARAM_STREAMING_DONT_SIGN, DEFAULT_DONT_SIGN); + options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); + options[I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE, i2p::stream::MAX_WINDOW_SIZE); 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"); +#if OPENSSL_PQ + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "6,4" : "6,4,0"); +#else + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "4,0"); +#endif 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 +528,10 @@ 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; + if (i2p::config::GetOption(prefix + I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE, value)) + options[I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE] = value; } void ClientContext::ReadTunnels () @@ -530,7 +555,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 +597,30 @@ 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); +#if !OPENSSL_PQ + if (sigType >= i2p::data::SIGNING_KEY_TYPE_MLDSA44) sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; +#endif 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 ()) @@ -605,15 +644,18 @@ namespace client } } - if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { + 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; bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); - auto clientTunnel = std::make_shared (name, dest, end, localDestination, destinationPort, gzip); + int datagramVersion = (i2p::datagram::DatagramVersion)section.second.get (UDP_CLIENT_TUNNEL_DATAGRAM_VERSION, (int)i2p::datagram::eDatagramV1); + auto clientTunnel = std::make_shared (name, dest, end, + localDestination, destinationPort, gzip, (i2p::datagram::DatagramVersion)datagramVersion); auto ins = m_ClientForwards.insert (std::make_pair (end, clientTunnel)); if (ins.second) @@ -635,7 +677,9 @@ namespace client LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); } - } else { + } + else + { boost::asio::ip::tcp::endpoint clientEndpoint; std::shared_ptr clientTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_SOCKS) @@ -651,7 +695,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,53 +757,66 @@ 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); +#if !OPENSSL_PQ + if (sigType >= i2p::data::SIGNING_KEY_TYPE_MLDSA44) sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; +#endif 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 ssl = section.second.get(I2P_SERVER_TUNNEL_SSL, false); + bool isUniqueLocal = section.second.get (I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, (host == "::1") ? false : 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 ()) @@ -765,8 +824,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"); @@ -846,11 +905,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 ()); } } @@ -867,31 +926,42 @@ namespace client uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + bool httpSendUserAgent; i2p::config::GetOption("httpproxy.senduseragent", httpSendUserAgent); if (httpAddresshelper) i2p::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); +#if !OPENSSL_PQ + if (sigType >= i2p::data::SIGNING_KEY_TYPE_MLDSA44) sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; +#endif 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 ()); } } @@ -912,8 +982,16 @@ namespace client std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); +#if !OPENSSL_PQ + if (sigType >= i2p::data::SIGNING_KEY_TYPE_MLDSA44) sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; +#endif 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 (); @@ -925,11 +1003,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 { @@ -939,7 +1018,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 4e969a6b..52790836 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.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,11 +12,10 @@ #include #include #include +#include #include #include "Destination.h" #include "I2PService.h" -#include "HTTPProxy.h" -#include "SOCKS.h" #include "I2PTunnel.h" #include "UDPTunnel.h" #include "SAM.h" @@ -63,7 +62,8 @@ namespace client const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; const char I2P_SERVER_TUNNEL_SSL[] = "ssl"; - + const char UDP_CLIENT_TUNNEL_DATAGRAM_VERSION[] = "datagramversion"; + class ClientContext { public: @@ -81,20 +81,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); @@ -141,8 +141,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 @@ -167,8 +166,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 3e5ab595..4c2771b5 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -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 { @@ -58,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 */ @@ -75,29 +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 SendRedirect(std::string& address); + 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(); @@ -108,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; @@ -126,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 */ }; @@ -161,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" @@ -190,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; @@ -205,23 +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)); } - void HTTPReqHandler::SendRedirect(std::string& address) + void HTTPReqHandler::SendRedirect(const std::string& address) { i2p::http::HTTPRes res; res.code = 302; res.add_header("Location", address); res.add_header("Connection", "close"); - 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) + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm) { confirm = false; const char *param = "i2paddresshelper="; @@ -237,8 +238,13 @@ 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") { @@ -269,7 +275,35 @@ namespace proxy { 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"); @@ -281,7 +315,8 @@ namespace proxy { req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ - req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + if (!m_SendUserAgent) + req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /** * i2pd PR #1816: @@ -342,7 +377,7 @@ namespace proxy { std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { - if (!m_Addresshelper) + if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); @@ -410,7 +445,7 @@ namespace proxy { bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { - std::string uri(m_ClientRequest.uri); + const std::string& uri = m_ClientRequest.uri; auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { @@ -437,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); @@ -477,7 +512,7 @@ namespace proxy { GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); - std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str()); + std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ()); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; @@ -517,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); @@ -548,20 +583,22 @@ 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 { @@ -570,57 +607,44 @@ namespace proxy { } } - 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); + else handler(*endpoints.begin ()); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { - if(!ec) { - if(m_RequestURL.host.size() > 255) { + 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); @@ -628,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 @@ -680,24 +703,6 @@ namespace proxy { } } - 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) { @@ -748,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..2c2b6801 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,41 +24,76 @@ 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_Owner (owner), m_Identity (identity), 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) + void I2CPDestination::SetEncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) { - m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); - } - - void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) - { - if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? + bool exist = false; + auto it = m_EncryptionKeys.find (keyType); + if (it != m_EncryptionKeys.end () && !memcmp (it->second->priv.data (), key, it->second->priv.size ())) // new key? + exist = true; + if (!exist) { - m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public - memcpy (m_ECIESx25519PrivateKey, key, 32); + auto encryptionKey = std::make_shared (keyType); + memcpy (encryptionKey->priv.data (), key, encryptionKey->priv.size ()); + encryptionKey->CreateDecryptor (); // from private key + m_EncryptionKeys.insert_or_assign (keyType, encryptionKey); + } + } + + void I2CPDestination::SetECIESx25519EncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) + { + bool exist = false; + auto it = m_EncryptionKeys.find (keyType); + if (it != m_EncryptionKeys.end () && !memcmp (it->second->priv.data (), key, it->second->priv.size ())) // new key? + exist = true; + if (!exist) + { + auto encryptionKey = std::make_shared (keyType); + memcpy (encryptionKey->priv.data (), key, encryptionKey->priv.size ()); + // create decryptor manually + auto decryptor = std::make_shared(key, true); // calculate publicKey + encryptionKey->decryptor = decryptor; + // assign public key from decryptor + memcpy (encryptionKey->pub.data (), decryptor->GetPubicKey (), encryptionKey->pub.size ()); + // insert or replace new + m_EncryptionKeys.insert_or_assign (keyType, encryptionKey); } } bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->Decrypt (encrypted, data); - if (m_Decryptor) - return m_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, "I2CP: Decryptor is not set"); return false; @@ -65,18 +101,35 @@ namespace client const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->GetPubicKey (); + auto it = m_EncryptionKeys.find (keyType); + if (it != m_EncryptionKeys.end ()) + return it->second->pub.data (); return nullptr; } bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; +#if __cplusplus >= 202002L // C++20 + return m_EncryptionKeys.contains (keyType); +#else + return m_EncryptionKeys.count (keyType) > 0; +#endif } + i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const + { + return !m_EncryptionKeys.empty () ? m_EncryptionKeys.rbegin ()->first : 0; + } - void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) + i2p::data::CryptoKeyType I2CPDestination::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; + } + + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len, + i2p::garlic::ECIESX25519AEADRatchetSession * from) { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; @@ -86,7 +139,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 +149,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 +189,8 @@ namespace client }); } } + else + LogPrint (eLogError, "I2CP: Can't request LeaseSet"); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -152,20 +221,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 +270,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 +284,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 +330,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 +407,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 +419,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 +470,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 +576,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 +620,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 +652,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,23 +660,20 @@ namespace client break; } offset++; - mapping.insert (std::make_pair (param, value)); + mapping.emplace (param, value); } } void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { - // get version - auto version = ExtractString (buf, len); - auto l = version.length () + 1 + 8; - uint8_t * payload = new uint8_t[l]; + constexpr std::string_view version(I2P_VERSION); + std::array payload; // set date auto ts = i2p::util::GetMillisecondsSinceEpoch (); - htobe64buf (payload, ts); - // echo vesrion back - PutString (payload + 8, l - 8, version); - SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); - delete[] payload; + htobe64buf (payload.data(), ts); + // send our version back + PutString (payload.data() + 8, payload.size() - 8, version); + SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload.data(), payload.size()); } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) @@ -558,13 +712,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 +738,7 @@ namespace client SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid } } - + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy @@ -669,6 +823,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); @@ -681,7 +855,7 @@ namespace client // we always assume this field as 20 bytes (DSA) regardless actual size // instead of //offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); - m_Destination->SetEncryptionPrivateKey (buf + offset); + m_Destination->SetEncryptionPrivateKey (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); } @@ -714,13 +888,10 @@ namespace client uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); + if (keyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + m_Destination->SetECIESx25519EncryptionPrivateKey (keyType, buf + offset); else - { - m_Destination->SetEncryptionType (keyType); - m_Destination->SetEncryptionPrivateKey (buf + offset); - } + m_Destination->SetEncryptionPrivateKey (keyType, buf + offset); // from identity offset += keyLen; } @@ -739,19 +910,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 +1087,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 +1116,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 +1136,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 +1170,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..102fc554 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,50 +85,55 @@ 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; }; - void SetECIESx25519EncryptionPrivateKey (const uint8_t * key); + void SetEncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key); + void SetECIESx25519EncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key); 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; + // 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, i2p::garlic::ECIESX25519AEADRatchetSession * from) 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: std::shared_ptr m_Owner; std::shared_ptr m_Identity; - i2p::data::CryptoKeyType m_EncryptionKeyType; - std::shared_ptr m_Decryptor; // standard - std::shared_ptr m_ECIESx25519Decryptor; - uint8_t m_ECIESx25519PrivateKey[32]; + std::map > m_EncryptionKeys; // last is most preferable 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 +167,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 +195,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 +209,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 +226,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 +240,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 5558cd8d..3fea7172 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 * @@ -31,9 +31,9 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, int port): + std::shared_ptr leaseSet, uint16_t port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), - m_IsQuiet (true) + m_IsReceiving (false) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -41,14 +41,13 @@ namespace client I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), - m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) + m_RemoteEndpoint (socket->remote_endpoint ()), m_IsReceiving (false) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, bool quiet, - std::shared_ptr sslCtx): - I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) + const boost::asio::ip::tcp::endpoint& target,std::shared_ptr sslCtx): + I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsReceiving (false) { m_Socket = std::make_shared (owner->GetService ()); if (sslCtx) @@ -82,14 +81,26 @@ namespace client return ourIP; } + boost::asio::ip::address GetLoopbackAddress6For(const i2p::data::IdentHash & addr) + { + boost::asio::ip::address_v6::bytes_type bytes; + const uint8_t * ident = addr; + bytes[0] = 0xfd; + memcpy (bytes.data ()+1, ident, 15); + boost::asio::ip::address ourIP = boost::asio::ip::address_v6 (bytes); + return ourIP; + } + #ifdef __linux__ - static void MapToLoopback(std::shared_ptr sock, const i2p::data::IdentHash & addr) + static void MapToLoopback(std::shared_ptr sock, const i2p::data::IdentHash & addr, bool isV4) { if (sock) { - // bind to 127.x.x.x address + // bind to 127.x.x.x address for ipv4 // where x.x.x are first three bytes from ident - auto ourIP = GetLoopbackAddressFor(addr); + // bind to fdxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx address for ipv6 + // where xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx are first 15 bytes from ident + auto ourIP = isV4 ? GetLoopbackAddressFor(addr) : GetLoopbackAddress6For(addr); boost::system::error_code ec; sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); if (ec) @@ -104,12 +115,19 @@ namespace client { I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ - if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && - m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) - { - m_Socket->open (boost::asio::ip::tcp::v4 ()); + if (isUniqueLocal && m_RemoteEndpoint.address ().is_loopback ()) + { auto ident = m_Stream->GetRemoteIdentity()->GetIdentHash(); - MapToLoopback(m_Socket, ident); + if (m_RemoteEndpoint.address ().is_v4 ()) + { + m_Socket->open (boost::asio::ip::tcp::v4 ()); + MapToLoopback(m_Socket, ident, true); + } + else if (m_RemoteEndpoint.address ().is_v6 ()) + { + m_Socket->open (boost::asio::ip::tcp::v6 ()); + MapToLoopback(m_Socket, ident, false); + } } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, @@ -151,18 +169,29 @@ namespace client void I2PTunnelConnection::Receive () { + if (m_IsReceiving) return; // already receiving + size_t bufSize = I2P_TUNNEL_CONNECTION_BUFFER_SIZE; + size_t unsentSize = m_Stream ? m_Stream->GetSendBufferSize () : 0; + if (unsentSize) + { + if (unsentSize >= I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE) return; // buffer is full + if (unsentSize > I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE - I2P_TUNNEL_CONNECTION_BUFFER_SIZE) + bufSize = I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE - unsentSize; + } + m_IsReceiving = true; if (m_SSL) - m_SSL->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + m_SSL->async_read_some (boost::asio::buffer(m_Buffer, bufSize), 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), + m_Socket->async_read_some (boost::asio::buffer(m_Buffer, bufSize), std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + m_IsReceiving = false; if (ecode) { if (ecode != boost::asio::error::operation_aborted) @@ -172,16 +201,41 @@ namespace client } } else + { + if (bytes_transferred < I2P_TUNNEL_CONNECTION_BUFFER_SIZE && !m_SSL) + { + boost::system::error_code ec; + size_t moreBytes = m_Socket->available(ec); + if (!ec && moreBytes && m_Stream) + { + // read more data from socket before sending to stream + if (bytes_transferred + moreBytes > I2P_TUNNEL_CONNECTION_BUFFER_SIZE) + moreBytes = I2P_TUNNEL_CONNECTION_BUFFER_SIZE - bytes_transferred; + if (m_Stream->GetSendBufferSize () < I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE) + { + size_t remaining = I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE - m_Stream->GetSendBufferSize (); + if (remaining < moreBytes) moreBytes = remaining; + } + else + moreBytes = 0; + } + if (moreBytes) + { + moreBytes = boost::asio::read (*m_Socket, boost::asio::buffer(m_Buffer + bytes_transferred, moreBytes), boost::asio::transfer_all (), ec); + if (!ec) bytes_transferred += moreBytes; + } + } WriteToStream (m_Buffer, bytes_transferred); + Receive (); // try to receive more while being sent to stream + } } void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) { if (m_Stream) { - auto s = shared_from_this (); m_Stream->AsyncSend (buf, len, - [s](const boost::system::error_code& ecode) + [s = shared_from_this ()](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); @@ -210,7 +264,7 @@ namespace client if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); @@ -218,7 +272,7 @@ namespace client else // closed by peer { // get remaining data - auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); else // no more data @@ -292,18 +346,7 @@ namespace client void I2PTunnelConnection::Established () { - if (m_IsQuiet) - StreamReceive (); - else - { - // send destination first like received from I2P - std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); - dest += "\n"; - if(sizeof(m_StreamBuffer) >= dest.size()) { - memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); - } - HandleStreamReceive (boost::system::error_code (), dest.size ()); - } + StreamReceive (); Receive (); } @@ -346,7 +389,12 @@ namespace client } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -370,10 +418,10 @@ namespace client } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - 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): - I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host), - m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) + 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 ()); @@ -392,13 +440,14 @@ 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 constexpr std::array excluded // list of excluded headers { "Keep-Alive:", "X-I2P" }; @@ -409,7 +458,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:")) @@ -428,7 +477,12 @@ namespace client } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -437,17 +491,12 @@ 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 ()); } @@ -475,12 +524,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 constexpr std::array excluded // list of excluded headers { "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" }; @@ -496,7 +546,12 @@ namespace client } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -516,7 +571,7 @@ namespace client I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass, std::shared_ptr sslCtx): - I2PTunnelConnection (owner, stream, target, true, sslCtx), m_From (stream->GetRemoteIdentity ()), + I2PTunnelConnection (owner, stream, target, sslCtx), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } @@ -564,7 +619,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(); @@ -572,7 +627,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; }; @@ -613,7 +668,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) { @@ -688,29 +743,24 @@ 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); - Accept (); - } else - { - auto resolver = std::make_shared(GetService ()); - resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), - std::bind (&I2PServerTunnel::HandleResolve, this, - std::placeholders::_1, std::placeholders::_2, resolver)); - } + Resolve (nullptr); + Accept (); } void I2PServerTunnel::Stop () @@ -720,23 +770,35 @@ namespace client auto localDestination = GetLocalDestination (); if (localDestination) localDestination->StopAcceptingStreams (); - + if (m_Resolver) + m_Resolver->cancel (); + ClearHandlers (); } - void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - std::shared_ptr resolver) + bool I2PServerTunnel::Resolve (std::shared_ptr stream) { + if (m_Resolver) return false; // already resolving + m_Resolver = std::make_shared(GetService ()); + m_Resolver->async_resolve (m_Address, "", + std::bind (&I2PServerTunnel::HandleResolve, this, + std::placeholders::_1, std::placeholders::_2, stream)); + return true; + } + + void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, + std::shared_ptr stream) + { + m_Resolver = nullptr; if (!ecode) { bool found = false; 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 ()) @@ -755,27 +817,27 @@ 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 (); + if (stream) + Connect (stream); } 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) @@ -787,7 +849,7 @@ 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 @@ -833,25 +895,37 @@ namespace client return; } } - // new connection - auto conn = CreateI2PConnection (stream); - AddHandler (conn); - if (m_LocalAddress) - conn->Connect (*m_LocalAddress); - else - conn->Connect (m_IsUniqueLocal); + if (!m_Endpoint.address ().is_unspecified ()) + Connect (stream); + else if (!Resolve (stream)) + { + LogPrint (eLogWarning, "I2PTunnel: Address ", m_Address, " can't be resolved. Incoming connection dropped"); + stream->Close (); + return; + } } } + void I2PServerTunnel::Connect (std::shared_ptr stream) + { + // new connection + auto conn = CreateI2PConnection (stream); + AddHandler (conn); + if (m_LocalAddress) + conn->Connect (*m_LocalAddress); + else + conn->Connect (m_IsUniqueLocal); + } + std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, GetEndpoint (), true, m_SSLCtx); + return std::make_shared (this, stream, GetEndpoint (), m_SSLCtx); } 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) { @@ -859,12 +933,22 @@ namespace client std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, GetEndpoint (), m_Host, GetSSLCtx ()); + 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) { diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 4c7b2002..00b95726 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 * @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -27,25 +28,27 @@ namespace i2p { namespace client { - const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; - const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds - const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds + constexpr size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192; + constexpr size_t I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE = 8*I2P_TUNNEL_CONNECTION_BUFFER_SIZE; + constexpr size_t I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE = 16384; + constexpr int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds + constexpr 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 - const int I2P_TUNNEL_HTTP_MAX_HEADER_SIZE = 8192; + constexpr std::string_view X_I2P_DEST_HASH { "X-I2P-DestHash" }; // hash in base64 + constexpr std::string_view X_I2P_DEST_B64 { "X-I2P-DestB64" }; // full address in base64 + constexpr std::string_view X_I2P_DEST_B32 { "X-I2P-DestB32" }; // .b32.i2p address + constexpr 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, - const boost::asio::ip::tcp::endpoint& target, bool quiet = true, + const boost::asio::ip::tcp::endpoint& target, std::shared_ptr sslCtx = nullptr); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); @@ -54,33 +57,35 @@ namespace client protected: + virtual void Established (); void Terminate (); void Receive (); void StreamReceive (); + void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded std::shared_ptr GetSocket () const { return m_Socket; }; + std::shared_ptr GetStream () const { return m_Stream; }; std::shared_ptr > GetSSL () const { return m_SSL; }; - + uint8_t * GetStreamBuffer () { return m_StreamBuffer; }; + private: void HandleConnect (const boost::system::error_code& ecode); void HandleHandshake (const boost::system::error_code& ecode); - void Established (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleWrite (const boost::system::error_code& ecode); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - + private: - uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; + uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_STREAM_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 + bool m_IsReceiving; }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection @@ -94,7 +99,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: @@ -107,20 +112,19 @@ namespace client public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - 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 @@ -133,7 +137,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len); + void Write (const uint8_t * buf, size_t len) override; private: @@ -154,7 +158,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 (); @@ -174,7 +178,7 @@ 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; }; @@ -183,8 +187,8 @@ namespace client { 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 (); @@ -200,7 +204,7 @@ namespace client 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; } @@ -208,33 +212,36 @@ namespace client private: - void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - std::shared_ptr resolver); + bool Resolve (std::shared_ptr stream); + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, + std::shared_ptr stream); void Accept (); void HandleAccept (std::shared_ptr stream); + void Connect (std::shared_ptr stream); virtual std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: 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; + std::shared_ptr m_Resolver; }; 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: @@ -242,16 +249,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: diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index 1e2e8275..40752f5b 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -73,7 +73,8 @@ namespace client { auto pool = GetTunnelPool(); if(!pool || !pool->StandardSelectPeers(path, hops, inbound, - std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, std::placeholders::_2))) + 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 93df59fb..80987c78 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #ifdef _MSC_VER #include #endif +#include #include "Base.h" #include "Identity.h" #include "Log.h" @@ -25,9 +26,9 @@ namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), - m_BufferOffset (0), - m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), - m_IsAccepting (false), m_Stream (nullptr) + m_BufferOffset (0), m_SocketType (SAMSocketType::eSAMSocketTypeUnknown), + m_IsSilent (false), m_IsAccepting (false), m_IsReceiving (false), + m_Version (MIN_SAM_VERSION) { } @@ -43,29 +44,27 @@ namespace client m_Stream->AsyncClose (); m_Stream = nullptr; } - auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { - case eSAMSocketTypeSession: + case SAMSocketType::eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; - case eSAMSocketTypeStream: + case SAMSocketType::eSAMSocketTypeStream: + break; + case SAMSocketType::eSAMSocketTypeAcceptor: + case SAMSocketType::eSAMSocketTypeForward: { - 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; } default: ; } - m_SocketType = eSAMSocketTypeTerminated; + m_SocketType = SAMSocketType::eSAMSocketTypeTerminated; if (m_Socket.is_open ()) { boost::system::error_code ec; @@ -82,21 +81,26 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } - static bool SAMVersionAcceptable(const std::string & ver) + static int ExtractVersion (std::string_view ver) { - return ver == "3.0" || ver == "3.1"; - } + int version = 0; + for (auto ch: ver) + { + if (ch >= '0' && ch <= '9') + { + version *= 10; + version += (ch - '0'); + } + } + return version; + } - static bool SAMVersionTooLow(const std::string & ver) + static std::string CreateVersion (int ver) { - return ver.size() && ver[0] < '3'; - } - - static bool SAMVersionTooHigh(const std::string & ver) - { - return ver.size() && ver > "3.1"; - } - + auto d = div (ver, 10); + return std::to_string (d.quot) + "." + std::to_string (d.rem); + } + void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -122,49 +126,41 @@ namespace client if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { - std::string maxver("3.1"); - std::string minver("3.0"); + int minVer = 0, maxVer = 0; // try to find MIN and MAX, 3.0 if not found if (separator) { separator++; - std::map params; - ExtractParams (separator, params); + auto params = ExtractParams (separator); auto it = params.find (SAM_PARAM_MAX); if (it != params.end ()) - maxver = it->second; + maxVer = ExtractVersion (it->second); it = params.find(SAM_PARAM_MIN); if (it != params.end ()) - minver = it->second; + minVer = ExtractVersion (it->second); } // version negotiation - std::string version; - if (SAMVersionAcceptable(maxver)) - { - version = maxver; - } - else if (SAMVersionAcceptable(minver)) - { - version = minver; - } - else if (SAMVersionTooLow(minver) && SAMVersionTooHigh(maxver)) - { - version = "3.0"; - } - - if (SAMVersionAcceptable(version)) - { -#ifdef _MSC_VER - size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); -#else - size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); -#endif - boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), - std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } + if (maxVer && maxVer <= MAX_SAM_VERSION) + m_Version = maxVer; + else if (minVer && minVer >= MIN_SAM_VERSION && minVer <= MAX_SAM_VERSION) + m_Version = minVer; + else if (!maxVer && !minVer) + m_Version = MIN_SAM_VERSION; else - SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true); + { + LogPrint (eLogError, "SAM: Handshake version mismatch ", minVer, " ", maxVer); + SendMessageReply (SAM_HANDSHAKE_NOVERSION, true); + return; + } + // send reply +#ifdef _MSC_VER + size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, CreateVersion (m_Version).c_str ()); +#else + size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, CreateVersion (m_Version).c_str ()); +#endif + boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), + std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } else { @@ -174,7 +170,7 @@ namespace client } } - bool SAMSocket::IsSession(const std::string & id) const + bool SAMSocket::IsSession(std::string_view id) const { return id == m_ID; } @@ -195,12 +191,12 @@ namespace client } } - void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) + void SAMSocket::SendMessageReply (std::string_view msg, bool close) { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) - boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), + if (!m_IsSilent || m_SocketType == SAMSocketType::eSAMSocketTypeForward) + boost::asio::async_write (m_Socket, boost::asio::buffer (msg.data (), msg.size ()), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else @@ -237,7 +233,7 @@ namespace client if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } - else if (m_SocketType == eSAMSocketTypeStream) + else if (m_SocketType == SAMSocketType::eSAMSocketTypeStream) HandleReceived (ecode, bytes_transferred); else { @@ -252,28 +248,40 @@ namespace client char * separator = strchr (m_Buffer, ' '); if (separator) { + // one word command + if (std::string_view (m_Buffer, separator - m_Buffer) == SAM_PING) + { + ProcessPing ({ separator + 1, (size_t)(eol - separator - 1) }); + return; + } + + // two words command + size_t l = 0; separator = strchr (separator + 1, ' '); if (separator) + { *separator = 0; + l = eol - separator - 1; + } else separator = eol; if (!strcmp (m_Buffer, SAM_SESSION_CREATE)) - ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessSessionCreate ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT)) ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) - ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessStreamAccept ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_STREAM_FORWARD)) - ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessStreamForward ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) - ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessDestGenerate ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) - ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessNamingLookup ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_SESSION_ADD)) - ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessSessionAdd ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE)) - ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + ProcessSessionRemove ({ separator + 1, l }); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; @@ -305,7 +313,6 @@ namespace client Terminate ("malformed message"); } } - else { LogPrint (eLogWarning, "SAM: Incomplete message ", bytes_transferred); @@ -316,7 +323,7 @@ namespace client } } - static bool IsAcceptableSessionName(const std::string & str) + static bool IsAcceptableSessionName(std::string_view str) { auto itr = str.begin(); while(itr != str.end()) @@ -329,60 +336,88 @@ namespace client return true; } - void SAMSocket::ProcessSessionCreate (char * buf, size_t len) + void SAMSocket::ProcessSessionCreate (std::string_view buf) { LogPrint (eLogDebug, "SAM: Session create: ", buf); - std::map params; - ExtractParams (buf, params); - std::string& style = params[SAM_PARAM_STYLE]; - std::string& id = params[SAM_PARAM_ID]; - std::string& destination = params[SAM_PARAM_DESTINATION]; + auto params = ExtractParams (buf); + std::string_view style = params[SAM_PARAM_STYLE]; + std::string_view id = params[SAM_PARAM_ID]; + std::string_view destination = params[SAM_PARAM_DESTINATION]; if(!IsAcceptableSessionName(id)) { // invalid session id - SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, strlen(SAM_SESSION_CREATE_INVALID_ID), true); + SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, true); return; } m_ID = id; if (m_Owner.FindSession (id)) { // session exists - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, true); return; } - SAMSessionType type = eSAMSessionTypeUnknown; - if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; - else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; - else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; - else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster; - if (type == eSAMSessionTypeUnknown) + SAMSessionType type = SAMSessionType::eSAMSessionTypeUnknown; + i2p::datagram::DatagramVersion datagramVersion = i2p::datagram::eDatagramV1; + if (style == SAM_VALUE_STREAM) type = SAMSessionType::eSAMSessionTypeStream; +#if __cplusplus >= 202002L // C++20 + else if (style.starts_with (SAM_VALUE_DATAGRAM)) +#else + else if (style.substr (0, SAM_VALUE_DATAGRAM.size ()) == SAM_VALUE_DATAGRAM) +#endif + { + // DATAGRAM, DATAGRAM1, DATAGRAM2, DATAGRAM3 + type = SAMSessionType::eSAMSessionTypeDatagram; + if (style.size () > SAM_VALUE_DATAGRAM.size ()) + { + switch (style[SAM_VALUE_DATAGRAM.size ()]) + { + case '1': datagramVersion = i2p::datagram::eDatagramV1; break; + case '2': datagramVersion = i2p::datagram::eDatagramV2; break; + case '3': datagramVersion = i2p::datagram::eDatagramV3; break; + default: type = SAMSessionType::eSAMSessionTypeUnknown; + } + } + } + else if (style == SAM_VALUE_RAW) type = SAMSessionType::eSAMSessionTypeRaw; + else if (style == SAM_VALUE_MASTER) + { + if (m_Version < SAM_VERSION_33) // < SAM 3.3 + { + SendSessionI2PError("MASTER session is not supported"); + return; + } + type = SAMSessionType::eSAMSessionTypeMaster; + } + if (type == SAMSessionType::eSAMSessionTypeUnknown) { // unknown style - SendI2PError("Unknown STYLE"); + SendSessionI2PError("Unknown STYLE"); return; } std::shared_ptr forward = nullptr; - if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && + if ((type == SAMSessionType::eSAMSessionTypeDatagram || type == SAMSessionType::eSAMSessionTypeRaw) && params.find(SAM_PARAM_HOST) != params.end() && params.find(SAM_PARAM_PORT) != params.end()) { // 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) + uint16_t port = 0; + std::string_view p = params[SAM_PARAM_PORT]; + auto res = std::from_chars(p.data(), p.data() + p.size(), port); + if (res.ec != std::errc()) { - SendI2PError("Invalid port"); + SendSessionI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); @@ -391,7 +426,7 @@ namespace client //ensure we actually received a destination if (destination.empty()) { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); + SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, true); return; } @@ -401,26 +436,37 @@ namespace client i2p::data::PrivateKeys keys; if (!keys.FromBase64(destination)) { - SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); + SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, true); return; } } // create destination - auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); + auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, params); if (session) { - m_SocketType = eSAMSocketTypeSession; - if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) + m_SocketType = SAMSocketType::eSAMSocketTypeSession; + if (type == SAMSessionType::eSAMSessionTypeDatagram || type == SAMSessionType::eSAMSessionTypeRaw) { session->UDPEndpoint = forward; - auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); - if (type == eSAMSessionTypeDatagram) + auto dest = session->GetLocalDestination ()->CreateDatagramDestination (true, datagramVersion); + uint16_t port = 0; + if (forward) + { + std::string_view p = params[SAM_PARAM_PORT]; + auto res = std::from_chars(p.data(), p.data() + p.size(), port); + if (res.ec != std::errc()) port = 0; + } + if (type == SAMSessionType::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 ()) @@ -433,7 +479,7 @@ namespace client } } else - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, true); } void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) @@ -465,33 +511,28 @@ 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); + SendMessageReply ({m_Buffer, l2}, false); } } void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { LogPrint (eLogDebug, "SAM: Stream connect: ", buf); - if ( m_SocketType != eSAMSocketTypeUnknown) + if ( m_SocketType != SAMSocketType::eSAMSocketTypeUnknown) { - SendI2PError ("Socket already in use"); + SendSessionI2PError ("Socket already in use"); return; } - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; - std::string& destination = params[SAM_PARAM_DESTINATION]; - std::string& silent = params[SAM_PARAM_SILENT]; + auto params = ExtractParams (buf); + std::string_view id = params[SAM_PARAM_ID]; + std::string_view destination = params[SAM_PARAM_DESTINATION]; + std::string_view silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); @@ -523,15 +564,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, @@ -539,10 +585,10 @@ namespace client shared_from_this(), std::placeholders::_1)); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, true); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); } void SAMSocket::Connect (std::shared_ptr remote, std::shared_ptr session) @@ -550,20 +596,25 @@ 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 = SAMSocketType::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, false); + } + else + SendMessageReply (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); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) @@ -573,92 +624,157 @@ 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"); } } - void SAMSocket::ProcessStreamAccept (char * buf, size_t len) + void SAMSocket::ProcessStreamAccept (std::string_view buf) { LogPrint (eLogDebug, "SAM: Stream accept: ", buf); - if ( m_SocketType != eSAMSocketTypeUnknown) + if ( m_SocketType != SAMSocketType::eSAMSocketTypeUnknown) { - SendI2PError ("Socket already in use"); + SendSessionI2PError ("Socket already in use"); return; } - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; - std::string& silent = params[SAM_PARAM_SILENT]; + auto params = ExtractParams (buf); + std::string_view id = params[SAM_PARAM_ID]; + std::string_view silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); if (session) { - m_SocketType = eSAMSocketTypeAcceptor; + m_SocketType = SAMSocketType::eSAMSocketTypeAcceptor; if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { m_IsAccepting = true; + SendMessageReply (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, 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); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); } - void SAMSocket::ProcessStreamForward (char * buf, size_t len) + void SAMSocket::ProcessStreamForward (std::string_view buf) { - LogPrint (eLogDebug, "SAM: Stream forward: ", buf); - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; - auto session = m_Owner.FindSession (id); + LogPrint(eLogDebug, "SAM: Stream forward: ", buf); + + auto params = ExtractParams(buf); + const auto itId = params.find(SAM_PARAM_ID); + if (itId == params.end()) + { + SendSessionI2PError("Missing ID"); + return; + } + std::string_view id = itId->second; + + auto session = m_Owner.FindSession(id); if (!session) { - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); + SendMessageReply(SAM_STREAM_STATUS_INVALID_ID, true); return; } - if (session->GetLocalDestination ()->IsAcceptingStreams ()) + if (session->GetLocalDestination()->IsAcceptingStreams()) { - SendI2PError ("Already accepting"); + SendSessionI2PError("Already accepting"); return; } - auto it = params.find (SAM_PARAM_PORT); - if (it == params.end ()) + + const auto itPort = params.find(SAM_PARAM_PORT); + if (itPort == params.end()) { - SendI2PError ("PORT is missing"); + SendSessionI2PError("PORT is missing"); return; } - auto port = std::stoi (it->second); - if (port <= 0 || port >= 0xFFFF) + + std::string_view portStr = itPort->second; + if (!std::all_of(portStr.begin(), portStr.end(), ::isdigit)) { - SendI2PError ("Invalid PORT"); + SendSessionI2PError("Port must be numeric"); return; } - boost::system::error_code ec; - auto ep = m_Socket.remote_endpoint (ec); - if (ec) + + uint16_t port = 0; + auto res = std::from_chars(portStr.data(), portStr.data() + portStr.size(), port); + if (res.ec != std::errc()) { - SendI2PError ("Socket error"); + SendSessionI2PError("Invalid port"); return; } - ep.port (port); - m_SocketType = eSAMSocketTypeForward; + + boost::asio::ip::tcp::endpoint ep; + const auto itHost = params.find(SAM_PARAM_HOST); + + if (itHost != params.end()) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::make_address(itHost->second, ec); + if (ec) + { + SendSessionI2PError("Invalid IP Address in HOST"); + return; + } + ep = boost::asio::ip::tcp::endpoint(addr, port); + } + else + { + boost::system::error_code ec; + ep = m_Socket.remote_endpoint(ec); + if (ec) + { + SendSessionI2PError("Socket error: cannot get remote endpoint"); + return; + } + ep.port(port); + } + + m_SocketType = SAMSocketType::eSAMSocketTypeForward; m_ID = id; m_IsAccepting = true; - std::string& silent = params[SAM_PARAM_SILENT]; - if (silent == SAM_VALUE_TRUE) m_IsSilent = true; - session->GetLocalDestination ()->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, - shared_from_this (), std::placeholders::_1, ep)); - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + + auto itSilent = params.find(SAM_PARAM_SILENT); + if (itSilent != params.end() && itSilent->second == SAM_VALUE_TRUE) + m_IsSilent = true; + + session->GetLocalDestination()->AcceptStreams( + std::bind(&SAMSocket::HandleI2PForward, shared_from_this(), std::placeholders::_1, ep)); + + SendMessageReply(SAM_STREAM_STATUS_OK, false); } + size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { LogPrint (eLogDebug, "SAM: Datagram send: ", buf, " ", len); - std::map params; - ExtractParams (buf, params); - size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; + auto params = ExtractParams (buf); + size_t size = 0; + std::string_view sizeStr = params[SAM_PARAM_SIZE]; + auto res = std::from_chars(sizeStr.data(), sizeStr.data() + sizeStr.size(), size); + if (res.ec != std::errc()) size = 0; + size_t offset = data - buf; if (offset + size <= len) { auto session = m_Owner.FindSession(m_ID); @@ -669,7 +785,7 @@ namespace client { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); - if (session->Type == eSAMSessionTypeDatagram) + if (session->Type == SAMSessionType::eSAMSessionTypeDatagram) d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); else // raw d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); @@ -688,11 +804,10 @@ namespace client return offset + size; } - void SAMSocket::ProcessDestGenerate (char * buf, size_t len) + void SAMSocket::ProcessDestGenerate (std::string_view buf) { LogPrint (eLogDebug, "SAM: Dest generate"); - std::map params; - ExtractParams (buf, params); + auto params = ExtractParams (buf); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; @@ -707,7 +822,7 @@ namespace client { try { - cryptoType = std::stoi(it->second); + cryptoType = std::stoi(std::string (it->second)); } catch (const std::exception& ex) { @@ -722,15 +837,14 @@ namespace client size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif - SendMessageReply (m_Buffer, l, false); + SendMessageReply ({m_Buffer, l}, false); } - void SAMSocket::ProcessNamingLookup (char * buf, size_t len) + void SAMSocket::ProcessNamingLookup (std::string_view buf) { LogPrint (eLogDebug, "SAM: Naming lookup: ", buf); - std::map params; - ExtractParams (buf, params); - std::string& name = params[SAM_PARAM_NAME]; + auto params = ExtractParams (buf); + std::string name (params[SAM_PARAM_NAME]); std::shared_ptr identity; std::shared_ptr addr; auto session = m_Owner.FindSession(m_ID); @@ -764,86 +878,122 @@ namespace client #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif - SendMessageReply (m_Buffer, len, false); + SendMessageReply ({m_Buffer, len}, false); } } - void SAMSocket::ProcessSessionAdd (char * buf, size_t len) + void SAMSocket::ProcessSessionAdd (std::string_view buf) { + if (m_Version < SAM_VERSION_33) // < SAM 3.3 + { + SendSessionI2PError("SESSION ADD is not supported"); + return; + } auto session = m_Owner.FindSession(m_ID); - if (session && session->Type == eSAMSessionTypeMaster) + if (session && session->Type == SAMSessionType::eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: Subsession add: ", buf); auto masterSession = std::static_pointer_cast(session); - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; + auto params = ExtractParams (buf); + std::string_view id = params[SAM_PARAM_ID]; if (masterSession->subsessions.count (id) > 1) { // session exists - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, false); return; } - std::string& style = params[SAM_PARAM_STYLE]; - SAMSessionType type = eSAMSessionTypeUnknown; - if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; + std::string_view style = params[SAM_PARAM_STYLE]; + SAMSessionType type = SAMSessionType::eSAMSessionTypeUnknown; + if (style == SAM_VALUE_STREAM) type = SAMSessionType::eSAMSessionTypeStream; // TODO: implement other styles - if (type == eSAMSessionTypeUnknown) + if (type == SAMSessionType::eSAMSessionTypeUnknown) { // unknown style - SendI2PError("Unsupported STYLE"); + SendSessionI2PError("Unsupported STYLE"); return; } - auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); - if (fromPort == -1) + uint16_t fromPort = 0; + auto it = params.find (SAM_PARAM_FROM_PORT); + if (it != params.end ()) { - SendI2PError("Invalid from port"); - return; + auto p = it->second; + auto res = std::from_chars(p.data(), p.data() + p.size(), fromPort); + if (res.ec != std::errc()) + { + SendSessionI2PError("Invalid from port"); + return; + } } auto subsession = std::make_shared(masterSession, id, type, fromPort); if (m_Owner.AddSession (subsession)) { - masterSession->subsessions.insert (id); + masterSession->subsessions.insert (std::string (id)); SendSessionCreateReplyOk (); } else - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, false); } else - SendI2PError ("Wrong session type"); + SendSessionI2PError ("Wrong session type"); } - void SAMSocket::ProcessSessionRemove (char * buf, size_t len) + void SAMSocket::ProcessSessionRemove (std::string_view buf) { + if (m_Version < SAM_VERSION_33) // < SAM 3.3 + { + SendSessionI2PError("SESSION REMOVE is not supported"); + return; + } auto session = m_Owner.FindSession(m_ID); - if (session && session->Type == eSAMSessionTypeMaster) + if (session && session->Type == SAMSessionType::eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: Subsession remove: ", buf); auto masterSession = std::static_pointer_cast(session); - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; + auto params = ExtractParams (buf); + std::string id(params[SAM_PARAM_ID]); if (!masterSession->subsessions.erase (id)) { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), false); + SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, false); return; } m_Owner.CloseSession (id); SendSessionCreateReplyOk (); } else - SendI2PError ("Wrong session type"); + SendSessionI2PError ("Wrong session type"); } - void SAMSocket::SendI2PError(const std::string & msg) + void SAMSocket::ProcessPing (std::string_view text) + { + LogPrint (eLogDebug, "SAM: Ping ", text); + SendReplyWithMessage (SAM_PONG, std::string (text)); + } + + 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); + 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) @@ -861,7 +1011,7 @@ namespace client #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif - SendMessageReply (m_Buffer, len, false); + SendMessageReply ({m_Buffer, len}, false); } } @@ -873,37 +1023,59 @@ namespace client #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #endif - SendMessageReply (m_Buffer, l, false); + SendMessageReply ({m_Buffer, l}, false); } - void SAMSocket::ExtractParams (char * buf, std::map& params) + const std::map SAMSocket::ExtractParams (std::string_view buf) { - char * separator; - do - { - separator = strchr (buf, ' '); - if (separator) *separator = 0; - char * value = strchr (buf, '='); - if (value) - { - *value = 0; - value++; - params[buf] = value; + std::map params; + size_t pos = 0; + while (pos < buf.length ()) + { + std::string_view field; + auto separator = buf.find (' ', pos); + if (separator != std::string_view::npos) + { + field = buf.substr (pos, separator - pos); + pos = separator + 1; } - buf = separator + 1; + else + { + field = buf.substr (pos); + pos = buf.length (); + } + auto value = field.find ('='); + if (value != std::string_view::npos) + params.emplace (field.substr (0, value), field.substr (value + 1)); } - while (separator); + return params; } void SAMSocket::Receive () { - m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), - std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, - shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + if (m_SocketType == SAMSocketType::eSAMSocketTypeStream) + { + if (m_IsReceiving) return; + size_t bufSize = SAM_SOCKET_BUFFER_SIZE; + size_t unsentSize = m_Stream ? m_Stream->GetSendBufferSize () : 0; + if (unsentSize) + { + if (unsentSize >= SAM_STREAM_MAX_SEND_BUFFER_SIZE) return; // buffer is full + if (unsentSize > SAM_STREAM_MAX_SEND_BUFFER_SIZE - SAM_SOCKET_BUFFER_SIZE) + bufSize = SAM_STREAM_MAX_SEND_BUFFER_SIZE - unsentSize; + } + m_IsReceiving = true; + m_Socket.async_read_some (boost::asio::buffer(m_Buffer, bufSize), + std::bind(&SAMSocket::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + else + m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), + std::bind(&SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + m_IsReceiving = false; if (ecode) { LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); @@ -913,16 +1085,13 @@ namespace client else { if (m_Stream) - { - bytes_transferred += m_BufferOffset; - m_BufferOffset = 0; + { m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, std::bind(&SAMSocket::HandleStreamSend, shared_from_this(), std::placeholders::_1)); - } + Receive (); + } else - { Terminate("No Stream Remaining"); - } } } @@ -933,16 +1102,16 @@ namespace client if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_STREAM_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), std::placeholders::_1, std::placeholders::_2), SAM_SOCKET_CONNECTION_MAX_IDLE); } else // closed by peer { - uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; + uint8_t * buff = new uint8_t[SAM_STREAM_BUFFER_SIZE]; // get remaining data - auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); + auto len = m_Stream->ReadSome (buff, SAM_STREAM_BUFFER_SIZE); if (len > 0) // still some data { WriteI2PDataImmediate(buff, len); @@ -993,18 +1162,18 @@ 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 { - if (m_SocketType != eSAMSocketTypeTerminated) + if (m_SocketType != SAMSocketType::eSAMSocketTypeTerminated) { if (bytes_transferred > 0) { @@ -1035,35 +1204,47 @@ namespace client if (stream) { LogPrint (eLogDebug, "SAM: Incoming I2P connection for session ", m_ID); - m_SocketType = eSAMSocketTypeStream; + m_SocketType = SAMSocketType::eSAMSocketTypeStream; m_IsAccepting = false; 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 () == SAMSocketType::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 != SAMSocketType::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 (); @@ -1079,7 +1260,7 @@ namespace client { LogPrint (eLogDebug, "SAM: Incoming forward I2P connection for session ", m_ID); auto newSocket = std::make_shared(m_Owner); - newSocket->SetSocketType (eSAMSocketTypeStream); + newSocket->SetSocketType (SAMSocketType::eSAMSocketTypeStream); auto s = shared_from_this (); newSocket->GetSocket ().async_connect (ep, [s, newSocket, stream](const boost::system::error_code& ecode) @@ -1127,11 +1308,11 @@ namespace client else { #ifdef _MSC_VER - size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #endif - if (len < SAM_SOCKET_BUFFER_SIZE - l) + if (len < SAM_STREAM_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); @@ -1155,11 +1336,11 @@ namespace client else { #ifdef _MSC_VER - size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); + size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); + size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #endif - if (len < SAM_SOCKET_BUFFER_SIZE - l) + if (len < SAM_STREAM_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); @@ -1172,10 +1353,10 @@ 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): + SAMSession::SAMSession (SAMBridge & parent, std::string_view id, SAMSessionType type): m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) { } @@ -1188,7 +1369,7 @@ namespace client } } - SAMSingleSession::SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest): + SAMSingleSession::SAMSingleSession (SAMBridge & parent, std::string_view name, SAMSessionType type, std::shared_ptr dest): SAMSession (parent, name, type), localDestination (dest) { @@ -1217,11 +1398,12 @@ namespace client subsessions.clear (); } - SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port): + SAMSubSession::SAMSubSession (std::shared_ptr master, std::string_view name, SAMSessionType type, uint16_t port): SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port) - { - if (Type == eSAMSessionTypeStream) + { + if (Type == SAMSessionType::eSAMSessionTypeStream && port) { + // additional streaming destination, use default if port is 0 auto d = masterSession->GetLocalDestination ()->CreateStreamingDestination (inPort); if (d) d->Start (); } @@ -1236,7 +1418,7 @@ namespace client void SAMSubSession::StopLocalDestination () { auto dest = GetLocalDestination (); - if (dest && Type == eSAMSessionTypeStream) + if (dest && Type == SAMSessionType::eSAMSessionTypeStream) { auto d = dest->RemoveStreamingDestination (inPort); if (d) d->Stop (); @@ -1244,10 +1426,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}, @@ -1286,12 +1468,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 (); } @@ -1336,37 +1520,44 @@ namespace client Accept (); } - std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, - const std::string& destination, const std::map * params) + std::shared_ptr SAMBridge::CreateSession (std::string_view id, SAMSessionType type, + std::string_view destination, const std::map& params) { +#if __GNUC__ < 10 // TODO: remove when older versions discontinued + std::map p; + for (auto it: params) + p.emplace (std::string (it.first), std::string (it.second)); +#else + std::map p(params.begin (), params.end ()); +#endif std::shared_ptr localDestination = nullptr; if (destination != "") { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; localDestination = m_IsSingleThread ? - i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : - i2p::client::context.CreateNewLocalDestination (keys, true, params); + i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, &p) : + i2p::client::context.CreateNewLocalDestination (keys, true, &p); } else // transient { // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; - if (params) + if (!params.empty ()) { - auto it = params->find (SAM_PARAM_SIGNATURE_TYPE); - if (it != params->end ()) + auto it = params.find (SAM_PARAM_SIGNATURE_TYPE); + if (it != params.end ()) { if (!ResolveSignatureType (it->second, signatureType)) LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } - it = params->find (SAM_PARAM_CRYPTO_TYPE); - if (it != params->end ()) + it = params.find (SAM_PARAM_CRYPTO_TYPE); + if (it != params.end ()) { try { - cryptoType = std::stoi(it->second); + cryptoType = std::stoi(std::string (it->second)); } catch (const std::exception& ex) { @@ -1375,16 +1566,16 @@ namespace client } } localDestination = m_IsSingleThread ? - i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : - i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); + i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, &p) : + i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, &p); } if (localDestination) { localDestination->Acquire (); - auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : + auto session = (type == SAMSessionType::eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); - auto ret = m_Sessions.insert (std::make_pair(id, session)); + auto ret = m_Sessions.emplace (id, session); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; @@ -1399,7 +1590,7 @@ namespace client return ret.second; } - void SAMBridge::CloseSession (const std::string& id) + void SAMBridge::CloseSession (std::string_view id) { std::shared_ptr session; { @@ -1416,18 +1607,43 @@ 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 } } - std::shared_ptr SAMBridge::FindSession (const std::string& id) const + 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 (); + if (streamingDest) + { + 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 (std::string_view id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); @@ -1436,7 +1652,7 @@ namespace client return nullptr; } - std::list > SAMBridge::ListSockets(const std::string & id) const + std::list > SAMBridge::ListSockets(std::string_view id) const { std::list > list; { @@ -1489,9 +1705,9 @@ namespace client { i2p::data::IdentityEx dest; dest.FromBase64 (destination); - if (session->Type == eSAMSessionTypeDatagram) + if (session->Type == SAMSessionType::eSAMSessionTypeDatagram) datagramDest->SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); - else if (session->Type == eSAMSessionTypeRaw) + else if (session->Type == SAMSessionType::eSAMSessionTypeRaw) datagramDest->SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); else LogPrint (eLogError, "SAM: Unexpected session type ", (int)session->Type, "for session ", sessionID); @@ -1516,25 +1732,23 @@ namespace client LogPrint (eLogError, "SAM: Datagram receive error: ", ecode.message ()); } - bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const + bool SAMBridge::ResolveSignatureType (std::string_view name, i2p::data::SigningKeyType& type) const { - try + auto res = std::from_chars(name.data(), name.data() + name.size(), type); + if (res.ec != std::errc()) { - type = std::stoi (name); - } - catch (const std::invalid_argument& ex) - { - // name is not numeric, resolving - auto it = m_SignatureTypes.find (name); - if (it != m_SignatureTypes.end ()) - type = it->second; + if (res.ec == std::errc::invalid_argument) + { + // name is not numeric, resolving + auto it = m_SignatureTypes.find (name); + if (it != m_SignatureTypes.end ()) + type = it->second; + else + return false; + } else return false; } - catch (const std::exception& ex) - { - return false; - } // name has been resolved return true; } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index b6ac23a6..fd73003f 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2025, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -29,27 +30,32 @@ namespace i2p namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; + const size_t SAM_STREAM_BUFFER_SIZE = 16384; + const size_t SAM_STREAM_MAX_SEND_BUFFER_SIZE = 8*SAM_SOCKET_BUFFER_SIZE; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // 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"; + constexpr std::string_view SAM_HANDSHAKE_NOVERSION { "HELLO REPLY RESULT=NOVERSION\n" }; const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n"; const char SAM_SESSION_CREATE[] = "SESSION CREATE"; const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n"; - const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n"; - const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; - const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n"; - const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; + constexpr std::string_view SAM_SESSION_CREATE_DUPLICATED_ID { "SESSION STATUS RESULT=DUPLICATED_ID\n" }; + constexpr std::string_view SAM_SESSION_CREATE_DUPLICATED_DEST { "SESSION STATUS RESULT=DUPLICATED_DEST\n" }; + constexpr std::string_view SAM_SESSION_CREATE_INVALID_ID { "SESSION STATUS RESULT=INVALID_ID\n" }; + constexpr std::string_view SAM_SESSION_STATUS_INVALID_KEY { "SESSION STATUS RESULT=INVALID_KEY\n" }; const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"%s\"\n"; const char SAM_SESSION_ADD[] = "SESSION ADD"; const char SAM_SESSION_REMOVE[] = "SESSION REMOVE"; const char SAM_STREAM_CONNECT[] = "STREAM CONNECT"; - const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; - const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; - 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"; + constexpr std::string_view SAM_STREAM_STATUS_OK { "STREAM STATUS RESULT=OK\n" }; + constexpr std::string_view SAM_STREAM_STATUS_INVALID_ID { "STREAM STATUS RESULT=INVALID_ID\n" }; + constexpr std::string_view 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 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"; @@ -77,14 +83,23 @@ namespace client const char SAM_PARAM_PORT[] = "PORT"; const char SAM_PARAM_FROM_PORT[] = "FROM_PORT"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; - const char SAM_VALUE_STREAM[] = "STREAM"; - const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; - const char SAM_VALUE_RAW[] = "RAW"; - const char SAM_VALUE_MASTER[] = "MASTER"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; - enum SAMSocketType + constexpr std::string_view SAM_VALUE_STREAM { "STREAM" }; + constexpr std::string_view SAM_VALUE_DATAGRAM { "DATAGRAM" }; + constexpr std::string_view SAM_VALUE_RAW { "RAW" }; + constexpr std::string_view SAM_VALUE_MASTER { "MASTER" }; + + constexpr std::string_view SAM_PING { "PING" }; + const char SAM_PONG[] = "PONG %s\n"; + + constexpr int MAKE_SAM_VERSION_NUMBER (int major, int minor) { return major*10 + minor; } + constexpr int MIN_SAM_VERSION = MAKE_SAM_VERSION_NUMBER (3, 0); + constexpr int MAX_SAM_VERSION = MAKE_SAM_VERSION_NUMBER (3, 3); + constexpr int SAM_VERSION_33 = MAKE_SAM_VERSION_NUMBER (3, 3); // SAM 3.3 + + enum class SAMSocketType { eSAMSocketTypeUnknown, eSAMSocketTypeSession, @@ -111,7 +126,7 @@ namespace client void Terminate (const char* reason); - bool IsSession(const std::string & id) const; + bool IsSession(std::string_view id) const; private: @@ -120,7 +135,7 @@ namespace client void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void SendMessageReply (const char * msg, size_t len, bool close); + void SendMessageReply (std::string_view msg, bool close); void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); @@ -133,17 +148,21 @@ namespace client void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void ProcessSessionCreate (char * buf, size_t len); + void ProcessSessionCreate (std::string_view buf); void ProcessStreamConnect (char * buf, size_t len, size_t rem); - void ProcessStreamAccept (char * buf, size_t len); - void ProcessStreamForward (char * buf, size_t len); - void ProcessDestGenerate (char * buf, size_t len); - void ProcessNamingLookup (char * buf, size_t len); - void ProcessSessionAdd (char * buf, size_t len); - void ProcessSessionRemove (char * buf, size_t len); - void SendI2PError(const std::string & msg); + void ProcessStreamAccept (std::string_view buf); + void ProcessStreamForward (std::string_view buf); + void ProcessDestGenerate (std::string_view buf); + void ProcessNamingLookup (std::string_view buf); + void ProcessSessionAdd (std::string_view buf); + void ProcessSessionRemove (std::string_view buf); + void ProcessPing (std::string_view text); + 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); + const std::map ExtractParams (std::string_view buf); void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); @@ -164,16 +183,18 @@ namespace client Socket_t m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; - size_t m_BufferOffset; - uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; + size_t m_BufferOffset; // for session only + uint8_t m_StreamBuffer[SAM_STREAM_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname bool m_IsSilent; bool m_IsAccepting; // for eSAMSocketTypeAcceptor only + bool m_IsReceiving; // for eSAMSocketTypeStream only std::shared_ptr m_Stream; + int m_Version; }; - enum SAMSessionType + enum class SAMSessionType { eSAMSessionTypeUnknown, eSAMSessionTypeStream, @@ -188,8 +209,9 @@ namespace client std::string Name; SAMSessionType Type; std::shared_ptr UDPEndpoint; // TODO: move - - SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); + std::list, uint64_t> > acceptQueue; // socket, receive time in seconds + + SAMSession (SAMBridge & parent, std::string_view name, SAMSessionType type); virtual ~SAMSession () {}; virtual std::shared_ptr GetLocalDestination () = 0; @@ -203,7 +225,7 @@ namespace client { std::shared_ptr localDestination; - SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); + SAMSingleSession (SAMBridge & parent, std::string_view name, SAMSessionType type, std::shared_ptr dest); ~SAMSingleSession (); std::shared_ptr GetLocalDestination () { return localDestination; }; @@ -212,18 +234,18 @@ namespace client struct SAMMasterSession: public SAMSingleSession { - std::set subsessions; - SAMMasterSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest): - SAMSingleSession (parent, name, eSAMSessionTypeMaster, dest) {}; + std::set > subsessions; + SAMMasterSession (SAMBridge & parent, std::string_view name, std::shared_ptr dest): + SAMSingleSession (parent, name, SAMSessionType::eSAMSessionTypeMaster, dest) {}; void Close (); }; 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, std::string_view name, SAMSessionType type, uint16_t port); // implements SAMSession std::shared_ptr GetLocalDestination (); void StopLocalDestination (); @@ -233,20 +255,20 @@ 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 (); }; - std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient - const std::map * params); + auto& GetService () { return GetIOService (); }; + std::shared_ptr CreateSession (std::string_view id, SAMSessionType type, std::string_view destination, // empty string means transient + const std::map& params); bool AddSession (std::shared_ptr session); - void CloseSession (const std::string& id); - std::shared_ptr FindSession (const std::string& id) const; + void CloseSession (std::string_view id); + std::shared_ptr FindSession (std::string_view id) const; - std::list > ListSockets(const std::string & id) const; + std::list > ListSockets(std::string_view id) const; /** send raw data to remote endpoint from our UDP Socket */ void SendTo (const std::vector& bufs, const boost::asio::ip::udp::endpoint& ep); @@ -254,7 +276,7 @@ namespace client void AddSocket(std::shared_ptr socket); void RemoveSocket(const std::shared_ptr & socket); - bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; + bool ResolveSignatureType (std::string_view name, i2p::data::SigningKeyType& type) const; private: @@ -264,6 +286,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; @@ -271,11 +297,11 @@ namespace client boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; mutable std::mutex m_SessionsMutex; - std::map > m_Sessions; + std::map, std::less<>> m_Sessions; mutable std::mutex m_OpenSocketsMutex; std::list > m_OpenSockets; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; - std::map m_SignatureTypes; + const std::map m_SignatureTypes; public: diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index ca87e22a..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 index 1e4b3d7c..54c4a21d 100644 --- a/libi2pd_client/UDPTunnel.cpp +++ b/libi2pd_client/UDPTunnel.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,20 +19,34 @@ 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) - { - 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(); + boost::system::error_code ec; + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint, 0, ec); + if (!ec) + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + else + LogPrint (eLogInfo, "UDP Server: Send exception: ", ec.message (), " to ", m_RemoteEndpoint); } - void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) + 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(); + boost::system::error_code ec; + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint, 0, ec); + if (!ec) + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + else + LogPrint (eLogInfo, "UDP Server: Send exception: ", ec.message (), " to ", m_RemoteEndpoint); } } @@ -41,11 +55,12 @@ namespace client 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 ) + while(itr != m_Sessions.end()) + { + if(now - itr->second->LastActivity >= delta ) itr = m_Sessions.erase(itr); else - ++itr; + itr++; } } @@ -66,15 +81,25 @@ namespace client UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) { auto ih = from.GetIdentHash(); - for (auto & s : m_Sessions ) + auto idx = GetSessionIndex (remotePort, localPort); { - if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) + std::lock_guard lock(m_SessionsMutex); + auto it = m_Sessions.find (idx); + if (it != m_Sessions.end ()) { - /** found existing session */ - LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); - return s; + 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()) @@ -84,10 +109,12 @@ namespace client } 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; + + 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, @@ -102,6 +129,7 @@ namespace client RemotePort(theirPort) { IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + IPSocket.non_blocking (true); Receive(); } @@ -144,9 +172,9 @@ namespace client } 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 port, bool gzip) : + 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_Gzip (gzip) + m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip) { } @@ -160,14 +188,23 @@ namespace client 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)); + 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 (); + if (dgram) { + dgram->ResetReceiver (m_inPort); + dgram->ResetRawReceiver (m_inPort); + } } std::vector > I2PUDPServerTunnel::GetSessions () @@ -175,8 +212,9 @@ namespace client std::vector > sessions; std::lock_guard lock (m_SessionsMutex); - for (UDPSessionPtr s: m_Sessions) + 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; @@ -195,10 +233,10 @@ namespace client 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) : + uint16_t remotePort, bool gzip, i2p::datagram::DatagramVersion datagramVersion) : 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) + m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip), m_DatagramVersion (datagramVersion) { } @@ -215,14 +253,19 @@ namespace client 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)); + m_LocalSocket->non_blocking (true); - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip, m_DatagramVersion); dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); + std::placeholders::_5), + RemotePort + ); dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, 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), + RemotePort + ); m_LocalDest->Start (); if (m_ResolveThread == nullptr) @@ -233,7 +276,10 @@ namespace client void I2PUDPClientTunnel::Stop () { auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) dgram->ResetReceiver (); + if (dgram) { + dgram->ResetReceiver (RemotePort); + dgram->ResetRawReceiver (RemotePort); + } m_cancel_resolve = true; m_Sessions.clear(); @@ -364,9 +410,13 @@ namespace client 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 (); + boost::system::error_code ec; + m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first, 0, ec); + if (!ec) + // mark convo as active + itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); + else + LogPrint (eLogInfo, "UDP Client: Send exception: ", ec.message (), " to ", itr->second->first); } } else diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h index 862ce216..7303aa9a 100644 --- a/libi2pd_client/UDPTunnel.h +++ b/libi2pd_client/UDPTunnel.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 * @@ -72,7 +72,7 @@ namespace client 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 */ + /** how long has this conversation been idle in ms */ uint64_t idle; }; @@ -104,6 +104,7 @@ namespace client 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: @@ -112,9 +113,10 @@ namespace client boost::asio::ip::address m_LocalAddress; boost::asio::ip::udp::endpoint m_RemoteEndpoint; std::mutex m_SessionsMutex; - std::vector m_Sessions; + 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: @@ -128,7 +130,7 @@ namespace client 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); + uint16_t remotePort, bool gzip, i2p::datagram::DatagramVersion datagramVersion); ~I2PUDPClientTunnel (); void Start (); @@ -173,6 +175,7 @@ namespace client uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; bool m_Gzip; + i2p::datagram::DatagramVersion m_DatagramVersion; std::shared_ptr m_LastSession; public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21daadd9..fb03d434 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,10 +49,6 @@ set(test-gost-sig_SRCS test-gost-sig.cpp ) -set(test-x25519_SRCS - test-x25519.cpp -) - set(test-aeadchacha20poly1305_SRCS test-aeadchacha20poly1305.cpp ) @@ -65,6 +61,14 @@ 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}) @@ -73,10 +77,11 @@ add_executable(test-http-url ${test-http-url_SRCS}) add_executable(test-base-64 ${test-base-64_SRCS}) add_executable(test-gost ${test-gost_SRCS}) add_executable(test-gost-sig ${test-gost-sig_SRCS}) -add_executable(test-x25519 ${test-x25519_SRCS}) add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) add_executable(test-blinding ${test-blinding_SRCS}) add_executable(test-elligator ${test-elligator_SRCS}) +add_executable(test-eddsa ${test-eddsa_SRCS}) +add_executable(test-aes ${test-aes_SRCS}) set(LIBS libi2pd @@ -97,10 +102,11 @@ target_link_libraries(test-http-url ${LIBS}) target_link_libraries(test-base-64 ${LIBS}) target_link_libraries(test-gost ${LIBS}) target_link_libraries(test-gost-sig ${LIBS}) -target_link_libraries(test-x25519 ${LIBS}) target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) target_link_libraries(test-blinding ${LIBS}) target_link_libraries(test-elligator ${LIBS}) +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) @@ -110,7 +116,8 @@ add_test(test-http-url ${TEST_PATH}/test-http-url) add_test(test-base-64 ${TEST_PATH}/test-base-64) add_test(test-gost ${TEST_PATH}/test-gost) add_test(test-gost-sig ${TEST_PATH}/test-gost-sig) -add_test(test-x25519 ${TEST_PATH}/test-x25519) add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) add_test(test-blinding ${TEST_PATH}/test-blinding) add_test(test-elligator ${TEST_PATH}/test-elligator) +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 9c5711e2..b020427d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,13 +1,14 @@ SYS := $(shell $(CXX) -dumpmachine) -CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++17 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files INCFLAGS += -I../libi2pd LIBI2PD = ../libi2pd.a TESTS = \ test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \ - test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator + 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 @@ -17,7 +18,7 @@ ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstrin endif LDLIBS = \ - -lboost_filesystem$(BOOST_SUFFIX) \ + -lboost_system$(BOOST_SUFFIX) \ -lboost_program_options$(BOOST_SUFFIX) \ -lssl \ -lcrypto \ @@ -43,9 +44,6 @@ test-gost: test-gost.cpp $(LIBI2PD) test-gost-sig: test-gost-sig.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-x25519: test-x25519.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) - test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) @@ -55,6 +53,12 @@ test-blinding: test-blinding.cpp $(LIBI2PD) 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 echo Running $$TEST; ./$$TEST ; done diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp index 64a0f358..2ba6a253 100644 --- a/tests/test-aeadchacha20poly1305.cpp +++ b/tests/test-aeadchacha20poly1305.cpp @@ -43,18 +43,20 @@ uint8_t encrypted[114] = int main () { uint8_t buf[114+16]; + i2p::crypto::AEADChaCha20Poly1305Encryptor encryptor; // test encryption - i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true); + encryptor.Encrypt ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16); assert (memcmp (buf, encrypted, 114) == 0); assert (memcmp (buf + 114, tag, 16) == 0); // test decryption uint8_t buf1[114]; - assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false)); + i2p::crypto::AEADChaCha20Poly1305Decryptor decryptor; + assert (decryptor.Decrypt (buf, 114, ad, 12, key, nonce, buf1, 114)); assert (memcmp (buf1, text, 114) == 0); // test encryption of multiple buffers memcpy (buf, text, 114); std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; - i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); - i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); + encryptor.Encrypt (bufs, key, nonce, buf + 114); + decryptor.Decrypt (buf, 114, nullptr, 0, key, nonce, buf1, 114); assert (memcmp (buf1, text, 114) == 0); } diff --git a/tests/test-aes.cpp b/tests/test-aes.cpp new file mode 100644 index 00000000..15f4de1e --- /dev/null +++ b/tests/test-aes.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "Crypto.h" + +uint8_t ecb_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t ecb_plain1[16] = +{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a +}; + +uint8_t ecb_cipher1[16] = +{ + 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8 +}; + +uint8_t cbc_key1[32] = +{ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 +}; + +uint8_t cbc_iv1[16] = +{ + 0xF5, 0x8C, 0x4C, 0x04, 0xD6, 0xE5, 0xF1, 0xBA, 0x77, 0x9E, 0xAB, 0xFB, 0x5F, 0x7B, 0xFB, 0xD6 +}; + +uint8_t cbc_plain1[16] = +{ + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51 +}; + +uint8_t cbc_cipher1[16] = +{ + 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d +}; + +int main () +{ + // ECB encrypt test1 + i2p::crypto::ECBEncryption ecbencryption; + ecbencryption.SetKey (ecb_key1); + uint8_t out[16]; + ecbencryption.Encrypt (ecb_plain1, out); + assert (memcmp (ecb_cipher1, out, 16) == 0); + + // ECB decrypt test1 + i2p::crypto::ECBDecryption ecbdecryption; + ecbdecryption.SetKey (ecb_key1); + ecbdecryption.Decrypt (ecb_cipher1, out); + assert (memcmp (ecb_plain1, out, 16) == 0); + // CBC encrypt test + i2p::crypto::CBCEncryption cbcencryption; + cbcencryption.SetKey (cbc_key1); + cbcencryption.Encrypt (cbc_plain1, 16, cbc_iv1, out); + assert (memcmp (cbc_cipher1, out, 16) == 0); + // CBC decrypt test + i2p::crypto::CBCDecryption cbcdecryption; + cbcdecryption.SetKey (cbc_key1); + cbcdecryption.Decrypt (cbc_cipher1, 16, cbc_iv1, out); + assert (memcmp (cbc_plain1, out, 16) == 0); +} + diff --git a/tests/test-base-64.cpp b/tests/test-base-64.cpp index 0ab46c06..63817bf4 100644 --- a/tests/test-base-64.cpp +++ b/tests/test-base-64.cpp @@ -11,8 +11,7 @@ int main() { char out[16]; /* bytes -> b64 */ - assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); - assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); + assert(ByteStreamToBase64(NULL, 0) == ""); assert(Base64EncodingBufferSize(2) == 4); assert(Base64EncodingBufferSize(4) == 8); @@ -23,19 +22,20 @@ int main() { assert(Base64EncodingBufferSize(12) == 16); assert(Base64EncodingBufferSize(13) == 20); - assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); - assert(memcmp(out, "dGVzdA==", 8) == 0); + const std::string out_str(ByteStreamToBase64((uint8_t *) in, in_len)); + assert(out_str.size() == 8); + assert(out_str == "dGVzdA=="); /* b64 -> bytes */ - assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); - assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); + assert(Base64ToByteStream("", NULL, 0) == 0); + assert(Base64ToByteStream("", (uint8_t *) out, sizeof(out)) == 0); in = "dGVzdA=="; /* valid b64 */ - assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); + assert(Base64ToByteStream(in, (uint8_t *) out, sizeof(out)) == 4); assert(memcmp(out, "test", 4) == 0); in = "dGVzdA="; /* invalid b64 : not padded */ - assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); + assert(Base64ToByteStream(in, (uint8_t *) out, sizeof(out)) == 0); in = "dG/z.A=="; /* invalid b64 : char not from alphabet */ // assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); diff --git a/tests/test-eddsa.cpp b/tests/test-eddsa.cpp new file mode 100644 index 00000000..9de2c088 --- /dev/null +++ b/tests/test-eddsa.cpp @@ -0,0 +1,66 @@ +#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); + assert(memcmp (s, sig, 64) == 0); + + i2p::crypto::EDDSA25519Verifier verifier; + verifier.SetPublicKey (pub); + assert(verifier.Verify (msg, 1023, s)); +} diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp deleted file mode 100644 index a1f3f424..00000000 --- a/tests/test-x25519.cpp +++ /dev/null @@ -1,38 +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 -}