diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 0f57a84a..00000000 --- a/.editorconfig +++ /dev/null @@ -1,39 +0,0 @@ -# editorconfig.org - -root = true - -[*] -# Unix style files -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[Makefile,Makefile.*] -indent_style = tab -indent_size = 4 - -[*.cmd] -indent_style = space -indent_size = 2 -end_of_line = crlf - -[*.{h,cpp}] -indent_style = tab -indent_size = 4 - -[*.rc] -indent_style = space -indent_size = 4 - -[*.{md,markdown}] -indent_style = space -indent_size = 2 -trim_trailing_whitespace = false - -[*.yml] -indent_style = space -indent_size = 2 - -[*.patch] -trim_trailing_whitespace = false diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml deleted file mode 100644 index 32698498..00000000 --- a/.github/workflows/build-deb.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Build Debian packages - -on: - push: - branches: - - '*' - paths: - - .github/workflows/build-deb.yml - - contrib/** - - daemon/** - - debian/** - - i18n/** - - libi2pd/** - - libi2pd_client/** - - Makefile - - Makefile.linux - tags: - - '*' - pull_request: - branches: - - '*' - -jobs: - build: - name: ${{ matrix.dist }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - dist: ['bullseye', 'bookworm', 'trixie'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Commit Hash - id: commit - uses: prompt/actions-commit-hash@v3.0.0 - - - name: Build package - uses: jtdor/build-deb-action@v1 - with: - docker-image: debian:${{ matrix.dist }}-slim - buildpackage-opts: --build=binary --no-sign - before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build" - extra-build-deps: devscripts git - - - name: Upload package - uses: actions/upload-artifact@v4 - with: - name: i2pd_${{ matrix.dist }} - path: debian/artifacts/i2pd_*.deb - - - name: Upload debugging symbols - uses: actions/upload-artifact@v4 - with: - name: i2pd-dbgsym_${{ matrix.dist }} - path: debian/artifacts/i2pd-dbgsym_*.deb diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml index a4a7566a..b11569b7 100644 --- a/.github/workflows/build-freebsd.yml +++ b/.github/workflows/build-freebsd.yml @@ -1,50 +1,21 @@ name: Build on FreeBSD -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: - - '*' +on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: macos-10.15 name: with UPnP - steps: - - name: Checkout - uses: actions/checkout@v4 - + - uses: actions/checkout@v2 - name: Test in FreeBSD id: test - uses: vmactions/freebsd-vm@v1 + uses: vmactions/freebsd-vm@v0.1.5 with: usesh: true mem: 2048 - sync: rsync - copyback: true - prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc + prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc run: | cd build cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . gmake -j2 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: i2pd-freebsd - path: build/i2pd diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index 31f0b90d..50672d26 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -1,45 +1,20 @@ name: Build on OSX -on: - push: - branches: - - '*' - paths: - - .github/workflows/build-osx.yml - - daemon/** - - i18n/** - - libi2pd/** - - libi2pd_client/** - - Makefile - - Makefile.homebrew - tags: - - '*' - pull_request: - branches: - - '*' +on: [push, pull_request] jobs: build: name: With USE_UPNP=${{ matrix.with_upnp }} runs-on: macOS-latest - strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install required formulae + - uses: actions/checkout@v2 + - name: install packages run: | - find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete brew update brew install boost miniupnpc openssl@1.1 - - - name: List installed formulae - run: brew list - - - name: Build application + - 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 deleted file mode 100644 index 922ebd0d..00000000 --- a/.github/workflows/build-windows-msvc.yml-disabled +++ /dev/null @@ -1,80 +0,0 @@ -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 6f10e62b..e7752d55 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,25 +1,6 @@ name: Build on Windows -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: - - '*' +on: [push, pull_request] defaults: run: @@ -27,224 +8,68 @@ defaults: jobs: build: - name: ${{ matrix.arch }} + name: Building using ${{ matrix.arch }} toolchain runs-on: windows-latest - strategy: - fail-fast: false + fail-fast: true 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 } + { 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 } ] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - + - uses: actions/checkout@v2 - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} - 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 + install: base-devel mingw-w64-${{ matrix.arch }}-gcc 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@v4 + uses: actions/upload-artifact@v2 with: name: i2pd-${{ matrix.arch_short }}.exe path: i2pd.exe - - 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: - - 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 + name: Building for Windows XP runs-on: windows-latest - - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - + - uses: actions/checkout@v2 - 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: Clone MinGW packages repository and revert boost to 1.85.0 + - name: Build WinXP-capable CRT packages run: | git clone https://github.com/msys2/MINGW-packages - cd MINGW-packages - git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost - cd mingw-w64-boost - sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD - - # headers - - name: Get headers package version - 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 + pushd MINGW-packages + pushd mingw-w64-headers-git sed -i 's/0x601/0x501/' PKGBUILD - 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 + 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 - 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@v4 + uses: actions/upload-artifact@v2 with: name: i2pd-xp.exe path: i2pd.exe diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b65ec9d..affa0b8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,67 +1,88 @@ name: Build on Ubuntu -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: - - '*' +on: [push, pull_request] jobs: build-make: name: Make with USE_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-latest - + runs-on: ubuntu-18.04 strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] - steps: - - name: Checkout - uses: actions/checkout@v4 - + - uses: actions/checkout@v2 - name: install packages run: | + sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update - sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev - + sudo apt-get install build-essential libboost1.74-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-latest - + runs-on: ubuntu-18.04 strategy: fail-fast: true matrix: with_upnp: ['ON', 'OFF'] - steps: - - name: Checkout - uses: actions/checkout@v4 - + - uses: actions/checkout@v2 - name: install packages run: | + sudo add-apt-repository ppa:mhier/libboost-latest sudo apt-get update - sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev - + sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: | cd build cmake -DWITH_UPNP=${{ matrix.with_upnp }} . make -j3 + build-deb-stretch: + name: Build package for stretch + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: change debian changelog + run: | + sudo apt-get update + sudo apt-get install devscripts + debchange -v "`git describe --tags`-stretch" -b -M --distribution stretch "trunk build" + - uses: singingwolfboy/build-dpkg-stretch@v1 + id: build + with: + args: --unsigned-source --unsigned-changes -b + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename }} + path: ${{ steps.build.outputs.filename }} + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename-dbgsym }} + path: ${{ steps.build.outputs.filename-dbgsym }} + build-deb-buster: + name: Build package for buster + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: change debian changelog + run: | + sudo apt-get update + sudo apt-get install devscripts + debchange -v "`git describe --tags`-buster" -b -M --distribution buster "trunk build" + - uses: singingwolfboy/build-dpkg-buster@v1 + id: build + with: + args: --unsigned-source --unsigned-changes -b + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename }} + path: ${{ steps.build.outputs.filename }} + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename-dbgsym }} + path: ${{ steps.build.outputs.filename-dbgsym }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c6d55664..1ac5b552 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,137 +4,67 @@ on: push: 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 }} + docker: runs-on: ubuntu-latest permissions: packages: write contents: read - strategy: - matrix: - include: [ - { platform: 'linux/amd64', archname: 'amd64' }, - { platform: 'linux/386', archname: 'i386' }, - { platform: 'linux/arm64', archname: 'arm64' }, - { platform: 'linux/arm/v7', archname: 'armv7' }, - ] - steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Login to GitHub Container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to GitHub Container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build container for ${{ matrix.archname }} - uses: docker/build-push-action@v5 - with: - context: ./contrib/docker - file: ./contrib/docker/Dockerfile - platforms: ${{ matrix.platform }} - push: true - tags: | - purplei2p/i2pd:latest-${{ matrix.archname }} - ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} - provenance: false + - name: Build and push trunk container + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: docker/build-push-action@v2 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + push: true + tags: | + purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:latest - push: - name: Pushing merged manifest - runs-on: ubuntu-latest + - name: Set env + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - permissions: - packages: write - contents: read - - needs: build - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create and push latest manifest image to Docker Hub - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: purplei2p/i2pd:latest - tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push latest manifest image to GHCR - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: ghcr.io/purplei2p/i2pd:latest - tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true - - - name: Store release version to env - if: ${{ startsWith(github.ref, 'refs/tags/') }} - run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - - - name: Create and push release manifest to Docker Hub - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push release manifest to GHCR - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true + - name: Build and push release container + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: docker/build-push-action@v2 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + push: true + tags: | + purplei2p/i2pd:latest + purplei2p/i2pd:latest-release + purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + ghcr.io/purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:latest-release + ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} diff --git a/ChangeLog b/ChangeLog index 6c4ef5b3..085ccfe1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,528 +1,6 @@ # 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 -- Localization to Swedish, Portuguese, Turkish, Polish -- Periodically send Datetime block in NTCP2 and SSU2 -- Don't select random port from reserved -- In memory table for peer profiles -- Store if router was unreachable in it's peer profile -- Show IPv6 addresses in square brackets in webconsole -- Check referer when processing Addresshelper -### Changed -- Algorithm for tunnel creation success rate calculation -- Drop incoming NTCP2 and SSU2 connection if published IP doesn't match actual endpoint -- Exclude actually unreachable router from netdb for 2 hours -- Select first hop from high bandwidth peers for client tunnels -- Drop too long or too short LeaseSet -- Delete router from netdb if became invalid after update -- Terminate existing session if clock skew detected -- Close previous UDP socket if open before reopening -- Minimal version for floodfill is 0.9.51 -- Sort transports by endpoints in webconsole -### Fixed -- Deadlock during processing I2NP block with Garlic in ECIES encrypted message to router -- Race condition with encrypted LeaseSets -- HTTP query detection -- Connection attempts to IPs from invalid ranges -- Publish "0.0.0.0" in RouterInfo -- Crash upon receiving PeerTest 7 -- Tunnels for closed SAM session socket -- Missing NTCP2 address in RouterInfo if enabled back - -## [2.45.1] - 2023-01-11 -### Added -- Full Cone NAT status error -### Changed -- Drop duplicated I2NP messages in SSU2 -- Set rejection code 30 if tunnel with id already exists -- Network status is always OK if peer test msg 5 received -### Fixed -- UPnP crash if SSU2 or NTCP2 is disabled -- Crash on termination for some platforms - -## [2.45.0] - 2023-01-03 -### Added -- Test for Symmetric NAT with peer test msgs 6 and 7 -- Webconsole "No Descriptors" router error state -- 1 and 15 seconds bandwidth calculation for i2pcontrol -- Show non-zero send queue size for transports in web console -- Compressible padding for I2P addresses -- Localization to Czech -- Don't accept incoming session from invalid/reserved addresses for NTCP2 and SSU2 -- Limit simultaneous tunnel build requests by 4 per pool -### Changed -- Removed SSU support -- Reduced bandwidth calculation interval from 60 to 15 seconds -- Increased default max transit tunnels number from 2500 to 5000 or 10000 for floodfill -- Transit tunnels limit is doubled if floodfill mode is enabled -- NTCP2 and SSU2 timestamps are rounded to seconds -- Drop RouterInfos and LeaseSets with timestamp from future -- Don't delete unreachable routers if tunnel creation success rate is too low -- Refuse duplicated incoming pending NTCP2 session from same IP -- Don't send SSU2 termination again if termination received block received -- Handle standard network error for SSU2 without throwing an exception -- Don't select overloaded peer for next tunnel -- Remove "X-Requested-With" in HTTP Proxy for non-AJAX requests -### Fixed -- File descriptors leak -- Random crash on AddressBook update -- Crash if incorrect LeaseSet size -- Spamming to log if no descriptors -- ::1 address in RouterInfo -- SSU2 network error handling (especially for Windows) -- Race condition with pending outgoing SSU2 sessions -- RTT self-reduction for long-live streams - -## [2.44.0] - 2022-11-20 -### Added -- SSL connection for server I2P tunnels -- Localization to Italian and Spanish -- SSU2 through SOCKS5 UDP proxy -- Reload tunnels through web console -- SSU2 send immediate ack request flag -- SSU2 send and verify path challenge -- Configurable ssu2.mtu4 and ssu2.mtu6 -### Changed -- SSU2 is enabled and SSU is disabled by default -- Separate network status and error -- Random selection between NTCP2 and SSU2 priority -- Added notbob.i2p to jump services -- Remove DoNotTrack flag from HTTP Request header -- Skip addresshelper page if destination was not changed -- SSU2 allow different ports from RelayReponse and HolePunch -- SSU2 resend PeerTest msg 1 and msg 2 -- SSU2 Send Retry instead SessionCreated if clock skew detected -### Fixed -- Long HTTP headers for HTTP proxy and HTTP server tunnel -- SSU2 resends and resend limits -- Crash at startup if addressbook is disabled -- NTCP2 ipv6 connection through SOCKS5 proxy -- SSU2 SessionRequest with zero token -- SSU2 MTU less than 1280 -- SSU2 port=1 -- Incorrect addresses from network interfaces -- Definitions for Darwin PPC; do not use pthread_setname_np - -## [2.43.0] - 2022-08-22 -### Added -- Complete SSU2 implementation -- Localization to Chinese -- Send RouterInfo update for long live sessions -- Explicit ipv6 ranges of known tunnel brokers for MTU detection -- Always send "Connection: close" and strip out Keep-Alive for server HTTP tunnel -- Show ports for all transports in web console -- Translation of webconsole site title -- Support for Windows ProgramData path when running as service -- Ability to turn off address book -- Handle signals TSTP and CONT to stop and resume network -### Changed -- Case insensitive headers for server HTTP tunnel -- Do not show 'Address registration' line if LeaseSet is encrypted -- SSU2 transports have higher priority than SSU -- Disable ElGamal precalculated table if no SSU -- Deprecate limits.ntcpsoft, limits.ntcphard and limits.ntcpthreads config options -- SSU2 is enabled and SSU is disabled by default for new installations -### Fixed -- Typo with Referer header name in HTTP proxy -- Can't handle garlic message from an exploratory tunnel -- Incorrect encryption key for exploratory lookup reply -- Bound checks issues in LeaseSets code -- MTU detection on Windows -- Crash on stop of active server tunnel -- Send datagram to wrong destination in SAM -- Incorrect static key in RouterInfo if the keys were regenerated -- Duplicated sessions in BOB - -## [2.42.1] - 2022-05-24 -### Fixed -- Incorrect jump link in HTTP Proxy - ## [2.42.0] - 2022-05-22 ### Added - Preliminary SSU2 implementation @@ -535,15 +13,15 @@ ### Changed - Maximum RouterInfo length increased to 3K - Skip unknown addresses in RouterInfo -- Don't pick own router for peer test +- Don't pick own router for peer test - Reseeds list - Internal numeric id for families - Use ipv6 preference only when netinet headers not used -- Close stream if delete requested +- Close stream if delete requested - Remove version from title in web console - Drop MESHNET build option - Set data path before initialization -- Don't show registration block in web console if token is not provided +- Don't show registration block in web console if token is not provided ### Fixed - Encrypted LeaseSet for EdDSA signature - Clients tunnels are not built if clock is not synced on start @@ -899,7 +377,7 @@ ### Added - Client auth flag for b33 address ### Changed -- Remove incoming NTCP2 session from pending list when established +- Remove incoming NTCP2 session from pending list when established - Handle errors for NTCP2 SessionConfrimed send ### Fixed - Failure to start on Windows XP @@ -1203,7 +681,7 @@ ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels -- Configurable list of reseed servers and initial addressbook +- Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support diff --git a/LICENSE b/LICENSE index f59491f5..9a1e4527 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2025, The PurpleI2P Project +Copyright (c) 2013-2020, The PurpleI2P Project All rights reserved. diff --git a/Makefile b/Makefile index ab98a4e0..e9bd9187 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SYS := $(shell $(CXX) -dumpmachine) ifneq (, $(findstring darwin, $(SYS))) SHARED_SUFFIX = dylib -else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) +else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) SHARED_SUFFIX = dll else SHARED_SUFFIX = so @@ -29,6 +29,7 @@ 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) @@ -46,10 +47,6 @@ else LD_DEBUG = -s endif -ifneq (, $(DESTDIR)) - PREFIX = $(DESTDIR) -endif - ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) @@ -57,33 +54,26 @@ ifneq (, $(findstring darwin, $(SYS))) else include Makefile.osx endif -else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) - DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp - include Makefile.mingw else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.linux else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd -else ifneq (, $(findstring haiku, $(SYS))) - DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp - include Makefile.haiku -else ifneq (, $(findstring solaris, $(SYS))) - DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp - include Makefile.solaris +else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) + DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp + include Makefile.mingw 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) - DEFINES += -DGITVER=$(GIT_VERSION) + NEEDED_CXXFLAGS += -DGITVER=\"$(GIT_VERSION)\" endif +NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) + 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)) @@ -116,17 +106,17 @@ wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## custom FLAGS to work at build-time. obj/%.o: %.cpp | mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $< + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) - $(CXX) $(DEFINES) $(LDFLAGS) -o $@ $^ $(LDLIBS) + $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) endif $(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) diff --git a/Makefile.bsd b/Makefile.bsd index 9c6de7f0..39e5651a 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -1,22 +1,12 @@ 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. -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 - +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 diff --git a/Makefile.haiku b/Makefile.haiku deleted file mode 100644 index eb56a207..00000000 --- a/Makefile.haiku +++ /dev/null @@ -1,14 +0,0 @@ -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 9d8d17cd..b1d81ce7 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -1,48 +1,51 @@ # root directory holding homebrew -BREWROOT = /opt/homebrew +BREWROOT = /usr/local 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} -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 +ifndef TRAVIS + CXX = clang++ +endif ifeq ($(USE_STATIC),yes) - 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 + 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 else LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib - LDLIBS = -lz -lssl -lcrypto -lboost_program_options -lpthread -ifeq ($(USE_UPNP),yes) - LDFLAGS += -L${UPNPROOT}/lib - LDLIBS += -lminiupnpc -endif + LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) - DEFINES += -DUSE_UPNP + LDFLAGS += -ldl + CXXFLAGS += -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 - install -d ${PREFIX}/bin - install -m 755 ${I2PD} ${PREFIX}/bin - install -d ${PREFIX}/etc ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d + install -d ${PREFIX}/bin ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/share/doc/i2pd ${PREFIX}/share/i2pd ${PREFIX}/share/man/man1 ${PREFIX}/var/lib/i2pd + install -m 755 ${I2PD} ${PREFIX}/bin/ install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd - install -d ${PREFIX}/share ${PREFIX}/share/doc ${PREFIX}/share/doc/i2pd - install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd - install -d ${PREFIX}/share/i2pd @cp -R contrib/certificates ${PREFIX}/share/i2pd/ - install -d ${PREFIX}/share/man ${PREFIX}/share/man/man1 + install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd @gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 - install -d ${PREFIX}/var ${PREFIX}/var/lib ${PREFIX}/var/lib/i2pd - @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/certificates + @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/var/lib/i2pd/tunnels.d @ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf @ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt diff --git a/Makefile.linux b/Makefile.linux index 4ea39e22..feeb722e 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -9,17 +9,20 @@ LDFLAGS ?= ${LD_DEBUG} ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FDLAGS to work at build-time. -# detect proper flag for c++17 support by compilers +# detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match $(CXX) 'clang'),5) + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 + NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 +else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 + NEEDED_CXXFLAGS += -std=c++11 + LDLIBS = -latomic +else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 -else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9 - NEEDED_CXXFLAGS += -std=c++17 - LDLIBS = -lboost_system -lstdc++fs -else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12 - NEEDED_CXXFLAGS += -std=c++17 -else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+ - NEEDED_CXXFLAGS += -std=c++20 + LDLIBS = -latomic else # not supported $(error Compiler too old) endif @@ -31,6 +34,9 @@ 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 @@ -40,7 +46,7 @@ ifeq ($(USE_UPNP),yes) endif LDLIBS += -lpthread -ldl else - LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic + LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif @@ -48,22 +54,23 @@ endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) - DEFINES += -DUSE_UPNP + 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 endif install: all - install -d ${PREFIX}/bin - install -m 755 ${I2PD} ${PREFIX}/bin - install -d ${PREFIX}/etc ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d + install -d ${PREFIX}/bin ${PREFIX}/etc ${PREFIX}/etc/i2pd ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/usr ${PREFIX}/usr/share ${PREFIX}/usr/share/doc/i2pd ${PREFIX}/usr/share/i2pd ${PREFIX}/usr/share/man ${PREFIX}/usr/share/man/man1 ${PREFIX}/var/lib ${PREFIX}/var/lib/i2pd + install -m 755 ${I2PD} ${PREFIX}/bin/ install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd - install -d ${PREFIX}/share ${PREFIX}/share/doc ${PREFIX}/share/doc/i2pd - install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd - install -d ${PREFIX}/share/i2pd - @cp -R contrib/certificates ${PREFIX}/share/i2pd/ - install -d ${PREFIX}/share/man ${PREFIX}/share/man/man1 - @gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 - install -d ${PREFIX}/var ${PREFIX}/var/lib ${PREFIX}/var/lib/i2pd - @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/certificates + @cp -R contrib/certificates ${PREFIX}/usr/share/i2pd/ + install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/usr/share/doc/i2pd + @gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/usr/share/man/man1 + @ln -sf ${PREFIX}/usr/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf.d ${PREFIX}/var/lib/i2pd/tunnels.d @ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf @ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt diff --git a/Makefile.mingw b/Makefile.mingw index 32d60764..e31d895e 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -3,48 +3,63 @@ USE_WIN32_APP := yes WINDRES = windres -CXXFLAGS := $(CXX_DEBUG) -fPIC -msse -INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -static -fPIC -msse +CXXFLAGS := $(CXX_DEBUG) -DWIN32_LEAN_AND_MEAN -fPIC -msse +INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 +LDFLAGS := ${LD_DEBUG} -static -NEEDED_CXXFLAGS += -std=c++20 -DEFINES += -DWIN32_LEAN_AND_MEAN +# detect proper flag for c++11 support by compilers +CXXVER := $(shell $(CXX) -dumpversion) +ifeq ($(shell expr match ${CXXVER} "[4]\.[7-9]\|4\.1[0-9]\|[5-6]"),4) # gcc 4.7 - 6 + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 + NEEDED_CXXFLAGS += -std=c++17 +else # not supported +$(error Compiler too old) +endif + +# Boost libraries suffix +BOOST_SUFFIX = -mt # UPNP Support ifeq ($(USE_UPNP),yes) - DEFINES += -DUSE_UPNP -DMINIUPNP_STATICLIB + CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -lminiupnpc endif -ifeq ($(USE_WINXP_FLAGS), yes) - DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -endif - LDLIBS += \ - $(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 \ + -lboost_system$(BOOST_SUFFIX) \ + -lboost_date_time$(BOOST_SUFFIX) \ + -lboost_filesystem$(BOOST_SUFFIX) \ + -lboost_program_options$(BOOST_SUFFIX) \ + -lssl \ + -lcrypto \ + -lz \ -lwsock32 \ -lws2_32 \ - -liphlpapi \ - -lcrypt32 \ -lgdi32 \ + -liphlpapi \ -lole32 \ -luuid \ -lpthread ifeq ($(USE_WIN32_APP), yes) - DEFINES += -DWIN32_APP + NEEDED_CXXFLAGS += -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) $(DEFINES) $(INCFLAGS) --preprocessor-arg=-MMD --preprocessor-arg=-MP --preprocessor-arg=-MF$@.d -i $< -o $@ + $(WINDRES) -i $< -o $@ diff --git a/Makefile.osx b/Makefile.osx index a2ad515e..2e52585e 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,20 +1,20 @@ CXX = clang++ -CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17 +CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX 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/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 + 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 else - LDLIBS = -lz -lssl -lcrypto -lboost_program_options -lpthread + LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl - DEFINES += -DUSE_UPNP + CXXFLAGS += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else @@ -22,8 +22,8 @@ ifeq ($(USE_UPNP),yes) endif endif -OSARCH = $(shell uname -p) - -ifneq ($(OSARCH),powerpc) +ifeq ($(USE_AESNI),yes) + CXXFLAGS += -D__AES__ -maes +else CXXFLAGS += -msse endif diff --git a/Makefile.solaris b/Makefile.solaris deleted file mode 100644 index 77d34114..00000000 --- a/Makefile.solaris +++ /dev/null @@ -1,9 +0,0 @@ -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 3463ed3b..6f2d23ec 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![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* @@ -70,15 +69,15 @@ Build instructions: **Supported systems:** -* GNU/Linux (Debian, Ubuntu, etc) - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) -* CentOS, Fedora, Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) -* Alpine, ArchLinux, openSUSE, Gentoo, etc. +* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) + * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) + * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. * Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) -* Mac OS - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) -* Docker image - [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) +* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) +* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) * 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 - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) * iOS Using i2pd @@ -100,23 +99,13 @@ Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](http Donations --------- -**E-Mail**: ```i2porignal at yandex.com``` - -**BTC**: ```3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f``` - -**LTC**: ```LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59``` - -**ETH**: ```0x9e5bac70d20d1079ceaa111127f4fb3bccce379d``` - -**GST**: ```GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG``` - -**DASH**: ```Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF``` - -**ZEC**: ```t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ``` - -**ANC**: ```AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z``` - -**XMR**: ```497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH``` +BTC: 3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f +LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 +ETH: 0x9e5bac70d20d1079ceaa111127f4fb3bccce379d +DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF +ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ +GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG +XMR: 497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH License ------- diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 48f65c27..0badf802 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,7 +29,7 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - //setlocale(LC_ALL, "Russian"); + setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); i2p::log::SetThrowFunction ([](const std::string& s) @@ -47,7 +47,7 @@ namespace util I2PService service((PSTR)SERVICE_NAME); if (!I2PService::Run(service)) { - LogPrint(eLogCritical, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); return false; } return false; @@ -61,10 +61,10 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - //setlocale(LC_ALL, "Russian"); + setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); #ifdef WIN32_APP - if (!i2p::win32::StartWin32App (isDaemon)) return false; + if (!i2p::win32::StartWin32App ()) 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 c9266b08..5d394d1a 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 32744584..873d6402 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 "version.h" +#include "../libi2pd/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-2023, The PurpleI2P Project" + VALUE "LegalCopyright", "Copyright (C) 2013-2022, 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 0e29c517..377e1076 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,7 +45,6 @@ namespace i2p namespace win32 { DWORD g_GracefulShutdownEndtime = 0; - bool g_isWinService; static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { @@ -145,40 +144,35 @@ namespace win32 s << bytes << " Bytes\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { 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 (testing) - s << " (Test)"; - if (error != eRouterErrorNone) - { - switch (error) + case eRouterStatusError: { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); + s << "Err"; + switch (i2p::context.GetError ()) + { + case eRouterErrorClockSkew: + s << " - Clock skew"; + break; + case eRouterErrorOffline: + s << " - Offline"; + break; + case eRouterErrorSymmetricNAT: + s << " - Symmetric NAT"; + break; + default: ; + } break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - case eRouterErrorFullConeNAT: - s << " - " << tr("Full cone NAT"); - break; - case eRouterErrorNoDescriptors: - s << " - " << tr("No Descriptors"); - break; - default: ; } + default: s << "Unk"; } } @@ -186,11 +180,11 @@ namespace win32 { s << "\n"; s << "Status: "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); + ShowNetworkStatus (s, i2p::context.GetStatus ()); if (i2p::context.SupportsV6 ()) { s << " / "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); + ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; @@ -313,7 +307,7 @@ namespace win32 } case ID_DATADIR: { - std::string datadir(i2p::fs::GetDataDir()); + std::string datadir(i2p::fs::GetUTF8DataDir()); ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); return 0; } @@ -355,7 +349,6 @@ namespace win32 } } } - [[fallthrough]]; } case WM_TRAYICON: { @@ -425,9 +418,8 @@ namespace win32 return DefWindowProc( hWnd, uMsg, wParam, lParam); } - bool StartWin32App (bool isWinService) + bool StartWin32App () { - g_isWinService = isWinService; if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); @@ -456,9 +448,7 @@ namespace win32 MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } - // COM requires message loop to work, which is not implemented in service mode - if (!g_isWinService) - SubscribeToEvents(); + SubscribeToEvents(); return true; } @@ -478,8 +468,7 @@ namespace win32 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - else if(!g_isWinService) - UnSubscribeFromEvents(); + // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } diff --git a/Win32/Win32App.h b/Win32/Win32App.h index 614de738..ebe49efd 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -17,7 +17,7 @@ namespace win32 { extern DWORD g_GracefulShutdownEndtime; - bool StartWin32App (bool isWinService); + bool StartWin32App (); void StopWin32App (); int RunWin32App (); bool GracefulShutdown (); diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp index 4ef768c8..5c56711d 100644 --- a/Win32/Win32NetState.cpp +++ b/Win32/Win32NetState.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,6 @@ IUnknown *pUnknown = nullptr; INetworkListManager *pNetworkListManager = nullptr; IConnectionPointContainer *pCPContainer = nullptr; IConnectionPoint *pConnectPoint = nullptr; -CNetworkListManagerEvent *pNetEvent = nullptr; DWORD Cookie = 0; void SubscribeToEvents() @@ -30,11 +29,7 @@ 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"); @@ -46,8 +41,8 @@ void SubscribeToEvents() Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); if(SUCCEEDED(Result)) { - pNetEvent = new CNetworkListManagerEvent; - Result = pConnectPoint->Advise((IUnknown *)pNetEvent, &Cookie); + CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; + Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); if (SUCCEEDED(Result)) LogPrint(eLogInfo, "NetState: Successfully subscribed to NetworkListManagerEvent messages"); else @@ -64,7 +59,6 @@ void SubscribeToEvents() void UnSubscribeFromEvents() { - LogPrint(eLogInfo, "NetState: Unsubscribing from NetworkListManagerEvents"); try { if (pConnectPoint) { @@ -72,25 +66,14 @@ 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 c1f47a24..c2ddaee4 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, 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 final : public INetworkListManagerEvents +class CNetworkListManagerEvent : public INetworkListManagerEvents { public: CNetworkListManagerEvent() : m_ref(1) { } @@ -23,15 +23,17 @@ 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 { - return E_NOINTERFACE; + Result = E_NOINTERFACE; } AddRef(); - return S_OK; + + return Result; } ULONG STDMETHODCALLTYPE AddRef() diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 057a1edd..d4ba0d76 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -21,7 +21,7 @@ BOOL I2PService::isService() HWINSTA hWinStation = GetProcessWindowStation(); if (hWinStation != NULL) { - USEROBJECTFLAGS uof = { FALSE, FALSE, 0 }; + USEROBJECTFLAGS uof = { 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(eLogCritical, "Win32Service: Start error: ", dwError); + LogPrint(eLogError, "Win32Service: Start error: ", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { - LogPrint(eLogCritical, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_STOPPED); } } @@ -162,7 +162,7 @@ void I2PService::Stop() } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } @@ -191,12 +191,12 @@ void I2PService::Pause() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Pause error: ", dwError); + LogPrint(eLogError, "Win32Service: Pause error: ", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } } @@ -215,12 +215,12 @@ void I2PService::Continue() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Continue error: ", dwError); + LogPrint(eLogError, "Win32Service: Continue error: ", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } } @@ -238,11 +238,11 @@ void I2PService::Shutdown() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Shutdown error: ", dwError); + LogPrint(eLogError, "Win32Service: Shutdown error: ", dwError); } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); } } diff --git a/Win32/mask.bmp b/Win32/mask.bmp new file mode 100644 index 00000000..cc2aeda7 Binary files /dev/null and b/Win32/mask.bmp differ diff --git a/build/.gitignore b/build/.gitignore index 39b8094c..872332c5 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,13 +1,6 @@ # Various generated files /CMakeFiles/ -/Testing/ -/tests/ -/.ninja_* -/arch.c -/build.ninja /i2pd -/i2pd.exe -/i2pd.exe.debug /libi2pd.a /libi2pdclient.a /libi2pdlang.a @@ -15,13 +8,8 @@ /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake -/CTestTestfile.cmake /install_manifest.txt -/Makefile +/arch.c # 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 bc936e18..cf0416cd 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,64 +1,46 @@ cmake_minimum_required(VERSION 3.7) - -if(${CMAKE_VERSION} VERSION_LESS 3.22) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.22) -endif() +cmake_policy(VERSION 3.7) +project("i2pd") # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) -# 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 -) +# 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() # 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) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) -option(WITH_GIT_VERSION "Use git commit info as version" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) -option(BUILD_TESTING "Build tests" OFF) -IF(BUILD_TESTING) - enable_testing() -ENDIF() +# paths +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") +set(CMAKE_SOURCE_DIR "..") -# Handle paths nicely +#Handle paths nicely include(GNUInstallDirs) -# Architecture +# architecture include(TargetArch) target_architecture(ARCHITECTURE) -include(CheckAtomic) - -if(WITH_STATIC) - if(MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - endif() -endif() +set(LIBI2PD_SRC_DIR ../libi2pd) +set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) +set(LANG_SRC_DIR ../i18n) +set(DAEMON_SRC_DIR ../daemon) 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 "") @@ -69,9 +51,11 @@ 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 "") @@ -84,7 +68,6 @@ 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 "") @@ -97,64 +80,39 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() -include_directories(${DAEMON_SRC_DIR}) - set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" - "${DAEMON_SRC_DIR}/I2PControlHandlers.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) -if(WIN32) - set(WIN32_SRC_DIR ${CMAKE_SOURCE_DIR}/Win32) - include_directories(${WIN32_SRC_DIR}) - - list(APPEND DAEMON_SRC - "${WIN32_SRC_DIR}/DaemonWin32.cpp" - "${WIN32_SRC_DIR}/Win32App.cpp" - "${WIN32_SRC_DIR}/Win32Service.cpp" - "${WIN32_SRC_DIR}/Win32NetState.cpp" - ) - - file(GLOB WIN32_RC ${WIN32_SRC_DIR}/*.rc) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_APP -DWIN32_LEAN_AND_MEAN -DNOMINMAX") - -endif() - if(WITH_UPNP) add_definitions(-DUSE_UPNP) endif() -if(WITH_GIT_VERSION) - include(GetGitRevisionDescription) - git_describe(GIT_VERSION) - add_definitions(-DGITVER=${GIT_VERSION}) -endif() - if(APPLE) add_definitions(-DMAC_OSX) endif() -if(HAIKU) - add_definitions(-D_DEFAULT_SOURCE -D_GNU_SOURCE) -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(MSVC) - add_definitions(-DWINVER=0x0600) - add_definitions(-D_WIN32_WINNT=0x0600) +# check for c++17 & c++11 support +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) +CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) +if(CXX17_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") +elseif(CXX11_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") else() - 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 + message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -184,6 +142,13 @@ 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") @@ -198,52 +163,27 @@ if(WITH_THREADSANITIZER) endif() endif() -if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 - list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") -endif() - -# Use std::atomic instead of GCC builtins on macOS PowerPC: -# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 -# This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. -if(APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc") - add_definitions(-DBOOST_SP_USE_STD_ATOMIC) -endif() # libraries +# TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 +# use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) +if(IOS) + set(CMAKE_THREAD_LIBS_INIT "-lpthread") + set(CMAKE_HAVE_THREADS_LIBRARY 1) + set(CMAKE_USE_WIN32_THREADS_INIT 0) + set(CMAKE_USE_PTHREADS_INIT 1) +else() + find_package(Threads REQUIRED) +endif() +if(THREADS_HAVE_PTHREAD_ARG) # compile time flag + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif() if(WITH_STATIC) - if(NOT MSVC) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - endif() - set(Boost_USE_STATIC_LIBS 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(Boost_USE_STATIC_RUNTIME ON) 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") @@ -253,28 +193,24 @@ 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 - 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() + 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) endif() -find_package(Boost REQUIRED COMPONENTS system filesystem program_options) -if(NOT DEFINED Boost_FOUND) +target_link_libraries(libi2pdclient libi2pd libi2pdlang) + +find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) +if(NOT DEFINED Boost_INCLUDE_DIRS) 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_FOUND) +if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() if(OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") - add_definitions(-DOPENSSL_SUPPRESS_DEPRECATED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") endif() if(WITH_UPNP) @@ -291,29 +227,11 @@ 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}") @@ -321,72 +239,48 @@ 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) - 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() + add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) if(WITH_STATIC) - if(NOT MSVC) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") - endif() + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") endif() if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now") endif() + if(WITH_UPNP) + set(UPNP_LIB ${MINIUPNPC_LIBRARY}) + endif() + # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") 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 ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) + target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") endif() - -if(BUILD_TESTING) - add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests) -endif() diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index e8bb2b84..6540f833 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -52,28 +52,22 @@ REM converting configuration files to DOS format (make usable in Windows Notepad %xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/* contrib/webconsole/style.css" >> build\build.log 2>&1 REM Prepare binary signing command if signing key and password provided -if defined SIGN ( - echo Signing enabled - - for %%X in (signtool.exe) do (set xSIGNTOOL=%%~$PATH:X) - if not defined xSIGNTOOL ( - if not defined SIGNTOOL ( - echo Error: Can't find signtool. Please provide path to binary using SIGNTOOL variable. - exit /b 1 - ) else ( - set "xSIGNTOOL=%SIGNTOOL%" - ) - ) - - if defined SIGNKEY ( - set "xSIGNKEYOPTS=/f ^"%SIGNKEY%^"" - ) - +if defined SIGNKEY ( if defined SIGNPASS ( - set "xSIGNPASSOPTS=/p ^"%SIGNPASS%^"" - ) + echo Signing options found - set "xSIGNOPTS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 %xSIGNKEYOPTS% %xSIGNPASSOPTS%" + for %%X in (signtool.exe) do (set xSIGNTOOL=%%~$PATH:X) + if not defined xSIGNTOOL ( + if not defined SIGNTOOL ( + echo Error: Can't find signtool. Please provide path to binary using SIGNTOOL variable. + exit /b 1 + ) else ( + set "xSIGNTOOL=%SIGNTOOL%" + ) + ) + + set "xSIGNOPTS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /f ^"%SIGNKEY%^" /p ^"%SIGNPASS%^"" + ) ) REM starting building diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake index 4954e3e5..b8296a1c 100644 --- a/build/cmake_modules/CheckAtomic.cmake +++ b/build/cmake_modules/CheckAtomic.cmake @@ -1,23 +1,18 @@ # atomic builtins are required for threading support. INCLUDE(CheckCXXSourceCompiles) -INCLUDE(CheckLibraryExists) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17") + set(CMAKE_REQUIRED_FLAGS "-std=c++11") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; -std::atomic y; -std::atomic z; int main() { - ++z; - ++y; - return ++x; + return x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) @@ -25,14 +20,13 @@ 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++17 ${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); - (void)i; return 0; } " ${varname}) @@ -40,16 +34,15 @@ int main() { endfunction(check_working_cxx_atomics64) -# Check for (non-64-bit) atomic operations. -if(MSVC) - set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) -else() +# This isn't necessary on MSVC, so avoid command-line switch annoyance +# by only running on GCC-like hosts. +if (LLVM_COMPILER_IS_GCC_COMPATIBLE) # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) - if(HAVE_LIBATOMIC) + if( HAVE_LIBATOMIC ) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) @@ -65,20 +58,20 @@ endif() if(MSVC) set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) else() - # First check if atomics work without the library. check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) - # If not, check if the library exists, and atomics work with it. - if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) - check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) - if(HAVE_CXX_LIBATOMICS64) - list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") - check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) - if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) - message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!") - endif() - else() - message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.") +endif() + +# If not, check if the library exists, and atomics work with it. +if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) + if(HAVE_CXX_LIBATOMICS64) + list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) + if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) + message(FATAL_ERROR "Host compiler must support std::atomic!") endif() + else() + message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() @@ -87,6 +80,7 @@ endif() ## assumes C++11 works. CHECK_CXX_SOURCE_COMPILES(" #ifdef _MSC_VER +#include /* Workaround for PR19898. */ #include #endif int main() { diff --git a/build/cmake_modules/FindCheck.cmake b/build/cmake_modules/FindCheck.cmake deleted file mode 100644 index 8ad818f4..00000000 --- a/build/cmake_modules/FindCheck.cmake +++ /dev/null @@ -1,55 +0,0 @@ -# - Try to find the CHECK libraries -# Once done this will define -# -# CHECK_FOUND - system has check -# CHECK_INCLUDE_DIRS - the check include directory -# CHECK_LIBRARIES - check library -# -# Copyright (c) 2007 Daniel Gollub -# Copyright (c) 2007-2009 Bjoern Ricks -# -# Redistribution and use is allowed according to the terms of the New -# BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - - -INCLUDE( FindPkgConfig ) - -IF ( Check_FIND_REQUIRED ) - SET( _pkgconfig_REQUIRED "REQUIRED" ) -ELSE( Check_FIND_REQUIRED ) - SET( _pkgconfig_REQUIRED "" ) -ENDIF ( Check_FIND_REQUIRED ) - -IF ( CHECK_MIN_VERSION ) - PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check>=${CHECK_MIN_VERSION} ) -ELSE ( CHECK_MIN_VERSION ) - PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check ) -ENDIF ( CHECK_MIN_VERSION ) - -# Look for CHECK include dir and libraries -IF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) - - FIND_PATH( CHECK_INCLUDE_DIRS check.h ) - - FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) - - IF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) - SET( CHECK_FOUND 1 ) - IF ( NOT Check_FIND_QUIETLY ) - MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) - ENDIF ( NOT Check_FIND_QUIETLY ) - ELSE ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) - IF ( Check_FIND_REQUIRED ) - MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) - ELSE ( Check_FIND_REQUIRED ) - IF ( NOT Check_FIND_QUIETLY ) - MESSAGE( STATUS "Could NOT find CHECK" ) - ENDIF ( NOT Check_FIND_QUIETLY ) - ENDIF ( Check_FIND_REQUIRED ) - ENDIF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) -ENDIF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) - -# Hide advanced variables from CMake GUIs -MARK_AS_ADVANCED( CHECK_INCLUDE_DIRS CHECK_LIBRARIES ) - diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake b/build/cmake_modules/GetGitRevisionDescription.cmake deleted file mode 100644 index a08895c6..00000000 --- a/build/cmake_modules/GetGitRevisionDescription.cmake +++ /dev/null @@ -1,284 +0,0 @@ -# - Returns a version string from Git -# -# These functions force a re-configure on each git commit so that you can -# trust the values of the variables in your build system. -# -# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) -# -# Returns the refspec and sha hash of the current head revision -# -# git_describe( [ ...]) -# -# Returns the results of git describe on the source tree, and adjusting -# the output so that it tests false if an error occurs. -# -# git_describe_working_tree( [ ...]) -# -# Returns the results of git describe on the working tree (--dirty option), -# and adjusting the output so that it tests false if an error occurs. -# -# git_get_exact_tag( [ ...]) -# -# Returns the results of git describe --exact-match on the source tree, -# and adjusting the output so that it tests false if there was no exact -# matching tag. -# -# git_local_changes() -# -# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. -# Uses the return code of "git diff-index --quiet HEAD --". -# Does not regard untracked files. -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2020 Ryan Pavlik -# http://academic.cleardefinition.com -# -# Copyright 2009-2013, Iowa State University. -# Copyright 2013-2020, Ryan Pavlik -# Copyright 2013-2020, Contributors -# SPDX-License-Identifier: BSL-1.0 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__get_git_revision_description) - return() -endif() -set(__get_git_revision_description YES) - -# We must run the following at "include" time, not at function call time, -# to find the path to this module rather than the path to a calling list file -get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) - -# Function _git_find_closest_git_dir finds the next closest .git directory -# that is part of any directory in the path defined by _start_dir. -# The result is returned in the parent scope variable whose name is passed -# as variable _git_dir_var. If no .git directory can be found, the -# 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 will return -# C:/bla/.git -# -function(_git_find_closest_git_dir _start_dir _git_dir_var) - set(cur_dir "${_start_dir}") - set(git_dir "${_start_dir}/.git") - while(NOT EXISTS "${git_dir}") - # .git dir not found, search parent directories - set(git_previous_parent "${cur_dir}") - get_filename_component(cur_dir "${cur_dir}" DIRECTORY) - if(cur_dir STREQUAL git_previous_parent) - # We have reached the root directory, we are not in git - set(${_git_dir_var} - "" - PARENT_SCOPE) - return() - endif() - set(git_dir "${cur_dir}/.git") - endwhile() - set(${_git_dir_var} - "${git_dir}" - PARENT_SCOPE) -endfunction() - -function(get_git_head_revision _refspecvar _hashvar) - _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) - - if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") - set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) - else() - set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) - endif() - if(NOT "${GIT_DIR}" STREQUAL "") - file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" - "${GIT_DIR}") - if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) - # We've gone above the CMake root dir. - set(GIT_DIR "") - endif() - endif() - if("${GIT_DIR}" STREQUAL "") - set(${_refspecvar} - "GITDIR-NOTFOUND" - PARENT_SCOPE) - set(${_hashvar} - "GITDIR-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - # Check if the current source dir is a git submodule or a worktree. - # In both cases .git is a file instead of a directory. - # - if(NOT IS_DIRECTORY ${GIT_DIR}) - # The following git command will return a non empty string that - # points to the super project working tree if the current - # source dir is inside a git submodule. - # Otherwise the command will return an empty string. - # - execute_process( - COMMAND "${GIT_EXECUTABLE}" rev-parse - --show-superproject-working-tree - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT "${out}" STREQUAL "") - # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule - file(READ ${GIT_DIR} submodule) - string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE - ${submodule}) - string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) - get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) - get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} - ABSOLUTE) - set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") - else() - # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree - file(READ ${GIT_DIR} worktree_ref) - # The .git directory contains a path to the worktree information directory - # inside the parent git repo of the worktree. - # - string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir - ${worktree_ref}) - string(STRIP ${git_worktree_dir} git_worktree_dir) - _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) - set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") - endif() - else() - set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") - endif() - set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") - if(NOT EXISTS "${GIT_DATA}") - file(MAKE_DIRECTORY "${GIT_DATA}") - endif() - - if(NOT EXISTS "${HEAD_SOURCE_FILE}") - return() - endif() - set(HEAD_FILE "${GIT_DATA}/HEAD") - configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) - - configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" - "${GIT_DATA}/grabRef.cmake" @ONLY) - include("${GIT_DATA}/grabRef.cmake") - - set(${_refspecvar} - "${HEAD_REF}" - PARENT_SCOPE) - set(${_hashvar} - "${HEAD_HASH}" - PARENT_SCOPE) -endfunction() - -function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} - "HEAD-HASH-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - # TODO sanitize - #if((${ARGN}" MATCHES "&&") OR - # (ARGN MATCHES "||") OR - # (ARGN MATCHES "\\;")) - # message("Please report the following error to the project!") - # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") - #endif() - - #message(STATUS "Arguments to execute_process: ${ARGN}") - - execute_process( - COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_describe_working_tree _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_get_exact_tag _var) - git_describe(out --exact-match ${ARGN}) - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_local_changes _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} - "HEAD-HASH-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(res EQUAL 0) - set(${_var} - "CLEAN" - PARENT_SCOPE) - else() - set(${_var} - "DIRTY" - PARENT_SCOPE) - endif() -endfunction() diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake.in b/build/cmake_modules/GetGitRevisionDescription.cmake.in deleted file mode 100644 index 116efc4e..00000000 --- a/build/cmake_modules/GetGitRevisionDescription.cmake.in +++ /dev/null @@ -1,43 +0,0 @@ -# -# Internal file for GetGitRevisionDescription.cmake -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright 2009-2012, Iowa State University -# Copyright 2011-2015, Contributors -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) -# SPDX-License-Identifier: BSL-1.0 - -set(HEAD_HASH) - -file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) - -string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) -if(HEAD_CONTENTS MATCHES "ref") - # named branch - string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - else() - configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) - file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) - if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") - set(HEAD_HASH "${CMAKE_MATCH_1}") - endif() - endif() -else() - # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) -endif() - -if(NOT HEAD_HASH) - file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) - string(STRIP "${HEAD_HASH}" HEAD_HASH) -endif() diff --git a/build/cmake_modules/TargetArch.cmake b/build/cmake_modules/TargetArch.cmake index e611c4f7..d9ebe7c9 100644 --- a/build/cmake_modules/TargetArch.cmake +++ b/build/cmake_modules/TargetArch.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2023, The PurpleI2P Project +# Copyright (c) 2017-2022, The PurpleI2P Project # This file is part of Purple i2pd project and licensed under BSD3 # See full license text in LICENSE file at top of project tree @@ -18,7 +18,7 @@ set(archdetect_c_code " || defined(_M_ARM64) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 8) #error cmake_ARCH arm64 - #elif defined(__ARM_ARCH_7__) \\ + #if defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ @@ -61,7 +61,7 @@ set(archdetect_c_code " #else #error cmake_ARCH mips #endif -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) || defined(__POWERPC__) \\ +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) @@ -83,13 +83,13 @@ function(target_architecture output_var) # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using - # the OS X SDK version 10.7 or later - you'll need 10.4/10.5/10.6 for that, so we - # disable it by default. Also, ppc64 is not supported in 10.6. + # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we + # disable it by default # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. - # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise; 10.6 also supports ppc. + # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) @@ -133,11 +133,11 @@ function(target_architecture output_var) enable_language(C) # Detect the architecture in a rather creative way... - # This compiles a small C program which is a series of ifdefs that selects - # a particular #error preprocessor directive whose message string contains - # the target architecture. The program will always fail to compile (both because - # file is not a valid C program, and obviously because of the presence of - # the #error preprocessor directives... but by exploiting the preprocessor in this + # This compiles a small C program which is a series of ifdefs that selects a + # particular #error preprocessor directive whose message string contains the + # target architecture. The program will always fail to compile (both because + # file is not a valid C program, and obviously because of the presence of the + # #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( diff --git a/build/cmake_modules/Version.cmake b/build/cmake_modules/Version.cmake deleted file mode 100644 index cb9551db..00000000 --- a/build/cmake_modules/Version.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# 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 a4b67ad2..cfeff812 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-2024, The PurpleI2P Project +AppCopyright=Copyright (c) 2013-2022, 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 deleted file mode 100644 index 8e34298a..00000000 --- a/contrib/apparmor/docker-i2pd +++ /dev/null @@ -1,42 +0,0 @@ -# _________________________________________ -# / 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.bin.i2pd b/contrib/apparmor/usr.sbin.i2pd similarity index 85% rename from contrib/apparmor/usr.bin.i2pd rename to contrib/apparmor/usr.sbin.i2pd index 4d370f3c..1e47cd74 100644 --- a/contrib/apparmor/usr.bin.i2pd +++ b/contrib/apparmor/usr.sbin.i2pd @@ -4,7 +4,7 @@ # #include -profile i2pd /{usr/,}bin/i2pd { +profile i2pd /{usr/,}sbin/i2pd { #include #include #include @@ -14,12 +14,12 @@ profile i2pd /{usr/,}bin/i2pd { /var/lib/i2pd/** rw, /var/log/i2pd/i2pd.log w, /{var/,}run/i2pd/i2pd.pid rwk, - /{usr/,}bin/i2pd mr, + /{usr/,}sbin/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 deleted file mode 100644 index ae44521b..00000000 --- a/contrib/certificates/reseed/admin_at_stormycloud.org.crt +++ /dev/null @@ -1,34 +0,0 @@ ------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 new file mode 100644 index 00000000..a332805a --- /dev/null +++ b/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----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 new file mode 100644 index 00000000..d0ff7c33 --- /dev/null +++ b/contrib/certificates/reseed/hottuna_at_mail.i2p.crt @@ -0,0 +1,33 @@ +-----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 c6a05e8b..12ce7a61 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----- -MIIFvjCCA6agAwIBAgIQBnsUOmOu2oZZIwHBmQc1BDANBgkqhkiG9w0BAQsFADBt +MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN -aWdvckBub3ZnLm5ldDAeFw0yMzAxMjgxNDM4MzFaFw0zMzAxMjgxNDM4MzFaMG0x +aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p -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/ +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/ BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC -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== +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== -----END CERTIFICATE----- diff --git a/contrib/debian/README b/contrib/debian/README index 7dc3a61f..cccbc4de 100644 --- a/contrib/debian/README +++ b/contrib/debian/README @@ -1,5 +1,2 @@ -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. +This forder contain systemd unit files. +To use systemd daemon control, place files from this directory to debian folder before building package. diff --git a/contrib/debian/bionic/compat b/contrib/debian/bionic/compat deleted file mode 100644 index b4de3947..00000000 --- a/contrib/debian/bionic/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/contrib/debian/bionic/control b/contrib/debian/bionic/control deleted file mode 100644 index d872881c..00000000 --- a/contrib/debian/bionic/control +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index ec635144..00000000 --- a/contrib/debian/trusty/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/contrib/debian/trusty/control b/contrib/debian/trusty/control deleted file mode 100644 index fc618ddc..00000000 --- a/contrib/debian/trusty/control +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 74d36c06..00000000 --- a/contrib/debian/trusty/patches/01-upnp.patch +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 546e252f..00000000 --- a/contrib/debian/trusty/patches/02-service.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index d8caec41..00000000 --- a/contrib/debian/trusty/patches/series +++ /dev/null @@ -1,2 +0,0 @@ -01-upnp.patch -02-service.patch diff --git a/contrib/debian/trusty/rules b/contrib/debian/trusty/rules deleted file mode 100755 index 97a4e008..00000000 --- a/contrib/debian/trusty/rules +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 deleted file mode 100644 index ec635144..00000000 --- a/contrib/debian/xenial/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/contrib/debian/xenial/control b/contrib/debian/xenial/control deleted file mode 100644 index fc618ddc..00000000 --- a/contrib/debian/xenial/control +++ /dev/null @@ -1,18 +0,0 @@ -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/02-service.patch b/contrib/debian/xenial/patches/02-service.patch deleted file mode 100644 index 546e252f..00000000 --- a/contrib/debian/xenial/patches/02-service.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index d8caec41..00000000 --- a/contrib/debian/xenial/patches/series +++ /dev/null @@ -1,2 +0,0 @@ -01-upnp.patch -02-service.patch diff --git a/contrib/debian/xenial/rules b/contrib/debian/xenial/rules deleted file mode 100755 index 11791d9b..00000000 --- a/contrib/debian/xenial/rules +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 deleted file mode 100644 index 63d877c1..00000000 --- a/contrib/dinit/i2pd +++ /dev/null @@ -1,8 +0,0 @@ -type = bgprocess -run-as = i2pd -command = /usr/bin/i2pd --conf=/var/lib/i2pd/i2pd.conf --pidfile=/var/lib/i2pd/i2pd.pid --daemon --service -smooth-recovery = true -depends-on = ntpd -# uncomment if you want to use i2pd with yggdrasil -# depends-on = yggdrasil -pid-file = /var/lib/i2pd/i2pd.pid diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index ead21f10..71af141e 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,18 +1,5 @@ -# -# Copyright (c) 2017-2022, The PurpleI2P Project -# -# This file is part of Purple i2pd project and licensed under BSD3 -# -# See full license text in LICENSE file at top of project tree -# - FROM alpine:latest -LABEL authors="Mikal Villa , Darknet Villain " -LABEL maintainer="R4SAS " - -LABEL org.opencontainers.image.source=https://github.com/PurpleI2P/i2pd -LABEL org.opencontainers.image.documentation=https://i2pd.readthedocs.io/en/latest/ -LABEL org.opencontainers.image.licenses=BSD3 +LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="openssl" @@ -24,28 +11,27 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" -ENV DEFAULT_ARGS=" --datadir=$DATA_DIR" +ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" - -# 1. Building binary -# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the -# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. # -# 1. install deps, clone and build. -# 2. strip binaries. -# 3. Purge all dependencies and other unrelated packages, including build directory. +# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the +# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. +# +# 1. install deps, clone and build. +# 2. strip binaries. +# 3. Purge all dependencies and other unrelated packages, including build directory. RUN apk update \ && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && make -j$(nproc) USE_UPNP=yes \ + && make USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ @@ -59,9 +45,6 @@ RUN apk update \ # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ -# 3. Copy preconfigured config file and entrypoint -COPY i2pd-docker.conf "$DATA_DIR/i2pd.conf" -RUN chown i2pd:nobody "$DATA_DIR/i2pd.conf" COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh diff --git a/contrib/docker/i2pd-docker.conf b/contrib/docker/i2pd-docker.conf deleted file mode 100644 index 3b91a235..00000000 --- a/contrib/docker/i2pd-docker.conf +++ /dev/null @@ -1,52 +0,0 @@ -## Preconfigured i2pd configuration file for a Docker container -## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ -## for more options you can use in this file. - -## Note that for exposing ports outside of container you need to bind all services to 0.0.0.0 - -log = file -loglevel = none - -ipv4 = true -ipv6 = false - -# bandwidth = L -# notransit = false -# floodfill = false - -[ntcp2] -enabled = true -published = true - -[ssu2] -enabled = true -published = true - -[http] -enabled = true -address = 0.0.0.0 -port = 7070 - -[httpproxy] -enabled = true -address = 0.0.0.0 -port = 4444 - -[socksproxy] -enabled = true -address = 0.0.0.0 -port = 4447 - -[sam] -enabled = true -address = 0.0.0.0 -port = 7656 - -[upnp] -enabled = false - -[reseed] -verify = true - -[limits] -# transittunnels = 2500 diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 7782187f..25378f82 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -1,13 +1,13 @@ # i2pd -# Copyright (C) 2021-2023 PurpleI2P team +# Copyright (C) 2021 PurpleI2P team # This file is distributed under the same license as the i2pd package. -# R4SAS , 2021-2023. +# R4SAS , 2021. # msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2023-06-10 01:25\n" +"POT-Creation-Date: 2021-08-06 17:12\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -18,766 +18,706 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:107 -#, c-format -msgid "%d day" -msgid_plural "%d days" +#: daemon/HTTPServer.cpp:177 +msgid "day" +msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:111 -#, c-format -msgid "%d hour" -msgid_plural "%d hours" +#: daemon/HTTPServer.cpp:181 +msgid "hour" +msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:115 -#, c-format -msgid "%d minute" -msgid_plural "%d minutes" +#: daemon/HTTPServer.cpp:185 +msgid "minute" +msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:118 -#, c-format -msgid "%d second" -msgid_plural "%d seconds" +#: daemon/HTTPServer.cpp:188 +msgid "second" +msgid_plural "seconds" msgstr[0] "" msgstr[1] "" -#. tr: Kibibyte -#: daemon/HTTPServer.cpp:126 -#, c-format -msgid "%.2f KiB" +#. tr: Kibibit +#: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 +msgid "KiB" msgstr "" -#. tr: Mebibyte -#: daemon/HTTPServer.cpp:128 -#, c-format -msgid "%.2f MiB" +#. tr: Mebibit +#: daemon/HTTPServer.cpp:198 +msgid "MiB" msgstr "" -#. tr: Gibibyte -#: daemon/HTTPServer.cpp:130 -#, c-format -msgid "%.2f GiB" +#. tr: Gibibit +#: daemon/HTTPServer.cpp:200 +msgid "GiB" msgstr "" -#: daemon/HTTPServer.cpp:147 +#: daemon/HTTPServer.cpp:217 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:148 +#: daemon/HTTPServer.cpp:218 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:149 +#: daemon/HTTPServer.cpp:219 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:150 +#: daemon/HTTPServer.cpp:220 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:151 +#: daemon/HTTPServer.cpp:221 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:153 +#: daemon/HTTPServer.cpp:223 msgid "exploratory" msgstr "" -#. tr: Webconsole page title -#: daemon/HTTPServer.cpp:185 -msgid "Purple I2P Webconsole" -msgstr "" - -#: daemon/HTTPServer.cpp:190 +#: daemon/HTTPServer.cpp:259 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:193 +#: daemon/HTTPServer.cpp:262 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:742 +#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:395 -#: daemon/HTTPServer.cpp:407 +#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 +#: daemon/HTTPServer.cpp:460 msgid "Local Destinations" msgstr "" -#: 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 +#: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:418 +#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 +#: daemon/HTTPServer.cpp:641 daemon/HTTPServer.cpp:684 +#: daemon/HTTPServer.cpp:688 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:692 +#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:372 -#: daemon/HTTPServer.cpp:813 daemon/HTTPServer.cpp:830 +#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 +#: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:898 +#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:204 +#: daemon/HTTPServer.cpp:271 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:927 -#: daemon/HTTPServer.cpp:937 +#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 +#: daemon/HTTPServer.cpp:924 msgid "SAM sessions" msgstr "" -#: 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 +#: daemon/HTTPServer.cpp:289 daemon/HTTPServer.cpp:1306 +#: daemon/HTTPServer.cpp:1309 daemon/HTTPServer.cpp:1312 +#: daemon/HTTPServer.cpp:1326 daemon/HTTPServer.cpp:1371 +#: daemon/HTTPServer.cpp:1374 daemon/HTTPServer.cpp:1377 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:229 +#: daemon/HTTPServer.cpp:296 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:230 +#: daemon/HTTPServer.cpp:297 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:231 +#: daemon/HTTPServer.cpp:298 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:235 -#: daemon/HTTPServer.cpp:336 +#: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 +#: daemon/HTTPServer.cpp:406 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:382 -#: daemon/HTTPServer.cpp:383 daemon/HTTPServer.cpp:1003 -#: daemon/HTTPServer.cpp:1011 +#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 +#: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 +#: daemon/HTTPServer.cpp:991 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:234 +#: daemon/HTTPServer.cpp:301 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:242 +#: daemon/HTTPServer.cpp:304 +msgid "Error" +msgstr "" + +#: daemon/HTTPServer.cpp:308 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:245 +#: daemon/HTTPServer.cpp:311 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:248 +#: daemon/HTTPServer.cpp:314 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:251 -msgid "Full cone NAT" -msgstr "" - -#: daemon/HTTPServer.cpp:254 -msgid "No Descriptors" -msgstr "" - -#: daemon/HTTPServer.cpp:263 +#: daemon/HTTPServer.cpp:326 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:266 +#: daemon/HTTPServer.cpp:329 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:271 +#: daemon/HTTPServer.cpp:334 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:277 daemon/HTTPServer.cpp:284 +#: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:291 +#: daemon/HTTPServer.cpp:354 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:292 +#: daemon/HTTPServer.cpp:355 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:296 -msgid "Total tunnel creation success rate" -msgstr "" - -#: daemon/HTTPServer.cpp:298 +#: daemon/HTTPServer.cpp:356 msgid "Received" msgstr "" -#. tr: Kibibyte/s -#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:303 -#: daemon/HTTPServer.cpp:306 -#, c-format -msgid "%.2f KiB/s" +#. tr: Kibibit/s +#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 +#: daemon/HTTPServer.cpp:364 +msgid "KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:301 +#: daemon/HTTPServer.cpp:359 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:304 +#: daemon/HTTPServer.cpp:362 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:307 +#: daemon/HTTPServer.cpp:365 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:310 +#: daemon/HTTPServer.cpp:368 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:314 +#: daemon/HTTPServer.cpp:371 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:316 +#: daemon/HTTPServer.cpp:373 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:317 +#: daemon/HTTPServer.cpp:374 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:318 +#: daemon/HTTPServer.cpp:375 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:319 +#: daemon/HTTPServer.cpp:376 msgid "Our external address" msgstr "" -#. tr: Shown when router doesn't publish itself and have "Firewalled" state -#: daemon/HTTPServer.cpp:349 +#: daemon/HTTPServer.cpp:384 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:363 +#: daemon/HTTPServer.cpp:416 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:364 +#: daemon/HTTPServer.cpp:417 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:987 +#: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:381 +#: daemon/HTTPServer.cpp:434 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:382 daemon/HTTPServer.cpp:383 -#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 -#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 +#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 +#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Enabled" msgstr "" -#: daemon/HTTPServer.cpp:382 daemon/HTTPServer.cpp:383 -#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 -#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 +#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 +#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Disabled" msgstr "" -#: daemon/HTTPServer.cpp:434 +#: daemon/HTTPServer.cpp:483 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:442 +#: daemon/HTTPServer.cpp:492 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:447 +#: daemon/HTTPServer.cpp:497 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:448 +#: daemon/HTTPServer.cpp:498 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:449 +#: daemon/HTTPServer.cpp:499 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:457 +#: daemon/HTTPServer.cpp:505 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:459 +#: daemon/HTTPServer.cpp:505 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:460 +#: daemon/HTTPServer.cpp:505 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:467 -msgid "Expire LeaseSet" -msgstr "" - -#: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:697 +#: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds -#: daemon/HTTPServer.cpp:494 daemon/HTTPServer.cpp:514 -#: daemon/HTTPServer.cpp:711 daemon/HTTPServer.cpp:731 -#, c-format -msgid "%dms" +#: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 +#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 +msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:716 +#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:521 +#: daemon/HTTPServer.cpp:537 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:522 +#: daemon/HTTPServer.cpp:537 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:535 +#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:551 +#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:814 +#: daemon/HTTPServer.cpp:545 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:540 +#: daemon/HTTPServer.cpp:552 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:548 daemon/HTTPServer.cpp:554 +#: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:551 +#: daemon/HTTPServer.cpp:561 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:561 daemon/HTTPServer.cpp:621 +#: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:572 daemon/HTTPServer.cpp:960 +#: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:595 +#: daemon/HTTPServer.cpp:602 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:613 daemon/HTTPServer.cpp:1430 -msgid "Such destination is not found" -msgstr "" - -#: daemon/HTTPServer.cpp:626 +#: daemon/HTTPServer.cpp:631 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:629 +#: daemon/HTTPServer.cpp:634 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:658 +#: daemon/HTTPServer.cpp:660 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:661 +#: daemon/HTTPServer.cpp:663 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:662 +#: daemon/HTTPServer.cpp:664 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:667 +#: daemon/HTTPServer.cpp:669 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:670 +#: daemon/HTTPServer.cpp:672 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:671 +#: daemon/HTTPServer.cpp:673 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:672 +#: daemon/HTTPServer.cpp:674 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:682 -msgid "floodfill mode is disabled" +#: daemon/HTTPServer.cpp:684 +msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:693 +#: daemon/HTTPServer.cpp:695 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:743 +#: daemon/HTTPServer.cpp:726 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:744 -msgid "Reload tunnels configuration" -msgstr "" - -#: daemon/HTTPServer.cpp:747 +#: daemon/HTTPServer.cpp:731 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:749 +#: daemon/HTTPServer.cpp:733 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:758 +#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:755 daemon/HTTPServer.cpp:760 +#: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:763 +#: daemon/HTTPServer.cpp:747 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:764 +#: daemon/HTTPServer.cpp:748 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:767 +#: daemon/HTTPServer.cpp:751 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:770 +#: daemon/HTTPServer.cpp:753 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:779 +#: daemon/HTTPServer.cpp:761 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:784 daemon/HTTPServer.cpp:803 +#: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:791 +#: daemon/HTTPServer.cpp:770 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:830 +#: daemon/HTTPServer.cpp:803 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:921 daemon/HTTPServer.cpp:944 +#: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:937 +#: daemon/HTTPServer.cpp:924 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:950 +#: daemon/HTTPServer.cpp:937 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:955 +#: daemon/HTTPServer.cpp:942 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:1020 +#: daemon/HTTPServer.cpp:999 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:1036 +#: daemon/HTTPServer.cpp:1015 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1050 +#: daemon/HTTPServer.cpp:1029 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1250 +#: daemon/HTTPServer.cpp:1227 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1269 +#: daemon/HTTPServer.cpp:1246 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1359 -#: daemon/HTTPServer.cpp:1414 daemon/HTTPServer.cpp:1454 +#: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1401 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1327 +#: daemon/HTTPServer.cpp:1304 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1329 +#: daemon/HTTPServer.cpp:1306 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1365 +#: daemon/HTTPServer.cpp:1309 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1335 +#: daemon/HTTPServer.cpp:1312 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1337 daemon/HTTPServer.cpp:1367 -#: daemon/HTTPServer.cpp:1432 +#: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 msgid "Return to destination page" msgstr "" -#: 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" +#: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 +#: daemon/HTTPServer.cpp:1403 +msgid "You will be redirected in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1359 -msgid "LeaseSet expiration time updated" +#: daemon/HTTPServer.cpp:1326 +msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1362 -msgid "LeaseSet is not found or already expired" -msgstr "" - -#: daemon/HTTPServer.cpp:1379 -#, c-format -msgid "Transit tunnels count must not exceed %d" -msgstr "" - -#: daemon/HTTPServer.cpp:1380 daemon/HTTPServer.cpp:1455 +#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1416 +#: daemon/HTTPServer.cpp:1363 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1417 +#: daemon/HTTPServer.cpp:1364 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1417 +#: daemon/HTTPServer.cpp:1364 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1418 +#: daemon/HTTPServer.cpp:1365 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1424 +#: daemon/HTTPServer.cpp:1371 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1427 +#: daemon/HTTPServer.cpp:1374 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1450 +#: daemon/HTTPServer.cpp:1377 +msgid "Such destination is not found" +msgstr "" + +#: daemon/HTTPServer.cpp:1397 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1454 +#: daemon/HTTPServer.cpp:1401 msgid "Command accepted" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:166 +#: libi2pd_client/HTTPProxy.cpp:157 msgid "Proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:174 +#: libi2pd_client/HTTPProxy.cpp:165 msgid "Proxy info" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:182 +#: libi2pd_client/HTTPProxy.cpp:173 msgid "Proxy error: Host not found" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:183 +#: libi2pd_client/HTTPProxy.cpp:174 msgid "Remote host not found in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:184 +#: libi2pd_client/HTTPProxy.cpp:175 msgid "You may try to find this host on jump services below" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:333 libi2pd_client/HTTPProxy.cpp:348 -#: libi2pd_client/HTTPProxy.cpp:417 libi2pd_client/HTTPProxy.cpp:460 +#: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 +#: libi2pd_client/HTTPProxy.cpp:322 libi2pd_client/HTTPProxy.cpp:365 msgid "Invalid request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:333 +#: libi2pd_client/HTTPProxy.cpp:273 msgid "Proxy unable to parse your request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:348 -msgid "Addresshelper is not supported" +#: libi2pd_client/HTTPProxy.cpp:288 +msgid "addresshelper is not supported" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:373 -#, c-format -msgid "" -"Host %s is already in router's addressbook. Be " -"careful: source of this URL may be harmful! Click here to update record: " -"Continue." +#: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "Host" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:375 -msgid "Addresshelper forced update rejected" +#: libi2pd_client/HTTPProxy.cpp:297 +msgid "added to router's addressbook from helper" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:382 -#, c-format -msgid "" -"To add host %s in router's addressbook, click here: Continue." +#: libi2pd_client/HTTPProxy.cpp:298 +msgid "Click here to proceed:" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:384 -msgid "Addresshelper request" +#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 +msgid "Continue" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:393 -#, c-format -msgid "" -"Host %s added to router's addressbook from helper. Click here to proceed: Continue." +#: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 +msgid "Addresshelper found" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:395 -msgid "Addresshelper adding" +#: libi2pd_client/HTTPProxy.cpp:306 +msgid "already in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:402 -#, c-format -msgid "" -"Host %s is already in router's addressbook. Click " -"here to update record: Continue." +#: libi2pd_client/HTTPProxy.cpp:307 +msgid "Click here to update record:" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:404 -msgid "Addresshelper update" +#: libi2pd_client/HTTPProxy.cpp:322 +msgid "invalid request uri" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:417 -msgid "Invalid request URI" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:460 +#: libi2pd_client/HTTPProxy.cpp:365 msgid "Can't detect destination host from request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:477 libi2pd_client/HTTPProxy.cpp:481 +#: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 msgid "Outproxy failure" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:477 -msgid "Bad outproxy settings" +#: libi2pd_client/HTTPProxy.cpp:382 +msgid "bad outproxy settings" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "not inside I2P network, but outproxy is not enabled" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:474 +msgid "unknown outproxy url" msgstr "" #: libi2pd_client/HTTPProxy.cpp:480 -#, c-format -msgid "Host %s is not inside I2P network, but outproxy is not enabled" +msgid "cannot resolve upstream proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:569 -msgid "Unknown outproxy URL" +#: libi2pd_client/HTTPProxy.cpp:488 +msgid "hostname too long" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:575 -msgid "Cannot resolve upstream proxy" +#: libi2pd_client/HTTPProxy.cpp:515 +msgid "cannot connect to upstream socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:583 -msgid "Hostname is too long" +#: libi2pd_client/HTTPProxy.cpp:521 +msgid "Cannot negotiate with socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:610 -msgid "Cannot connect to upstream SOCKS proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:616 -msgid "Cannot negotiate with SOCKS proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:658 +#: libi2pd_client/HTTPProxy.cpp:563 msgid "CONNECT error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:658 -msgid "Failed to connect" +#: libi2pd_client/HTTPProxy.cpp:563 +msgid "Failed to Connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:669 libi2pd_client/HTTPProxy.cpp:695 -msgid "SOCKS proxy error" +#: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 +msgid "socks proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:677 -msgid "Failed to send request to upstream" +#: libi2pd_client/HTTPProxy.cpp:582 +msgid "failed to send request to upstream" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:698 -msgid "No reply from SOCKS proxy" +#: libi2pd_client/HTTPProxy.cpp:603 +msgid "No Reply From socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:705 -msgid "Cannot connect" +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "cannot connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:705 -msgid "HTTP out proxy not implemented" +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "http out proxy not implemented" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:706 -msgid "Cannot connect to upstream HTTP proxy" +#: libi2pd_client/HTTPProxy.cpp:611 +msgid "cannot connect to upstream http proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:739 +#: libi2pd_client/HTTPProxy.cpp:644 msgid "Host is down" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:739 +#: libi2pd_client/HTTPProxy.cpp:644 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 ce775ecb..04779473 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -1,30 +1,29 @@ -`xgettext` command for extracting translation ---- - -``` -xgettext --omit-header -ctr: -ktr -kntr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp -``` - -Regex for transforming gettext translations to our format: ---- - -``` -in: ^(\"|#[:.,]|msgctxt)(.*)$\n -out: -``` - -``` -in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\n(msgstr\[1\]\ \"(.*)\"\n)?(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? -out: #{"$2", {"$3", "$5", "$7", "$9", "$11"}},\n -``` - -``` -in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n -out: {"$1", "$2"},\n -``` - - -``` -in: \n\n -out: \n -``` +`xgettext` command for extracting translation +--- + +``` +xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +``` + +Regex for transforming gettext translations to our format: +--- + +``` +in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? +out: #{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n +``` + +``` +in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n +out: {"$1", "$2"},\n +``` + +``` +in: ^#[:.](.*)$\n +out: +``` + +``` +in: \n\n +out: \n +``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 1cc357e2..43cf7ddd 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: /run/i2pd.pid, not used in Windows) +## Where to write pidfile (default: i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid ## Logging configuration section @@ -31,16 +31,15 @@ ## * 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, critical, none) +## Log messages above this level (debug, info, *warn, error, 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) @@ -71,101 +70,74 @@ ## don't just uncomment this # port = 4567 -## Enable communication through ipv4 (default: true) +## Enable communication through ipv4 ipv4 = true -## Enable communication through ipv6 (default: false) +## Enable communication through ipv6 ipv6 = false +## Enable SSU transport (default = true) +# ssu = true + ## Bandwidth configuration -## L limit bandwidth to 32 KB/sec, O - to 256 KB/sec, P - to 2048 KB/sec, +## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/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. 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. +## 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 # bandwidth = L -## Max % of bandwidth limit for transit. 0-100 (default: 100) +## Max % of bandwidth limit for transit. 0-100. 100 by default # share = 100 ## Router will not accept transit tunnels, disabling transit traffic completely -## (default: false) +## (default = false) # notransit = true -## Router will be floodfill (default: false) +## Router will be floodfill ## Note: that mode uses much more network connections and CPU! # floodfill = true -[ntcp2] -## Enable NTCP2 transport (default: true) -# enabled = true -## Publish address in RouterInfo (default: true) -# published = true -## Port for incoming connections (default is global port option value) -# port = 4567 - -[ssu2] -## Enable SSU2 transport (default: true) -# enabled = true -## Publish address in RouterInfo (default: true) -# published = true -## Port for incoming connections (default is global port option value) -# port = 4567 - [http] ## Web Console settings -## Enable the Web Console (default: true) +## Uncomment and set to 'false' to disable Web Console # enabled = true -## 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: /) +## Address and port service will listen on +address = 127.0.0.1 +port = 7070 +## Path to web console, default "/" # webroot = / -## Enable Web Console authentication (default: false) -## You should not use Web Console via public networks without additional encryption. -## HTTP authentication is not encryption layer! +## Uncomment following lines to enable Web Console authentication # auth = true # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), afrikaans, armenian, chinese, czech, french, -## german, italian, polish, portuguese, russian, spanish, turkish, turkmen, ukrainian -## and uzbek languages +## Currently supported english (default), afrikaans, armenian, french, german, +## russian, turkmen, ukrainian and uzbek languages # lang = english [httpproxy] -## Enable the HTTP proxy (default: true) +## Uncomment and set to 'false' to disable HTTP Proxy # enabled = true -## 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 +## Address and port service will listen on +address = 127.0.0.1 +port = 4444 +## Optional keys file for proxy local destination # keys = http-proxy-keys.dat ## Enable address helper for adding .i2p domains with "jump URLs" (default: true) -## You should disable this feature if your i2pd HTTP Proxy is public, -## because anyone could spoof the short domain via addresshelper and forward other users to phishing links # addresshelper = true ## Address of a proxy server inside I2P, which is used to visit regular Internet # outproxy = http://false.i2p ## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. [socksproxy] -## Enable the SOCKS proxy (default: true) +## Uncomment and set to 'false' to disable SOCKS Proxy # enabled = true -## 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 +## Address and port service will listen on +address = 127.0.0.1 +port = 4447 +## Optional keys file for proxy local destination # keys = socks-proxy-keys.dat ## Socks outproxy. Example below is set to use Tor for all connections except i2p -## Enable SOCKS outproxy (works only with SOCKS4, default: false) +## Uncomment and set to 'true' to enable using of SOCKS outproxy # outproxy.enabled = false ## Address and port of outproxy # outproxy = 127.0.0.1 @@ -173,34 +145,33 @@ ipv6 = false ## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. [sam] -## Enable the SAM bridge (default: true) -# enabled = false -## Address and ports service will listen on (default: 127.0.0.1:7656, udp: 7655) +## Comment or set to 'false' to disable SAM Bridge +enabled = true +## Address and port service will listen on # address = 127.0.0.1 # port = 7656 -# portudp = 7655 [bob] -## Enable the BOB command channel (default: false) +## Uncomment and set to 'true' to enable BOB command channel # enabled = false -## Address and port service will listen on (default: 127.0.0.1:2827) +## Address and port service will listen on # address = 127.0.0.1 # port = 2827 [i2cp] -## Enable the I2CP protocol (default: false) +## Uncomment and set to 'true' to enable I2CP protocol # enabled = false -## Address and port service will listen on (default: 127.0.0.1:7654) +## Address and port service will listen on # address = 127.0.0.1 # port = 7654 [i2pcontrol] -## Enable the I2PControl protocol (default: false) +## Uncomment and set to 'true' to enable I2PControl protocol # enabled = false -## Address and port service will listen on (default: 127.0.0.1:7650) +## Address and port service will listen on # address = 127.0.0.1 # port = 7650 -## Authentication password (default: itoopie) +## Authentication password. "itoopie" by default # password = itoopie [precomputation] @@ -211,11 +182,11 @@ ipv6 = false [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 (default: false) +## Enable connectivity over the Yggdrasil network # yggdrasil = false ## You can bind address from your Yggdrasil subnet 300::/64 ## The address must first be added to the network interface @@ -223,13 +194,13 @@ ipv6 = false [reseed] ## Options for bootstrapping into I2P network, aka reseeding -## Enable reseed data verification (default: true) +## Enable or disable reseed data verification. 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:71e:281a:9ed3::ace]:7070/ +# yggurls = http://[324:9de3:fea4:f6ac::ace]:7070/ ## Path to local reseed data file (.su3) for manual reseeding # file = /path/to/i2pseeds.su3 ## or HTTPS URL to reseed from @@ -239,7 +210,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 (default: 25) +## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default # threshold = 25 [addressbook] @@ -247,25 +218,24 @@ 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 +# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt [limits] -## Maximum active transit sessions (default: 10000) -## This value is doubled if floodfill mode is enabled! -# transittunnels = 10000 +## Maximum active transit sessions (default:2500) +# transittunnels = 2500 ## 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. (default: false) +## Enable explicit trust options. false by default # 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? (default: false) +## Should we hide our router from other routers? false by default # hidden = true [exploratory] @@ -281,3 +251,11 @@ 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 1ab46979..45fe4cc6 100644 --- a/contrib/i2pd.service +++ b/contrib/i2pd.service @@ -1,8 +1,7 @@ [Unit] Description=I2P Router written in C++ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ -Wants=network.target -After=network.target network-online.target +After=network.target [Service] User=i2pd @@ -12,7 +11,7 @@ RuntimeDirectoryMode=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 +ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service ExecReload=/bin/sh -c "kill -HUP $MAINPID" PIDFile=/run/i2pd/i2pd.pid ### Uncomment, if auto restart needed @@ -30,7 +29,7 @@ SendSIGKILL=yes #TimeoutStopSec=10m # If you have problems with hanging i2pd, you can try increase this -LimitNOFILE=8192 +LimitNOFILE=4096 # To enable write of coredump uncomment this #LimitCORE=infinity diff --git a/contrib/openrc/i2pd.openrc b/contrib/openrc/i2pd.openrc index 0233eed8..deca4625 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/bin/i2pd" +command="/usr/sbin/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 23daddf6..2e6ba24f 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.58.0 +Version: 2.42.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -10,7 +10,11 @@ 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++ @@ -20,72 +24,96 @@ 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 -%cmake \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ -%if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . +%if 0%{?rhel} == 7 +%cmake3 \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ + -DBUILD_SHARED_LIBS:BOOL=OFF %else - -DBUILD_SHARED_LIBS:BOOL=OFF +%cmake \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ +%if 0%{?fedora} > 29 + -DBUILD_SHARED_LIBS:BOOL=OFF \ + . +%else + -DBUILD_SHARED_LIBS:BOOL=OFF +%endif %endif -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif - %if 0%{?mageia} > 7 - pushd build - %endif +%if 0%{?rhel} == 9 +pushd redhat-linux-build +%endif + +%if 0%{?fedora} >= 35 +%if 0%{?fedora} < 37 +pushd redhat-linux-build +%endif +%else +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif +%endif + +%if 0%{?mageia} > 7 +pushd build %endif make %{?_smp_mflags} -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 - popd +%if 0%{?rhel} == 9 +popd %endif +%if 0%{?fedora} >= 33 +%if 0%{?fedora} < 37 +popd +%endif +%endif + +%if 0%{?mageia} > 7 +popd +%endif %install pushd build -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?rhel} == 9 +pushd redhat-linux-build +%endif - %if 0%{?mageia} - pushd build - %endif +%if 0%{?fedora} >= 35 +%if 0%{?fedora} < 37 +pushd redhat-linux-build +%endif +%else +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif +%endif + +%if 0%{?mageia} +pushd build %endif chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd +%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd @@ -121,7 +149,7 @@ getent passwd i2pd >/dev/null || \ %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_bindir}/i2pd +%{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt @@ -136,72 +164,6 @@ 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 - -* Wed Jan 11 2023 orignal - 2.45.1 -- update to 2.45.1 - -* Tue Jan 3 2023 orignal - 2.45.0 -- update to 2.45.0 - -* Sun Nov 20 2022 orignal - 2.44.0 -- update to 2.44.0 - -* Mon Aug 22 2022 orignal - 2.43.0 -- update to 2.43.0 - -* Tue May 24 2022 r4sas - 2.42.1 -- update to 2.42.1 - * Sun May 22 2022 orignal - 2.42.0 - update to 2.42.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index c2b7c923..b60a9d40 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.58.0 +Version: 2.42.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -8,7 +8,11 @@ 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++ @@ -18,72 +22,95 @@ 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 -%cmake \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ -%if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . +%if 0%{?rhel} == 7 +%cmake3 \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ + -DBUILD_SHARED_LIBS:BOOL=OFF %else - -DBUILD_SHARED_LIBS:BOOL=OFF +%cmake \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ +%if 0%{?fedora} > 29 + -DBUILD_SHARED_LIBS:BOOL=OFF \ + . +%else + -DBUILD_SHARED_LIBS:BOOL=OFF +%endif %endif -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?rhel} == 9 +pushd redhat-linux-build +%endif - %if 0%{?mageia} > 7 - pushd build - %endif +%if 0%{?fedora} >= 35 +%if 0%{?fedora} < 37 +pushd redhat-linux-build +%endif +%else +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif +%endif + +%if 0%{?mageia} > 7 +pushd build %endif make %{?_smp_mflags} -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 - popd +%if 0%{?rhel} == 9 +popd %endif +%if 0%{?fedora} >= 33 +%if 0%{?fedora} < 37 +popd +%endif +%endif + +%if 0%{?mageia} > 7 +popd +%endif %install pushd build -%if 0%{?rhel} >= 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?rhel} == 9 +pushd redhat-linux-build +%endif - %if 0%{?mageia} - pushd build - %endif +%if 0%{?fedora} >= 35 +%if 0%{?fedora} < 37 +pushd redhat-linux-build +%endif +%else +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif +%endif + +%if 0%{?mageia} +pushd build %endif chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd +%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd @@ -119,7 +146,7 @@ getent passwd i2pd >/dev/null || \ %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_bindir}/i2pd +%{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt @@ -134,72 +161,6 @@ 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 - -* Wed Jan 11 2023 orignal - 2.45.1 -- update to 2.45.1 - -* Tue Jan 3 2023 orignal - 2.45.0 -- update to 2.45.0 - -* Sun Nov 20 2022 orignal - 2.44.0 -- update to 2.44.0 - -* Mon Aug 22 2022 orignal - 2.43.0 -- update to 2.43.0 - -* Tue May 24 2022 r4sas - 2.42.1 -- update to 2.42.1 - * Sun May 22 2022 orignal - 2.42.0 - update to 2.42.0 diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index fc455e79..55723c43 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -5,7 +5,6 @@ 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 d2cd4d5e..19b58958 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/bin/i2pd --daemon --service --log=file --logfile=$LOGFILE +exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE diff --git a/contrib/webconsole/hacker.css b/contrib/webconsole/hacker.css deleted file mode 100644 index 5d93be50..00000000 --- a/contrib/webconsole/hacker.css +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 2f399fea..5d63d1a7 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -1,297 +1,293 @@ -/* - * 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: #fafafa; - --main-text-color: #103456; - --main-link-color: #894c84; - --main-link-hover-color: #fafafa; -} - -@media (prefers-color-scheme: dark) { - :root { - --main-bg-color: #242424; - --main-text-color: #17ab5c; - --main-link-color: #bf64b7; - --main-link-hover-color: #000000; - } -} - -body { - font: 100%/1.5em sans-serif; - margin: 0; - padding: 1.5em; - background: var(--main-bg-color); - color: var(--main-text-color); -} - -a, .slide label { - text-decoration: none; - color: var(--main-link-color); -} - -a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { - color: var(--main-link-hover-color); - background: var(--main-link-color); -} - -a.button { - appearance: button; - text-decoration: none; - padding: 0 5px; - border: 1px solid var(--main-link-color); -} - -.header { - font-size: 2.5em; - text-align: center; - margin: 1em 0; - color: var(--main-link-color); -} - -.wrapper { - margin: 0 auto; - padding: 1em; - max-width: 64em; -} - -.menu { - display: block; - float: left; - overflow: hidden; - padding: 4px; - max-width: 12em; - white-space: nowrap; - text-overflow: ellipsis; -} - -.listitem { - display: block; - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.tableitem { - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.content { -/* float: left;*/ - font-size: 1em; - margin-left: 2em; - padding: 4px; - max-width: 50em; - overflow: auto; - left: 35%; - position: absolute; -} - -.tunnel.established { - color: #56B734; -} - -.tunnel.expiring { - color: #D3AE3F; -} - -.tunnel.failed { - color: #D33F3F; -} - -.tunnel.building { - color: #434343; -} - -caption { - font-size: 1.5em; - text-align: center; - color: var(--main-link-color); -} - -table { - display: table; - border-collapse: collapse; - text-align: center; -} - -table.extaddr { - text-align: left; -} - -table.services { - width: 100%; -} - -textarea { - background-color: var(--main-bg-color); - color: var(--main-text-color); - word-break: break-all; -} - -.streamdest { - width: 120px; - max-width: 240px; - overflow: hidden; - text-overflow: ellipsis; -} - -.slide div.slidecontent, .slide [type="checkbox"] { - display: none; -} - -.slide [type="checkbox"]:checked ~ div.slidecontent { - display: block; - margin-top: 0; - padding: 0; -} - -.disabled { - color: #D33F3F; -} - -.enabled { - color: #56B734; -} - -button[type=submit] { - background-color: transparent; - color: var(--main-link-color); - text-decoration: none; - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input, select, select option { - background-color: var(--main-bg-color); - color: var(--main-link-color); - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input:focus, select:focus, select option:focus { - outline: none; -} - -input[type=number]::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -@media screen and (max-width: 1150px) { /* adaptive style */ - .wrapper { - max-width: 58em; - } - - .content { - max-width: 40em; - } -} - -@media screen and (max-width: 980px) { - body { - font: 100%/1.2em sans-serif; - padding: 1.2em 0 0 0; - } - - .menu { - width: 100%; - max-width: unset; - display: block; - float: none; - position: unset; - font-size: 16px; - text-align: center; - } - - .menu a, .commands a { - display: inline-block; - padding: 4px; - } - - .content { - float: none; - margin-left: unset; - margin-top: 16px; - max-width: 100%; - width: 100%; - text-align: center; - position: absolute; - left: 0; - } - - a, .slide label { - display: block; - } - - .header { - margin: unset; - font-size: 1.5em; - } - - small { - display: block - } - - a.button { - appearance: button; - text-decoration: none; - margin-top: 10px; - padding: 6px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - width: -webkit-fill-available; - } - - input, select { - width: 35%; - text-align: center; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 18px; - } - - table.extaddr { - margin: auto; - text-align: unset; - } - - textarea { - width: -webkit-fill-available; - height: auto; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 12px; - } - - button[type=submit] { - padding: 5px 15px; - background: transparent; - border: 2px solid var(--main-link-color); - cursor: pointer; - -webkit-border-radius: 5px; - border-radius: 5px; - position: relative; - height: 36px; - display: -webkit-inline-box; - margin-top: 10px; - } -} +/* + * Copyright (c) 2021-2022, The PurpleI2P Project + * + * This file is part of Purple i2pd project and licensed under BSD3 + * + * See full license text in LICENSE file at top of project tree + * + ****************************************************************** + * + * This is style sheet for webconsole, with @media selectors for adaptive + * view on desktop and mobile devices, respecting preferred user's color + * scheme used in system/browser. + * + * Minified copy of that style sheet is bundled inside i2pd sources. +*/ + +:root { + --main-bg-color: #fafafa; + --main-text-color: #103456; + --main-link-color: #894c84; + --main-link-hover-color: #fafafa; +} + +@media (prefers-color-scheme: dark) { + :root { + --main-bg-color: #242424; + --main-text-color: #17ab5c; + --main-link-color: #bf64b7; + --main-link-hover-color: #000000; + } +} + +body { + font: 100%/1.5em sans-serif; + margin: 0; + padding: 1.5em; + background: var(--main-bg-color); + color: var(--main-text-color); +} + +a, .slide label { + text-decoration: none; + color: var(--main-link-color); +} + +a:hover, .slide label:hover, button[type=submit]:hover { + color: var(--main-link-hover-color); + background: var(--main-link-color); +} + +a.button { + appearance: button; + text-decoration: none; + padding: 0 5px; + border: 1px solid var(--main-link-color); +} + +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: var(--main-link-color); +} + +.wrapper { + margin: 0 auto; + padding: 1em; + max-width: 64em; +} + +.menu { + display: block; + float: left; + overflow: hidden; + padding: 4px; + max-width: 12em; + white-space: nowrap; + text-overflow: ellipsis; +} + +.listitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.tableitem { + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.content { + float: left; + font-size: 1em; + margin-left: 2em; + padding: 4px; + max-width: 50em; + overflow: auto; +} + +.tunnel.established { + color: #56B734; +} + +.tunnel.expiring { + color: #D3AE3F; +} + +.tunnel.failed { + color: #D33F3F; +} + +.tunnel.building { + color: #434343; +} + +caption { + font-size: 1.5em; + text-align: center; + color: var(--main-link-color); +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +table.extaddr { + text-align: left; +} + +table.services { + width: 100%; +} + +textarea { + background-color: var(--main-bg-color); + color: var(--main-text-color); + word-break: break-all; +} + +.streamdest { + width: 120px; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide div.slidecontent, .slide [type="checkbox"] { + display: none; +} + +.slide [type="checkbox"]:checked ~ div.slidecontent { + display: block; + margin-top: 0; + padding: 0; +} + +.disabled { + color: #D33F3F; +} + +.enabled { + color: #56B734; +} + +button[type=submit] { + background-color: transparent; + color: var(--main-link-color); + text-decoration: none; + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input, select, select option { + background-color: var(--main-bg-color); + color: var(--main-link-color); + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input:focus, select:focus, select option:focus { + outline: none; +} + +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +@media screen and (max-width: 1150px) { /* adaptive style */ + .wrapper { + max-width: 58em; + } + + .content { + max-width: 40em; + } +} + +@media screen and (max-width: 980px) { + body { + font: 100%/1.2em sans-serif; + padding: 1.2em 0 0 0; + } + + .menu { + width: 100%; + max-width: unset; + display: block; + float: none; + position: unset; + font-size: 16px; + text-align: center; + } + + .menu a, .commands a { + display: inline-block; + padding: 4px; + } + + .content { + float: none; + margin-left: unset; + margin-top: 16px; + max-width: 100%; + width: 100%; + text-align: center; + } + + a, .slide label { + display: block; + } + + .header { + margin: unset; + font-size: 1.5em; + } + + small { + display: block + } + + a.button { + appearance: button; + text-decoration: none; + margin-top: 10px; + padding: 6px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + width: -webkit-fill-available; + } + + input, select { + width: 35%; + text-align: center; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 18px; + } + + table.extaddr { + margin: auto; + text-align: unset; + } + + textarea { + width: -webkit-fill-available; + height: auto; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 12px; + } + + button[type=submit] { + padding: 5px 15px; + background: transparent; + border: 2px solid var(--main-link-color); + cursor: pointer; + -webkit-border-radius: 5px; + border-radius: 5px; + position: relative; + height: 36px; + display: -webkit-inline-box; + margin-top: 10px; + } +} \ No newline at end of file diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 5c4f9c97..0ecc446a 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,7 +8,6 @@ #include #include -#include #include "Daemon.h" @@ -32,6 +31,7 @@ #include "Crypto.h" #include "UPnP.h" #include "Timestamp.h" +#include "util.h" #include "I18N.h" namespace i2p @@ -58,7 +58,9 @@ namespace util bool Daemon_Singleton::IsService () const { bool service = false; +#ifndef _WIN32 i2p::config::GetOption("service", service); +#endif return service; } @@ -150,78 +152,144 @@ namespace util LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - 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); - - i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces + 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); 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 (); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + + // ifname -> address + std::string ifname; i2p::config::GetOption("ifname", ifname); + if (ipv4 && i2p::config::IsDefault ("address4")) + { + std::string ifname4; i2p::config::GetOption("ifname4", ifname4); + if (!ifname4.empty ()) + i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 + else if (!ifname.empty ()) + i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 + } + if (ipv6 && i2p::config::IsDefault ("address6")) + { + std::string ifname6; i2p::config::GetOption("ifname6", ifname6); + if (!ifname6.empty ()) + i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 + else if (!ifname.empty ()) + i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 + } + + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + boost::asio::ip::address_v6 yggaddr; + if (ygg) + { + std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); + if (!yggaddress.empty ()) + { + yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); + if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || + !i2p::util::net::IsLocalAddress (yggaddr)) + { + LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); + ygg = false; + } + } + else + { + yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (yggaddr.is_unspecified ()) + { + LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); + ygg = false; + } + } + } + + uint16_t port; i2p::config::GetOption("port", port); + if (!i2p::config::IsDefault("port")) + { + LogPrint(eLogInfo, "Daemon: Accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); + i2p::context.SetSupportsMesh (ygg, yggaddr); + + i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) + { + std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); + if (!ntcp2proxy.empty ()) published = false; + } + if (published) + { + uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); + if (!ntcp2port) ntcp2port = port; // use standard port + i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish + if (ipv6) + { + std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); + auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); + if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) + i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured + } + } + else + i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish + } + if (ygg) + { + i2p::context.PublishNTCP2Address (port, true, false, false, true); + i2p::context.UpdateNTCP2V6Address (yggaddr); + if (!ipv4 && !ipv6) + i2p::context.SetStatus (eRouterStatusMesh); + } + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) + { + bool published; i2p::config::GetOption("ssu2.published", published); + if (published) + { + uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); + i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish + } + else + i2p::context.PublishSSU2Address (0, false, ipv4, ipv6); // unpublish + } + + bool transit; i2p::config::GetOption("notransit", transit); + i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) - { + if (isFloodfill) { LogPrint(eLogInfo, "Daemon: Router configured as floodfill"); i2p::context.SetFloodfill (true); } else + { i2p::context.SetFloodfill (false); - - bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetAcceptsTunnels (!transit); - uint32_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); - if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels")) - transitTunnels *= 2; // double default number of transit tunnels for floodfill - i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels); + } /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); if (bandwidth.length () > 0) { - 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' )) + if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') { i2p::context.SetBandwidth (bandwidth[0]); LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); } - 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)) + else { auto value = std::atoi(bandwidth.c_str()); if (value > 0) @@ -234,7 +302,7 @@ namespace util LogPrint(eLogInfo, "Daemon: Unexpected bandwidth ", bandwidth, ". Set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } - } + } } else if (isFloodfill) { @@ -300,7 +368,7 @@ namespace util if (hidden) { LogPrint(eLogInfo, "Daemon: Hidden mode enabled"); - i2p::context.SetHidden(true); + i2p::data::netdb.SetHidden(true); } std::string httpLang; i2p::config::GetOption("http.lang", httpLang); @@ -330,16 +398,19 @@ namespace util bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + bool ssu; i2p::config::GetOption("ssu", ssu); + bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); LogPrint(eLogInfo, "Daemon: Starting Transports"); - if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled"); + if(!ssu) LogPrint(eLogInfo, "Daemon: SSU disabled"); if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled"); - i2p::transport::transports.Start(ntcp2, ssu2); - if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2()) + i2p::transport::transports.SetCheckReserved(checkInReserved); + i2p::transport::transports.Start(ntcp2, ssu, ssu2); + if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); else { - LogPrint(eLogCritical, "Daemon: Failed to start Transports"); + LogPrint(eLogError, "Daemon: Failed to start Transports"); /** shut down netdb right away */ i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); @@ -358,17 +429,15 @@ namespace util } catch (std::exception& ex) { - LogPrint (eLogCritical, "Daemon: Failed to start Webconsole: ", ex.what ()); + LogPrint (eLogError, "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 (); @@ -385,7 +454,7 @@ namespace util } catch (std::exception& ex) { - LogPrint (eLogCritical, "Daemon: Failed to start I2PControl: ", ex.what ()); + LogPrint (eLogError, "Daemon: Failed to start I2PControl: ", ex.what ()); ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); } } @@ -397,8 +466,6 @@ 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 3f53b89f..26d4a047 100644 --- a/daemon/Daemon.h +++ b/daemon/Daemon.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -97,15 +97,15 @@ namespace util return instance; } }; -#else // Unix-like systems, including Linux -#define Daemon i2p::util::DaemonUnix::Instance() - class DaemonUnix : public Daemon_Singleton +#else +#define Daemon i2p::util::DaemonLinux::Instance() + class DaemonLinux : public Daemon_Singleton { public: - static DaemonUnix& Instance() + static DaemonLinux& Instance() { - static DaemonUnix instance; + static DaemonLinux instance; return instance; } diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index bb2f44db..beb5528d 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -43,11 +42,10 @@ namespace i2p { namespace http { - - static void LoadExtCSS (std::string fileName = "style") + static void LoadExtCSS () { std::stringstream s; - std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css"); + std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); if (i2p::fs::Exists(styleFile)) { std::ifstream f(styleFile, std::ifstream::binary); s << f.rdbuf(); @@ -82,14 +80,15 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; + const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; - const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; + const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; + const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time) { @@ -106,18 +105,18 @@ namespace http { int num; if ((num = seconds / 86400) > 0) { - s << ntr("%d day", "%d days", num, num) << ", "; + s << num << " " << tr("day", "days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << ntr("%d hour", "%d hours", num, num) << ", "; + s << num << " " << tr("hour", "hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << ntr("%d minute", "%d minutes", num, num) << ", "; + s << num << " " << tr("minute", "minutes", num) << ", "; seconds -= num * 60; } - s << ntr("%d second", "%d seconds", seconds, seconds); + s << seconds << " " << tr("second", "seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) @@ -125,38 +124,40 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes); + s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); else if (numKBytes < 1024 * 1024) - s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024); + s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); else - s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024); + s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { - std::string state; - std::string_view stateText; - switch (eState) - { + std::string state, stateText; + switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStatePending : state = "building"; break; - case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; - case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; + case i2p::tunnel::eTunnelStateBuildFailed : + case i2p::tunnel::eTunnelStateTestFailed : 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); - s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: - ShowTraffic(s, bytes); - s << "\r\n"; + if (state == "building") stateText = tr("building"); + else if (state == "failed") stateText = tr("failed"); + else if (state == "expiring") stateText = tr("expiring"); + else if (state == "established") stateText = tr("established"); + else stateText = tr("unknown"); + + s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; + s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; } static void SetLogLevel (const std::string& level) { - if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") + if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); @@ -168,46 +169,21 @@ 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"; + " Purple I2P Webconsole\r\n"; GetStyles(s); - if (theme == "black") - { - s << - ""; - - } s << "\r\n" "\r\n" @@ -224,7 +200,7 @@ namespace http { if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) s << " " << tr("Transit Tunnels") << "
\r\n"; s << - " " << tr("Transports") << "
\r\n" + " " << tr ("Transports") << "
\r\n" " " << tr("I2P tunnels") << "
\r\n"; if (i2p::client::context.GetSAMBridge ()) s << " " << tr("SAM sessions") << "
\r\n"; @@ -241,45 +217,40 @@ namespace http { "\r\n"; } - static void ShowError(std::stringstream& s, std::string_view string) + static void ShowError(std::stringstream& s, const std::string& string) { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { 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) + case eRouterStatusError: { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); + s << tr("Error"); + switch (i2p::context.GetError ()) + { + case eRouterErrorClockSkew: + s << " - " << tr("Clock skew"); + break; + case eRouterErrorOffline: + s << " - " << tr("Offline"); + break; + case eRouterErrorSymmetricNAT: + s << " - " << tr("Symmetric NAT"); + break; + default: ; + } break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - case eRouterErrorFullConeNAT: - s << " - " << tr("Full cone NAT"); - break; - case eRouterErrorNoDescriptors: - s << " - " << tr("No Descriptors"); - break; - default: ; } + default: s << tr("Unknown"); } } @@ -288,16 +259,13 @@ namespace http { s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); 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"; - } + s << "" << tr("Network status") << ": "; + ShowNetworkStatus (s, i2p::context.GetStatus ()); + s << "
\r\n"; if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); + ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); s << "
\r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) @@ -318,69 +286,60 @@ namespace http { if (family.length () > 0) s << ""<< tr("Family") << ": " << family << "
\r\n"; s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; - bool isTotalTCSR; - i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); - if (isTotalTCSR) { - s << "" << tr("Total tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%
\r\n"; - } s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetInBandwidth15s () / 1024) << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; s << "
"; if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { s << "\r\n\r\n
\r\n"; } - if (includeHiddenContent) - { + if (includeHiddenContent) { s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; s << "" << tr("Version") << ": " VERSION "
\r\n"; s << ""<< tr("Our external address") << ":" << "
\r\n\r\n"; - auto addresses = i2p::context.GetRouterInfo().GetAddresses (); - if (addresses) + for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { - for (const auto& address : *addresses) + s << "\r\n\r\n\r\n"; - if (address->published) - s << "\r\n"; - else - { - s << "\r\n"; - } - s << "\r\n"; + case i2p::data::RouterInfo::eTransportNTCP: + s << "NTCP2"; + break; + case i2p::data::RouterInfo::eTransportSSU: + s << "SSU"; + break; + case i2p::data::RouterInfo::eTransportSSU2: + s << "SSU2"; + break; + default: + s << tr("Unknown"); } + if (address->IsV6 ()) + { + if (address->IsV4 ()) s << "v4"; + s << "v6"; + } + s << "\r\n"; + if (address->published) + s << "\r\n"; + else + { + s << "\r\n"; + } + s << "\r\n"; } s << "
"; + switch (address->transportStyle) { - if (!address) continue; - s << "
"; - switch (address->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - s << "NTCP2"; - break; - case i2p::data::RouterInfo::eTransportSSU2: - s << "SSU2"; - break; - default: - s << tr("Unknown"); - } - bool v6 = address->IsV6 (); - if (v6) - { - if (address->IsV4 ()) s << "v4"; - s << "v6"; - } - s << "" << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":") << address->port << "" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported"); - if (address->port) - s << " :" << address->port; - s << "
" << address->host.to_string() << ":" << address->port << "" << tr("supported"); + if (address->port) + s << " :" << address->port; + s << "
\r\n"; } @@ -448,23 +407,10 @@ 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 ()); @@ -473,14 +419,15 @@ namespace http { s << "
\r\n
\r\n"; } - if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) + if (dest->IsPublic() && token) { 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" - " GetIdentHash ().ToBase32 () << "\">\r\n" + " \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"; @@ -489,23 +436,9 @@ namespace http { if (dest->GetNumRemoteLeaseSets()) { s << "
\r\n\r\n
\r\n" - << "" - << "" - << "" // LeaseSet expiration button column - << "" - << "" - << ""; + << "\r\n\r\n
\r\n
" << tr("Address") << " " << tr("Type") << "" << tr("EncType") << "
"; 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"; @@ -522,15 +455,13 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; + s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; } ); } s << "⇒ " << it->GetTunnelID () << ":me"; if (it->LatencyIsKnown()) - s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; + s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "\r\n"; } @@ -545,33 +476,27 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; + s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; } ); } if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "\r\n"; } } s << "
\r\n"; - s << "" << tr("Tags") << "
\r\n" - << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; + s << "" << tr("Tags") << "
\r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } - s << "
\r\n" - << "\r\n" - << "
\r\n" - << "\r\n\r\n" - << "\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; + s << "
\r\n\r\n" + << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; } else s << tr("Outgoing") << ": 0
\r\n"; s << "
\r\n"; @@ -586,11 +511,8 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } - s << "
\r\n" - << "\r\n" - << "
\r\n\r\n" - << "\r\n" - << "\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; + s << "
\r\n\r\n" + << "
\r\n\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; } else s << tr("Tags sessions") << ": 0
\r\n"; s << "
\r\n"; @@ -609,21 +531,19 @@ namespace http { ShowLeaseSetDestination (s, dest, token); // Print table with streams information - s << "\r\n\r\n\r\n" - << "" - << "" // Stream closing button column - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "\r\n\r\n\r\n"; + s << "
" - << tr("Streams") - << "
StreamID DestinationSentReceivedOutInBufRTTWindowStatus
\r\n\r\n\r\n"; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { @@ -650,8 +570,6 @@ namespace http { } s << "\r\n
" << tr("Streams") << "
StreamID"; // Stream closing button column + s << "DestinationSentReceivedOutInBufRTTWindowStatus
"; } - else - ShowError(s, tr("Such destination is not found")); } void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) @@ -686,10 +604,7 @@ 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)); - ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false); - } + ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); if (!ls) return; s << "
IsExpired()) @@ -720,7 +635,7 @@ namespace http { } else if (!i2p::context.IsFloodfill ()) { - s << "" << tr("LeaseSets") << ": " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".
\r\n"; + s << "" << tr("LeaseSets") << ": " << tr("not floodfill") << ".
\r\n"; } else { @@ -732,7 +647,6 @@ 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 (); @@ -744,15 +658,13 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; + s << "⇒ " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " "; } ); } s << "⇒ " << it->GetTunnelID () << ":me"; if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
\r\n"; } @@ -767,14 +679,12 @@ namespace http { it->VisitTunnelHops( [&s](std::shared_ptr hopIdent) { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; + s << " " << i2p::data::GetIdentHashAbbreviation (hopIdent->GetIdentHash ()) << " ⇒"; } ); } if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "\r\n"; } @@ -787,7 +697,8 @@ namespace http { s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; s << " " << tr("Run peer test") << "
\r\n"; - s << " " << tr("Reload tunnels configuration") << "
\r\n"; + + // s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
\r\n"; @@ -812,43 +723,33 @@ namespace http { s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; - auto loglevel = i2p::log::Logger().GetLogLevel(); s << "" << tr("Logging level") << "
\r\n"; - s << " none \r\n"; - s << " critical \r\n"; - s << " error \r\n"; - s << " warn \r\n"; - s << " info \r\n"; - s << " debug
\r\n
\r\n"; + s << " none \r\n"; + s << " error \r\n"; + s << " warn \r\n"; + s << " info \r\n"; + s << " debug
\r\n
\r\n"; - uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels (); + uint16_t maxTunnels = GetMaxNumTransitTunnels (); s << "" << tr("Transit tunnels limit") << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; - s << " \r\n"; + s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n"; - // get current used language - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); - - s << "" - << tr("Change language") - << "
\r\n" - << "
\r\n" - << " \r\n" - << " \r\n" - << " \r\n"; + s << " \r\n"; + s << " \r\n" - << " \r\n" - << "
\r\n
\r\n"; + s << " \r\n"; + s << " \r\n"; + s << "\r\n
\r\n"; } @@ -856,71 +757,51 @@ namespace http { { if (i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "" << tr("Transit Tunnels") << ":
\r\n"; - s << ""; + s << "" << tr("Transit Tunnels") << ":
\r\n
\r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { + s << "
\r\n"; if (std::dynamic_pointer_cast(it)) - s << "
\r\n"; + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " " << it->GetNumTransmittedBytes () << "\r\n"; } - s << "
ID" << tr("Amount") << "" << tr("Next") << "
" << it->GetTunnelID () << ""; + s << it->GetTunnelID () << " ⇒ "; else if (std::dynamic_pointer_cast(it)) - s << "
" << it->GetTunnelID () << ""; + s << " ⇒ " << it->GetTunnelID (); else - s << "
" << it->GetTunnelID () << ""; - ShowTraffic(s, it->GetNumTransmittedBytes ()); - s << "" << it->GetNextPeerName () << "
\r\n"; + s << "
\r\n"; } else { - s << "" << tr("Transit Tunnels") << ": " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".
\r\n"; + s << "" << tr("Transit Tunnels") << ": " << tr("no transit tunnels currently built") << ".
\r\n"; } } template static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) { - auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) - { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; - std::set sortedSessions(comp); - for (const auto& it : sessions) - { - auto ret = sortedSessions.insert(it.second); - if (!ret.second) - LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); - } std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; - for (const auto& it: sortedSessions) + for (const auto& it: sessions ) { - auto endpoint = it->GetRemoteEndpoint (); - if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) + if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s << "
\r\n"; - if (it->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; - if (it->GetRelayTag ()) - tmp_s << " [itag:" << it->GetRelayTag () << "]"; - if (it->GetSendQueueSize () > 0) - tmp_s << " [queue:" << it->GetSendQueueSize () << "]"; - if (it->IsSlow ()) tmp_s << " [slow]"; + if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; + tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << it.second->GetRemoteEndpoint ().address ().to_string (); + if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; + tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s << "
\r\n" << std::endl; cnt++; } - if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) + if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s6 << "
\r\n"; - if (it->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); - if (!it->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; - if (it->GetRelayTag ()) - tmp_s6 << " [itag:" << it->GetRelayTag () << "]"; - if (it->GetSendQueueSize () > 0) - tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]"; + if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << "[" << it.second->GetRemoteEndpoint ().address ().to_string () << "]"; + if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s6 << "
\r\n" << std::endl; cnt6++; } @@ -949,6 +830,46 @@ namespace http { if (!sessions.empty ()) ShowTransportSessions (s, sessions, "NTCP2"); } + auto ssuServer = i2p::transport::transports.GetSSUServer (); + if (ssuServer) + { + auto sessions = ssuServer->GetSessions (); + if (!sessions.empty ()) + { + s << "
\r\n\r\n
"; + for (const auto& it: sessions) + { + s << "
\r\n"; + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << " ⇒ "; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << " ⇒ "; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + if (it.second->GetRelayTag ()) + s << " [itag:" << it.second->GetRelayTag () << "]"; + s << "
\r\n" << std::endl; + } + s << "
\r\n
\r\n"; + } + auto sessions6 = ssuServer->GetSessionsV6 (); + if (!sessions6.empty ()) + { + s << "
\r\n\r\n
"; + for (const auto& it: sessions6) + { + s << "
\r\n"; + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << " ⇒ "; + s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << " ⇒ "; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + if (it.second->GetRelayTag ()) + s << " [itag:" << it.second->GetRelayTag () << "]"; + s << "
\r\n" << std::endl; + } + s << "
\r\n
\r\n"; + } + } auto ssu2Server = i2p::transport::transports.GetSSU2Server (); if (ssu2Server) { @@ -974,39 +895,25 @@ namespace http { for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); - auto sam_id = i2p::data::ByteStreamToBase64 ((const uint8_t *)it.first.data (), it.first.length ()); // base64, becuase session name might be UTF-8 - s << "\r\n"; } else - s << "" << tr("SAM sessions") << ": " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".
\r\n"; + s << "" << tr("SAM sessions") << ": " << tr("no sessions currently running") << ".
\r\n"; } 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; } - 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) - { + + auto session = sam->FindSession (id); + if (!session) { ShowError(s, tr("SAM session not found")); return; } @@ -1018,19 +925,18 @@ 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({ (const char *)sam_id.data (), l })) + for (const auto& it: sam->ListSockets(id)) { s << "
"; switch (it->GetSocketType ()) { - 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; + 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; default: s << "unknown"; break; } - if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ()) - s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
\r\n"; } s << "
\r\n"; @@ -1039,42 +945,34 @@ namespace http { void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - - auto& clientTunnels = i2p::client::context.GetClientTunnels (); - auto httpProxy = i2p::client::context.GetHttpProxy (); - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (!clientTunnels.empty () || httpProxy || socksProxy) + s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; + for (auto& it: i2p::client::context.GetClientTunnels ()) { - 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"; + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; } + 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) + { + 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"; auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { @@ -1112,7 +1010,7 @@ namespace http { for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; + s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; @@ -1308,7 +1206,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO + ShowError(s, tr("Unknown page") + ": " + page); return; } } @@ -1322,7 +1220,7 @@ namespace http { url.parse_query(params); std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands"; + std::string redirect = "5; url=" + webroot + "?page=commands"; std::string token = params["token"]; if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) @@ -1334,7 +1232,7 @@ namespace http { std::string cmd = params["cmd"]; if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); - else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) + else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); @@ -1396,50 +1294,20 @@ namespace http { s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; - res.add_header("Refresh", redirect.c_str()); - return; - } - else if (cmd == HTTP_COMMAND_EXPIRELEASE) - { - std::string b32 = params["b32"]; - std::string lease = params["lease"]; - - i2p::data::IdentHash ident, leaseident; - ident.FromBase32 (b32); - leaseident.FromBase32 (lease); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (dest) - { - auto leaseset = dest->FindLeaseSet (leaseident); - if (leaseset) - { - leaseset->ExpireLease (); - s << "" << tr("SUCCESS") << ": " << tr("LeaseSet expiration time updated") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("LeaseSet is not found or already expired") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; - - s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; + s << "

" << tr("You will be redirected in 5 seconds") << ""; + redirect = "5; 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) - i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); + if (limit > 0 && limit <= 65535) + 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("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
\r\n
\r\n"; s << "" << tr("Back to commands list") << "\r\n
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + s << "

" << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } @@ -1464,11 +1332,13 @@ 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 sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); + auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); + sig[len] = 0; out << "#!sig=" << sig; s << "" << tr("SUCCESS") << ":
\r\n

\r\n" "\r\n
\r\n
\r\n" @@ -1477,6 +1347,7 @@ 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"; @@ -1500,21 +1371,18 @@ namespace http { } else if (cmd == HTTP_COMMAND_RELOAD_CSS) { - std::string theme; i2p::config::GetOption("http.theme", theme); - - if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme); - else LoadExtCSS(); + LoadExtCSS(); } else { res.code = 400; - ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO + ShowError(s, tr("Unknown command") + ": " + cmd); return; } s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

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

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + s << "

" << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); } @@ -1527,13 +1395,13 @@ namespace http { reply.body = content; m_SendBuffer = reply.to_string(); - boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), + boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), 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.get_executor ()), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), 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_Hostname(address) { } diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 38b790d4..8646253f 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,8 +24,6 @@ namespace http { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds - const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds - const int TRANSIT_TUNNELS_LIMIT = 1000000; class HTTPConnection: public std::enable_shared_from_this { @@ -83,8 +81,8 @@ namespace http bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_context m_Service; - boost::asio::executor_work_guard m_Work; + boost::asio::io_service m_Service; + boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; std::string m_Hostname; }; diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h index 2772fb69..0acbe8d1 100644 --- a/daemon/HTTPServerResources.h +++ b/daemon/HTTPServerResources.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -38,14 +38,14 @@ namespace http "@media (prefers-color-scheme: dark) { :root { --main-bg-color: #242424; --main-text-color: #17ab5c; --main-link-color: #bf64b7; --main-link-hover-color: #000000; } }\r\n" "body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: var(--main-bg-color); color: var(--main-text-color); }\r\n" "a, .slide label { text-decoration: none; color: var(--main-link-color); }\r\n" - "a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n" + "a:hover, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n" "a.button { appearance: button; text-decoration: none; padding: 0 5px; border: 1px solid var(--main-link-color); }\r\n" ".header { font-size: 2.5em; text-align: center; margin: 1em 0; color: var(--main-link-color); }\r\n" ".wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n" ".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 { font-size: 1em; margin-left: 2em; padding: 4px; max-width: 50em; overflow: auto; position: absolute; left: 35%; }\r\n" + ".content { float: left; font-size: 1em; margin-left: 2em; padding: 4px; max-width: 50em; overflow: auto; }\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; position: absolute; left: 0; }\r\n" + " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%; text-align: center; }\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/I2PControl.cpp b/daemon/I2PControl.cpp index 9babce93..46a219bf 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,20 +8,31 @@ #include #include -#include #include #include // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS + +#include +#include +#include +#include #include +#include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" -#include "Tunnel.h" +#include "RouterContext.h" #include "Daemon.h" +#include "Tunnel.h" +#include "Timestamp.h" +#include "Transports.h" +#include "version.h" +#include "util.h" +#include "ClientContext.h" #include "I2PControl.h" namespace i2p @@ -29,24 +40,11 @@ namespace i2p namespace client { I2PControlService::I2PControlService (const std::string& address, int port): - m_IsRunning (false), + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), 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 @@ -57,46 +55,58 @@ 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); - 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"); - } + 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); // handlers m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlHandlers::RouterInfoHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlHandlers::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlHandlers::ClientServicesInfoHandler; + m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; + m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; + // RouterInfo + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; + m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; + m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; + // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; + + // NetworkSetting + m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; + m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; + + // ClientServicesInfo + m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; + m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; + m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; + m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; + m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; + m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () @@ -110,7 +120,7 @@ namespace client { Accept (); m_IsRunning = true; - m_Thread = std::make_unique(std::bind (&I2PControlService::Run, this)); + m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); } } @@ -119,19 +129,12 @@ namespace client if (m_IsRunning) { m_IsRunning = false; - 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_Acceptor.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); + delete m_Thread; m_Thread = nullptr; } } @@ -153,60 +156,40 @@ namespace client void I2PControlService::Accept () { - 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 + 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)); } - template - void I2PControlService::HandleAccepted (const boost::system::error_code& ecode, - std::shared_ptr newSocket) + void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { 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 ", newSocket->lowest_layer ().remote_endpoint ()); - Handshake (newSocket); - } - - template + LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ()); + Handshake (socket); + } + void I2PControlService::Handshake (std::shared_ptr socket) { socket->async_handshake(boost::asio::ssl::stream_base::server, - [this, socket](const boost::system::error_code& ecode) - { - if (ecode) - { - LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); - return; - } - ReadRequest (socket); - }); + 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); } - template void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); @@ -216,13 +199,10 @@ namespace client #else boost::asio::buffer (request->data (), request->size ()), #endif - [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) - { - HandleRequestReceived (ecode, bytes_transferred, socket, request); - }); + std::bind(&I2PControlService::HandleRequestReceived, this, + std::placeholders::_1, std::placeholders::_2, socket, request)); } - template void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) @@ -300,7 +280,37 @@ namespace client } } - template + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const + { + ss << "\"" << name << "\":" << value; + } + + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const + { + ss << "\"" << name << "\":"; + if (value.length () > 0) + { + if (quotes) + ss << "\"" << value << "\""; + else + ss << value; + } + else + ss << "null"; + } + + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const + { + ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; + } + + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const + { + std::ostringstream buf; + boost::property_tree::write_json (buf, value, false); + ss << "\"" << name << "\":" << buf.str(); + } + void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -310,12 +320,12 @@ namespace client std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; - header << "Content-Length: " << std::to_string(len) << "\r\n"; + header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; header << "Content-Type: application/json\r\n"; header << "Date: "; - 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"; + 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"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); @@ -323,11 +333,16 @@ 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 (), - [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); - }); + 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 ()); + } } // handlers @@ -381,6 +396,91 @@ namespace client m_Tokens.clear (); } +// RouterInfo + + void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + bool first = true; + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); + auto it1 = m_RouterInfoHandlers.find (it->first); + if (it1 != m_RouterInfoHandlers.end ()) + { + if (!first) results << ","; + else first = false; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); + } + } + + void I2PControlService::UptimeHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); + } + + void I2PControlService::VersionHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.version", VERSION); + } + + void I2PControlService::StatusHandler (std::ostringstream& results) + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); + } + + void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); + } + + void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); + } + + void I2PControlService::NetStatusHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); + } + + void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) + { + int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); + InsertParam (results, "i2p.router.net.tunnels.participating", transit); + } + + void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) + { + int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); + InsertParam (results, "i2p.router.net.tunnels.successrate", rate); + } + + void I2PControlService::InboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetInBandwidth (); + InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); + } + + void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth (); + InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); + } + + void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); + } + + void I2PControlService::NetTotalSentBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); + } + // RouterManager @@ -388,11 +488,10 @@ 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 (it != params.begin ()) results << ","; + if (it1 != m_RouterManagerHandlers.end ()) { (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); @@ -433,59 +532,257 @@ namespace client i2p::data::netdb.Reseed (); } +// network setting + void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); + auto it1 = m_NetworkSettingHandlers.find (it->first); + if (it1 != m_NetworkSettingHandlers.end ()) { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(it->second.data (), results); + } else + LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); + } + } + + void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.in", bw); + } + + void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.out", bw); + } + // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { 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); - 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 + 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) + // 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 { + LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); + } + EVP_PKEY_free (pkey); + } + +// ClientServicesInfo + + void I2PControlService::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) { - LogPrint (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 (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); + auto it1 = m_ClientServicesInfoHandlers.find (it->first); + if (it1 != m_ClientServicesInfoHandlers.end ()) + { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); + } + } + + void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + boost::property_tree::ptree client_tunnels, server_tunnels; + + for (auto& it: i2p::client::context.GetClientTunnels ()) { - LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); - PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); - fclose (f); + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + + auto& serverTunnels = i2p::client::context.GetServerTunnels (); + if (!serverTunnels.empty ()) { + for (auto& it: serverTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + st.put("port", it.second->GetLocalPort ()); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + } + + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + pt.add_child("client", client_tunnels); + pt.add_child("server", server_tunnels); + + InsertParam (results, "I2PTunnel", pt); + } + + void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto httpProxy = i2p::client::context.GetHttpProxy (); + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); } else - LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); - EVP_PKEY_free (pkey); + pt.put("enabled", false); + + InsertParam (results, "HTTPProxy", pt); + } + + void I2PControlService::SOCKSInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto socksProxy = i2p::client::context.GetSocksProxy (); + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "SOCKS", pt); + } + + void I2PControlService::SAMInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + pt.put("enabled", true); + boost::property_tree::ptree sam_sessions; + for (auto& it: sam->GetSessions ()) + { + boost::property_tree::ptree sam_session, sam_session_sockets; + auto& name = it.second->GetLocalDestination ()->GetNickname (); + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + sam_session.put("name", name); + sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + + for (const auto& socket: sam->ListSockets(it.first)) + { + boost::property_tree::ptree stream; + stream.put("type", socket->GetSocketType ()); + stream.put("peer", socket->GetSocket ().remote_endpoint()); + + sam_session_sockets.push_back(std::make_pair("", stream)); + } + sam_session.add_child("sockets", sam_session_sockets); + sam_sessions.add_child(it.first, sam_session); + } + + pt.add_child("sessions", sam_sessions); + } + else + pt.put("enabled", false); + + InsertParam (results, "SAM", pt); + } + + void I2PControlService::BOBInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto bob = i2p::client::context.GetBOBCommandChannel (); + if (bob) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "BOB", pt); + } + + void I2PControlService::I2CPInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto i2cp = i2p::client::context.GetI2CPServer (); + if (i2cp) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "I2CP", pt); } } } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index 83dd6549..32b8933c 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,7 +20,6 @@ #include #include #include -#include "I2PControlHandlers.h" namespace i2p { @@ -33,8 +32,10 @@ namespace client const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; - class I2PControlService: public I2PControlHandlers + class I2PControlService { + typedef boost::asio::ssl::stream ssl_socket; + public: I2PControlService (const std::string& address, int port); @@ -47,59 +48,94 @@ namespace client void Run (); void Accept (); - template - void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr newSocket); - template + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void Handshake (std::shared_ptr socket); - template + void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); - template void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); - template void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); + void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, + std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); private: + void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes = true) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; + // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); + // RouterInfo + typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); + void UptimeHandler (std::ostringstream& results); + void VersionHandler (std::ostringstream& results); + void StatusHandler (std::ostringstream& results); + void NetDbKnownPeersHandler (std::ostringstream& results); + void NetDbActivePeersHandler (std::ostringstream& results); + void NetStatusHandler (std::ostringstream& results); + void TunnelsParticipatingHandler (std::ostringstream& results); + void TunnelsSuccessRateHandler (std::ostringstream& results); + void InboundBandwidth1S (std::ostringstream& results); + void OutboundBandwidth1S (std::ostringstream& results); + void NetTotalReceivedBytes (std::ostringstream& results); + void NetTotalSentBytes (std::ostringstream& results); + // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); + // NetworkSetting + typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); + void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); + + // ClientServicesInfo + typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results); + void I2PTunnelInfoHandler (std::ostringstream& results); + void HTTPProxyInfoHandler (std::ostringstream& results); + void SOCKSInfoHandler (std::ostringstream& results); + void SAMInfoHandler (std::ostringstream& results); + void BOBInfoHandler (std::ostringstream& results); + void I2CPInfoHandler (std::ostringstream& results); + private: std::string m_Password; bool m_IsRunning; - std::unique_ptr m_Thread; + std::thread * m_Thread; - 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::io_service m_Service; + boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; std::map m_MethodHandlers; std::map m_I2PControlHandlers; + std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; + std::map m_NetworkSettingHandlers; + std::map m_ClientServicesInfoHandlers; }; } } diff --git a/daemon/I2PControlHandlers.cpp b/daemon/I2PControlHandlers.cpp deleted file mode 100644 index c94887a3..00000000 --- a/daemon/I2PControlHandlers.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* -* 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 -#define BOOST_BIND_GLOBAL_PLACEHOLDERS -#include -#include - -#include "Log.h" -#include "RouterContext.h" -#include "NetDb.hpp" -#include "Tunnel.h" -#include "Transports.h" -#include "version.h" -#include "ClientContext.h" -#include "I2PControlHandlers.h" - -namespace i2p -{ -namespace client -{ - I2PControlHandlers::I2PControlHandlers () - { - // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlHandlers::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlHandlers::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlHandlers::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlHandlers::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlHandlers::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlHandlers::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"] = &I2PControlHandlers::InboundBandwidth15S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"] = &I2PControlHandlers::OutboundBandwidth15S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlHandlers::NetStatusHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlHandlers::TunnelsSuccessRateHandler; - m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlHandlers::NetTotalReceivedBytes; - m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlHandlers::NetTotalSentBytes; - - // NetworkSetting - m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlHandlers::InboundBandwidthLimit; - m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlHandlers::OutboundBandwidthLimit; - - // ClientServicesInfo - m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlHandlers::I2PTunnelInfoHandler; - m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlHandlers::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlHandlers::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlHandlers::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlHandlers::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlHandlers::I2CPInfoHandler; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, int value) const - { - ss << "\"" << name << "\":" << value; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const - { - ss << "\"" << name << "\":"; - if (value.length () > 0) - { - if (quotes) - ss << "\"" << value << "\""; - else - ss << value; - } - else - ss << "null"; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, double value) const - { - ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const - { - std::ostringstream buf; - boost::property_tree::write_json (buf, value, false); - ss << "\"" << name << "\":" << buf.str(); - } - -// RouterInfo - - void I2PControlHandlers::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - bool first = true; - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); - auto it1 = m_RouterInfoHandlers.find (it->first); - if (it1 != m_RouterInfoHandlers.end ()) - { - if (!first) results << ","; - else first = false; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); - } - } - - void I2PControlHandlers::UptimeHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); - } - - void I2PControlHandlers::VersionHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.version", VERSION); - } - - void I2PControlHandlers::StatusHandler (std::ostringstream& results) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); - } - - void I2PControlHandlers::NetDbKnownPeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); - } - - void I2PControlHandlers::NetDbActivePeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); - } - - void I2PControlHandlers::NetStatusHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); - } - - void I2PControlHandlers::TunnelsParticipatingHandler (std::ostringstream& results) - { - int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); - InsertParam (results, "i2p.router.net.tunnels.participating", transit); - } - - void I2PControlHandlers::TunnelsSuccessRateHandler (std::ostringstream& results) - { - int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); - InsertParam (results, "i2p.router.net.tunnels.successrate", rate); - } - - void I2PControlHandlers::InboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth (); - InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); - } - - void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth15s (); - InsertParam (results, "i2p.router.net.bw.inbound.15s", bw); - } - - void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth (); - InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); - } - - void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth15s (); - InsertParam (results, "i2p.router.net.bw.outbound.15s", bw); - } - - void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); - } - - void I2PControlHandlers::NetTotalSentBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); - } - -// network setting - void I2PControlHandlers::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); - auto it1 = m_NetworkSettingHandlers.find (it->first); - if (it1 != m_NetworkSettingHandlers.end ()) { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(it->second.data (), results); - } else - LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); - } - } - - void I2PControlHandlers::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.in", bw); - } - - void I2PControlHandlers::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.out", bw); - } - -// ClientServicesInfo - - void I2PControlHandlers::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); - auto it1 = m_ClientServicesInfoHandlers.find (it->first); - if (it1 != m_ClientServicesInfoHandlers.end ()) - { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); - } - } - - void I2PControlHandlers::I2PTunnelInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - boost::property_tree::ptree client_tunnels, server_tunnels; - - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - - auto& serverTunnels = i2p::client::context.GetServerTunnels (); - if (!serverTunnels.empty ()) { - for (auto& it: serverTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - st.put("port", it.second->GetLocalPort ()); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - auto& clientForwards = i2p::client::context.GetClientForwards (); - if (!clientForwards.empty ()) - { - for (auto& it: clientForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - } - - auto& serverForwards = i2p::client::context.GetServerForwards (); - if (!serverForwards.empty ()) - { - for (auto& it: serverForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - pt.add_child("client", client_tunnels); - pt.add_child("server", server_tunnels); - - InsertParam (results, "I2PTunnel", pt); - } - - void I2PControlHandlers::HTTPProxyInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "HTTPProxy", pt); - } - - void I2PControlHandlers::SOCKSInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) - { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "SOCKS", pt); - } - - void I2PControlHandlers::SAMInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - pt.put("enabled", true); - boost::property_tree::ptree sam_sessions; - for (auto& it: sam->GetSessions ()) - { - boost::property_tree::ptree sam_session, sam_session_sockets; - auto& name = it.second->GetLocalDestination ()->GetNickname (); - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - sam_session.put("name", name); - sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - - for (const auto& socket: sam->ListSockets(it.first)) - { - boost::property_tree::ptree stream; - stream.put("type", (int)socket->GetSocketType ()); - stream.put("peer", socket->GetSocket ().remote_endpoint()); - - sam_session_sockets.push_back(std::make_pair("", stream)); - } - sam_session.add_child("sockets", sam_session_sockets); - sam_sessions.add_child(it.first, sam_session); - } - - pt.add_child("sessions", sam_sessions); - } - else - pt.put("enabled", false); - - InsertParam (results, "SAM", pt); - } - - void I2PControlHandlers::BOBInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto bob = i2p::client::context.GetBOBCommandChannel (); - if (bob) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "BOB", pt); - } - - void I2PControlHandlers::I2CPInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto i2cp = i2p::client::context.GetI2CPServer (); - if (i2cp) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "I2CP", pt); - } -} -} diff --git a/daemon/I2PControlHandlers.h b/daemon/I2PControlHandlers.h deleted file mode 100644 index d106f288..00000000 --- a/daemon/I2PControlHandlers.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -* Copyright (c) 2013-2022, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef I2P_CONTROL_HANDLERS_H__ -#define I2P_CONTROL_HANDLERS_H__ - -#include -#include -#include -#include - -namespace i2p -{ -namespace client -{ - class I2PControlHandlers - { - public: - - I2PControlHandlers (); - - // methods - // TODO: make protected - void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - - protected: - - void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes = true) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; - - private: - - // RouterInfo - typedef void (I2PControlHandlers::*RouterInfoRequestHandler)(std::ostringstream& results); - void UptimeHandler (std::ostringstream& results); - void VersionHandler (std::ostringstream& results); - void StatusHandler (std::ostringstream& results); - void NetDbKnownPeersHandler (std::ostringstream& results); - void NetDbActivePeersHandler (std::ostringstream& results); - void NetStatusHandler (std::ostringstream& results); - void TunnelsParticipatingHandler (std::ostringstream& results); - void TunnelsSuccessRateHandler (std::ostringstream& results); - void InboundBandwidth1S (std::ostringstream& results); - void InboundBandwidth15S (std::ostringstream& results); - void OutboundBandwidth1S (std::ostringstream& results); - void OutboundBandwidth15S (std::ostringstream& results); - void NetTotalReceivedBytes (std::ostringstream& results); - void NetTotalSentBytes (std::ostringstream& results); - - // NetworkSetting - typedef void (I2PControlHandlers::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); - void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); - void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); - - // ClientServicesInfo - typedef void (I2PControlHandlers::*ClientServicesInfoRequestHandler)(std::ostringstream& results); - void I2PTunnelInfoHandler (std::ostringstream& results); - void HTTPProxyInfoHandler (std::ostringstream& results); - void SOCKSInfoHandler (std::ostringstream& results); - void SAMInfoHandler (std::ostringstream& results); - void BOBInfoHandler (std::ostringstream& results); - void I2CPInfoHandler (std::ostringstream& results); - - private: - - std::map m_RouterInfoHandlers; - std::map m_NetworkSettingHandlers; - std::map m_ClientServicesInfoHandlers; - }; -} -} - -#endif diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 8e6dbcf6..6ea4dc24 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,15 +1,10 @@ -/* -* 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" @@ -52,7 +47,7 @@ namespace transport { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: Starting"); - boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); + m_Service.post (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 @@ -115,16 +110,10 @@ 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) { @@ -132,7 +121,6 @@ namespace transport return; } else -#endif { LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) @@ -150,7 +138,7 @@ namespace transport // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); - i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress)); + i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); // port mapping PortMapping (); } @@ -171,18 +159,17 @@ namespace transport void UPnP::PortMapping () { - auto a = context.GetRouterInfo().GetAddresses(); - if (!a) return; - for (const auto& address : *a) + const auto& a = context.GetRouterInfo().GetAddresses(); + for (const auto& address : a) { - if (address && !address->host.is_v6 () && address->port) + if (!address->host.is_v6 () && address->port) TryPortMapping (address); } - m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes + m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) - PortMapping (); + PortMapping (); }); } @@ -223,11 +210,10 @@ namespace transport void UPnP::CloseMapping () { - auto a = context.GetRouterInfo().GetAddresses(); - if (!a) return; - for (const auto& address : *a) + const auto& a = context.GetRouterInfo().GetAddresses(); + for (const auto& address : a) { - if (address && !address->host.is_v6 () && address->port) + if (!address->host.is_v6 () && address->port) CloseMapping (address); } } @@ -262,10 +248,10 @@ namespace transport { switch (address->transportStyle) { - case i2p::data::RouterInfo::eTransportNTCP2: + case i2p::data::RouterInfo::eTransportNTCP: return "TCP"; break; - case i2p::data::RouterInfo::eTransportSSU2: + case i2p::data::RouterInfo::eTransportSSU: default: return "UDP"; } diff --git a/daemon/UPnP.h b/daemon/UPnP.h index 2a5fe9f3..59f3b785 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,8 +28,7 @@ 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, @@ -67,7 +66,7 @@ namespace transport std::unique_ptr m_Thread; std::condition_variable m_Started; std::mutex m_StartedMutex; - boost::asio::io_context m_Service; + boost::asio::io_service 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 0c182afe..563a0f54 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,8 +24,6 @@ #include "Tunnel.h" #include "RouterContext.h" #include "ClientContext.h" -#include "Transports.h" -#include "util.h" void handle_signal(int sig) { @@ -56,14 +54,6 @@ void handle_signal(int sig) case SIGPIPE: LogPrint(eLogInfo, "SIGPIPE received"); break; - case SIGTSTP: - LogPrint(eLogInfo, "Daemon: Got SIGTSTP, disconnecting from network..."); - i2p::transport::transports.SetOnline(false); - break; - case SIGCONT: - LogPrint(eLogInfo, "Daemon: Got SIGCONT, restoring connection to network..."); - i2p::transport::transports.SetOnline(true); - break; } } @@ -71,7 +61,7 @@ namespace i2p { namespace util { - bool DaemonUnix::start() + bool DaemonLinux::start() { if (isDaemon) { @@ -163,36 +153,24 @@ 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(eLogCritical, "Daemon: Could not write pidfile ", pidfile, ": ", strerror(errno)); + LogPrint(eLogError, "Daemon: Could not write pidfile ", pidfile, ": ", strerror(errno)); std::cerr << "i2pd: Could not write pidfile " << pidfile << ": " << strerror(errno) << std::endl; return false; } } gracefulShutdownInterval = 0; // not specified - // handle signal TSTP - bool handleTSTP; i2p::config::GetOption("unix.handle_sigtstp", handleTSTP); - // Signal handler struct sigaction sa; sa.sa_handler = handle_signal; @@ -204,24 +182,18 @@ namespace i2p sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGPIPE, &sa, 0); - if (handleTSTP) - { - sigaction(SIGTSTP, &sa, 0); - sigaction(SIGCONT, &sa, 0); - } return Daemon_Singleton::start(); } - bool DaemonUnix::stop() + bool DaemonLinux::stop() { i2p::fs::Remove(pidfile); return Daemon_Singleton::stop(); } - void DaemonUnix::run () + void DaemonLinux::run () { - i2p::util::SetThreadName ("i2pd-daemon"); while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); diff --git a/debian/NEWS b/debian/NEWS deleted file mode 100644 index c0add110..00000000 --- a/debian/NEWS +++ /dev/null @@ -1,5 +0,0 @@ -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 2267246d..53129046 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,148 +1,3 @@ -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 - - -- orignal Wed, 15 Feb 2023 19:00:00 +0000 - -i2pd (2.45.1-1) unstable; urgency=medium - - * updated to version 2.45.1/0.9.57 - - -- orignal Wed, 11 Jan 2023 19:00:00 +0000 - -i2pd (2.45.0-1) unstable; urgency=high - - * updated to version 2.45.0/0.9.57 - * compat level 12 - * standards version 4.3.0 - * increased nofile limit in service and init.d to 8192 - * added conffiles - * removed #1210 patch - - -- r4sas Tue, 3 Jan 2023 18:00:00 +0000 - -i2pd (2.44.0-1) unstable; urgency=medium - - * updated to version 2.44.0/0.9.56 - - -- orignal Sun, 20 Nov 2022 19:00:00 +0000 - -i2pd (2.43.0-1) unstable; urgency=medium - - * updated to version 2.43.0/0.9.55 - - -- orignal Mon, 22 Aug 2022 16:00:00 +0000 - -i2pd (2.42.1-1) unstable; urgency=medium - - * updated to version 2.42.1/0.9.54 - * remove -O3 optimization flag - - -- r4sas Tue, 24 May 2022 12:00:00 +0000 - i2pd (2.42.0-1) unstable; urgency=medium * updated to version 2.42.0/0.9.54 diff --git a/debian/compat b/debian/compat index 48082f72..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -12 +9 diff --git a/debian/control b/debian/control index 48f5e680..318463bc 100644 --- a/debian/control +++ b/debian/control @@ -2,8 +2,8 @@ Source: i2pd Section: net Priority: optional Maintainer: r4sas -Build-Depends: debhelper (>= 12~), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 4.3.0 +Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev +Standards-Version: 3.9.8 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd diff --git a/debian/copyright b/debian/copyright index 352e260b..73df9da0 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,18 +3,18 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2023 PurpleI2P +Copyright: 2013-2020 PurpleI2P License: BSD-3-clause Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen - 2016-2023 R4SAS + 2016-2020 R4SAS 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause - Copyright (c) 2013-2023, The PurpleI2P Project + Copyright (c) 2013-2017, The PurpleI2P Project . All rights reserved. . diff --git a/debian/i2pd.1 b/debian/i2pd.1 index 42c282d9..5145321f 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 ipv4 (\fIenabled\fR by default) +Enable communication through ipv6 (\fIenabled\fR by default) .TP \fB\-\-ipv6\fR Enable communication through ipv6 (\fIdisabled\fR by default) diff --git a/debian/i2pd.default b/debian/i2pd.default index 90392ede..bd1d073f 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -8,4 +8,4 @@ I2PD_ENABLED="yes" DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this -ulimit -n 8192 +ulimit -n 4096 diff --git a/debian/i2pd.init b/debian/i2pd.init index 9b5f7669..33fd80a5 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/bin/$NAME # Introduce the server's location here +DAEMON=/usr/sbin/$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 bde52854..6eb6c8c2 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,6 +1,7 @@ -i2pd usr/bin/ +i2pd usr/sbin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ +contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/ -contrib/apparmor/usr.bin.i2pd etc/apparmor.d +contrib/apparmor/usr.sbin.i2pd etc/apparmor.d diff --git a/debian/i2pd.links b/debian/i2pd.links index 16558791..a149967f 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,4 +1,5 @@ -etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf +etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf +etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d usr/share/i2pd/certificates var/lib/i2pd/certificates diff --git a/debian/patches/01-fix-1210.patch b/debian/patches/01-fix-1210.patch new file mode 100644 index 00000000..9ad9bb0f --- /dev/null +++ b/debian/patches/01-fix-1210.patch @@ -0,0 +1,27 @@ +Description: fix #1210 + Disables two options, which not presented in old systemd versions +Author: r4sas + +Bug: https://github.com/PurpleI2P/i2pd/issues/1210 +Reviewed-By: r4sas +Last-Update: 2020-05-25 + +Index: i2pd/contrib/i2pd.service +=================================================================== +--- i2pd.orig/contrib/i2pd.service ++++ i2pd/contrib/i2pd.service +@@ -6,10 +6,10 @@ After=network.target + [Service] + User=i2pd + Group=i2pd +-RuntimeDirectory=i2pd +-RuntimeDirectoryMode=0700 +-LogsDirectory=i2pd +-LogsDirectoryMode=0700 ++#RuntimeDirectory=i2pd ++#RuntimeDirectoryMode=0700 ++#LogsDirectory=i2pd ++#LogsDirectoryMode=0700 + Type=forking + ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service + ExecReload=/bin/sh -c "kill -HUP $MAINPID" diff --git a/debian/patches/01-upnp.patch b/debian/patches/01-upnp.patch deleted file mode 100644 index 74d36c06..00000000 --- a/debian/patches/01-upnp.patch +++ /dev/null @@ -1,17 +0,0 @@ -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/01-upnp.patch b/debian/patches/02-upnp.patch similarity index 80% rename from contrib/debian/xenial/patches/01-upnp.patch rename to debian/patches/02-upnp.patch index 74d36c06..bec8f2b0 100644 --- a/contrib/debian/xenial/patches/01-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -2,13 +2,13 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2024-12-30 +Last-Update: 2022-03-23 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -31,7 +31,7 @@ # import source files lists - include filelist.mk +@@ -31,7 +31,7 @@ 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/debian/patches/series b/debian/patches/series index f97fdf65..2f816712 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1,2 @@ -01-upnp.patch +01-fix-1210.patch +02-upnp.patch diff --git a/debian/rules b/debian/rules index fc769066..dbe904f1 100755 --- a/debian/rules +++ b/debian/rules @@ -1,13 +1,18 @@ #!/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_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -O3 + export DEB_LDFLAGS_MAINT_APPEND = + %: - dh $@ + dh $@ --parallel override_dh_auto_install: diff --git a/debian/watch b/debian/watch index 2ec6c29b..64367c81 100644 --- a/debian/watch +++ b/debian/watch @@ -1,4 +1,3 @@ -version=4 -opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ +version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ https://github.com/PurpleI2P/i2pd/tags \ (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index 0afa086f..5860facf 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,10 +29,7 @@ namespace afrikaans // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { {"failed", "Het misluk"}, {"unknown", "onbekend"}, @@ -67,16 +64,16 @@ namespace afrikaans // language namespace static std::map> plurals { - {"%d days", {"%d dag", "%d dae"}}, - {"%d hours", {"%d uur", "%d ure"}}, - {"%d minutes", {"%d minuut", "%d minute"}}, - {"%d seconds", {"%d seconde", "%d sekondes"}}, + {"days", {"dag", "dae"}}, + {"hours", {"uur", "ure"}}, + {"minutes", {"minuut", "minute"}}, + {"seconds", {"seconde", "sekondes"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp index af22d0d9..586e7579 100644 --- a/i18n/Armenian.cpp +++ b/i18n/Armenian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,17 @@ namespace armenian // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f ԿիԲ"}, - {"%.2f MiB", "%.2f ՄիԲ"}, - {"%.2f GiB", "%.2f ԳիԲ"}, + {"KiB", "ԿիԲ"}, + {"MiB", "ՄիԲ"}, + {"GiB", "ԳիԲ"}, {"building", "կառուցվում է"}, {"failed", "Անհաջող"}, {"expiring", "Լրանում է"}, {"established", "կարգավոյված է"}, {"unknown", "անհայտ"}, {"exploratory", "հետազոտոկան"}, - {"Purple I2P Webconsole", "Վեբ-կոնսոլ Purple I2P"}, {"i2pd webconsole", "Վեբ-կոնսոլ i2pd"}, {"Main page", "Գլխավոր էջ"}, {"Router commands", "Երթուղիչի հրահանգներ"}, @@ -61,10 +57,10 @@ namespace armenian // language namespace {"Unknown", "Անհայտ"}, {"Proxy", "Պրոկսի"}, {"Mesh", "MESH-ցանց"}, + {"Error", "Սխալ"}, {"Clock skew", "Ոչ ճշգրիտ ժամանակ"}, {"Offline", "Օֆլայն"}, {"Symmetric NAT", "Սիմետրիկ NAT"}, - {"Full cone NAT", "Full cone NAT"}, {"Uptime", "Առկայություն"}, {"Network status", "Ցանցի կարգավիճակ"}, {"Network status v6", "Ցանցի կարգավիճակ v6"}, @@ -72,7 +68,7 @@ namespace armenian // language namespace {"Family", "Խմբատեսակ"}, {"Tunnel creation success rate", "Հաջողությամբ կառուցված թունելներ"}, {"Received", "Ստացվել է"}, - {"%.2f KiB/s", "%.2f ԿիԲ/վ"}, + {"KiB/s", "ԿիԲ/վ"}, {"Sent", "Ուղարկվել է"}, {"Transit", "Տարանցում"}, {"Data path", "Տվյալների ուղին"}, @@ -93,12 +89,12 @@ namespace armenian // language namespace {"Address registration line", "Հասցեի գրանցման տող"}, {"Domain", "Տիրույթ"}, {"Generate", "Գեներացնել"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Նշում. արդյունքի տողը կարող է օգտագործվել միայն 2LD տիրույթներ գրանցելու համար (example.i2p): Ենթատիրույթներ գրանցելու համար խնդրում ենք օգտագործել i2pd-tools գործիքակազմը:"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Նշում. արդյունքի տողը կարող է օգտագործվել միայն 2LD տիրույթներ գրանցելու համար (example.i2p): Ենթատիրույթներ գրանցելու համար խնդրում ենք օգտագործել i2pd-tools գործիքակազմը"}, {"Address", "Հասցե"}, {"Type", "Տեսակը"}, {"EncType", "Գաղտնագրի տեսակը"}, {"Inbound tunnels", "Մուտքային թունելներ"}, - {"%dms", "%dմլվ"}, + {"ms", "մլվ"}, {"Outbound tunnels", "Ելքային թունելներ"}, {"Tags", "Թեգեր"}, {"Incoming", "Մուտքային"}, @@ -116,10 +112,11 @@ namespace armenian // language namespace {"Invalid", "Անվավեր"}, {"Store type", "Պահեստավորման տեսակը"}, {"Expires", "Սպառվում է"}, - {"Non Expired Leases", "Չսպառված Lease-եր"}, + {"Non Expired Leases", "Չսպառված Lease-եր"}, {"Gateway", "Դարպաս"}, {"TunnelID", "Թունելի ID"}, {"EndDate", "Ավարտ"}, + {"not floodfill", "ոչ floodfill-ներ"}, {"Queue size", "Հերթի չափսը"}, {"Run peer test", "Գործարկել փորձարկումը"}, {"Decline transit tunnels", "Մերժել տարանցիկ թունելներ"}, @@ -149,7 +146,10 @@ namespace armenian // language namespace {"Destination not found", "Հասցեի վայրը չի գտնվել"}, {"StreamID can't be null", "StreamID-ն չի կարող լինել դատարկ"}, {"Return to destination page", "Վերադառնալ նախորդ էջի հասցե"}, + {"You will be redirected in 5 seconds", "Դուք կտեղափոխվեք 5 վայրկյանից"}, + {"Transit tunnels count must not exceed 65535", "Տարանցիկ թունելների քանակը չպետք է գերազանցի 65535-ը"}, {"Back to commands list", "Վերադառնալ հրահանգների ցուցակ"}, + {"Register at reg.i2p", "Գրանցել reg.i2p-ում"}, {"Description", "Նկարագրություն"}, {"A bit information about service on domain", "Մի փոքր տեղեկատվություն տիրոիյթում գտնվող ծառայության մասին"}, {"Submit", "Ուղարկվել"}, @@ -165,41 +165,49 @@ namespace armenian // language namespace {"You may try to find this host on jump services below", "Ստորև Դուք կարող եք գտնել այս հոսթը jump ծառայությունների միջոցով"}, {"Invalid request", "Սխալ հարցում"}, {"Proxy unable to parse your request", "Պրոկսին չի կարող հասկանալ Ձեր հարցումը"}, - {"Invalid request URI", "Սխալ ձևավորված URI հարցում"}, + {"addresshelper is not supported", "addresshelper-ը համատեղելի չէ"}, + {"Host", "Հոսթ"}, + {"added to router's addressbook from helper", "Ավելացված է երթուղիչի հասցեագրքում helper-ի միջոցով"}, + {"Click here to proceed:", "Շարունակելու համար սեղմեք այստեղ"}, + {"Continue", "Շարունակել"}, + {"Addresshelper found", "addresshelper-ը գնտված է"}, + {"already in router's addressbook", "արդեն առկա է երթուղիչի հասցեագրքում"}, + {"Click here to update record:", "Սեղմեկ այստեղ որպեսզի թարվացնեք գրառումը"}, + {"invalid request uri", "Սխալ ձևավորված URI հարցում"}, {"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 պրոկսիի հետ"}, + {"bad outproxy settings", "Սխալ արտաքին պրոկսի կարգավորումներ"}, + {"not inside I2P network, but outproxy is not enabled", "Հարցումը I2P ցանցից դուրս է, բայց արտաքին պրոքսին միացված չէ"}, + {"unknown outproxy url", "արտաքին պրոքսիի անհայտ URL"}, + {"cannot resolve upstream proxy", "Չհաջողվեց որոշել վերադաս պրոկսին"}, + {"hostname too long", "Հոսթի անունը չափազանց երկար է"}, + {"cannot connect to upstream socks proxy", "չհաջողվեց միանալ վերադաս socks պրոկսիին"}, + {"Cannot negotiate with socks proxy", "Չհաջողվեց պայմանավորվել վերադաս socks պրոկսիի հետ"}, {"CONNECT error", "Սխալ CONNECT հարցում"}, - {"Failed to connect", "Միանալ չhաջողվեց"}, - {"SOCKS proxy error", "Սխալ SOCKS պրոկսի"}, - {"Failed to send request to upstream", "Չհաջողվեց հարցումն ուղարկել վերադաս պրոկսիին"}, - {"No reply from SOCKS proxy", "Բացակայում է պատասխանը SOCKS պրոկսի սերվերի կողմից"}, - {"Cannot connect", "Հնարավոր չե միանալ"}, - {"HTTP out proxy not implemented", "Արտաքին HTTP պրոկսին դեռ իրականացված չէ"}, - {"Cannot connect to upstream HTTP proxy", "Չհաջողվեց միանալ վերադաս HTTP պրոկսի սերվերին"}, + {"Failed to Connect", "Միանալ չhաջողվեց"}, + {"socks proxy error", "Սխալ SOCKS պրոկսի"}, + {"failed to send request to upstream", "Չհաջողվեց հարցումն ուղարկել վերադաս պրոկսիին"}, + {"No Reply From socks proxy", "Բացակայում է պատասխանը SOCKS պրոկսի սերվերի կողմից"}, + {"cannot connect", "Հնարավոր չե միանալ"}, + {"http out proxy not implemented", "Արտաքին http պրոկսին դեռ իրականացված չէ"}, + {"cannot connect to upstream http proxy", "Չհաջողվեց միանալ վերադաս http պրոկսի սերվերին"}, {"Host is down", "Հոսթն անհասանելի է"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Հոսթի հետ կապը հաստատել չհաջողվեց, հնարավոր է այն անջատված է, փորձեք միանալ քիչ ուշ:"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Հոսթի հետ կապը հաստատել չհաջողվեց, հնարավոր է այն անջատված է, փորձեք միանալ քիչ ուշ"}, {"", ""}, }; static std::map> plurals { - {"%d days", {"%d օր", "%d օր"}}, - {"%d hours", {"%d ժամ", "%d ժամ"}}, - {"%d minutes", {"%d րոպե", "%d րոպե"}}, - {"%d seconds", {"%d վարկյան", "%d վարկյան"}}, + {"days", {"օր", "օր"}}, + {"hours", {"ժամ", "ժամ"}}, + {"minutes", {"րոպե", "րոպե"}}, + {"seconds", {"վարկյան", "վարկյան"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp deleted file mode 100644 index f439bb67..00000000 --- a/i18n/Chinese.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Simplified Chinese localization file - -namespace i2p -{ -namespace i18n -{ -namespace chinese // language namespace -{ - // language name in lowercase - static std::string language = "chinese"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return 0; - } - - // 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", "正在构建"}, - {"failed", "连接失败"}, - {"expiring", "即将过期"}, - {"established", "连接成功"}, - {"unknown", "未知"}, - {"exploratory", "探索"}, - {"Purple I2P Webconsole", "Purple 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", "IPv6 网络状态"}, - {"Stopping in", "距停止还有:"}, - {"Family", "家族"}, - {"Tunnel creation success rate", "隧道创建成功率"}, - {"Total tunnel creation success rate", "当前隧道创建成功率"}, - {"Received", "已接收"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"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.", "注意: 结果字符串只能用于注册二级域名(例如:example.i2p)。若需注册三级域名,请使用 i2pd-tools。"}, - {"Address", "地址"}, - {"Type", "类型"}, - {"EncType", "加密类型"}, - {"Expire LeaseSet", "到期租约集"}, - {"Inbound tunnels", "入站隧道"}, - {"%dms", "%dms"}, - {"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", "隧道 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", "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 注册域名"}, - {"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 已在路由地址簿中请注意:此地址的来源可能是有害的!点击此处更新记录:继续"}, - {"Addresshelper forced update rejected", "地址助手强制更新被拒绝"}, - {"To add host %s in router's addressbook, click here: Continue.", "若要在路由器地址簿中添加主机 %s 请点击这里: 继续"}, - {"Addresshelper request", "请求地址助手"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "主机 %s 已通过地址助手添加到路由地址簿中。点击此处继续:继续"}, - {"Addresshelper adding", "正在添加地址助手"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "主机 %s 已在路由地址簿中。点击此处更新记录:继续"}, - {"Addresshelper update", "更新地址助手"}, - {"Invalid request URI", "无效的 URI 请求"}, - {"Can't detect destination host from request", "无法从请求中检测到目标主机"}, - {"Outproxy failure", "出口代理故障"}, - {"Bad outproxy settings", "错误的出口代理设置"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "主机 %s 不在 I2P 网络内,但出口代理未启用"}, - {"Unknown outproxy URL", "未知的出口代理地址"}, - {"Cannot resolve upstream proxy", "无法解析上游代理"}, - {"Hostname is too long", "主机名过长"}, - {"Cannot connect to upstream SOCKS proxy", "无法连接到上游 SOCKS 代理"}, - {"Cannot negotiate with SOCKS proxy", "无法与 SOCKS 代理协商"}, - {"CONNECT error", "连接错误"}, - {"Failed to connect", "连接失败"}, - {"SOCKS proxy error", "SOCKS 代理错误"}, - {"Failed to send request to upstream", "向上游发送请求失败"}, - {"No reply from SOCKS proxy", "没有来自 SOCKS 代理的回复"}, - {"Cannot connect", "无法连接"}, - {"HTTP out proxy not implemented", "HTTP 出口代理未实现"}, - {"Cannot connect to upstream HTTP proxy", "无法连接到上游 HTTP 代理"}, - {"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 hours", {"%d 小时"}}, - {"%d minutes", {"%d 分钟"}}, - {"%d seconds", {"%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/Czech.cpp b/i18n/Czech.cpp deleted file mode 100644 index c9697f41..00000000 --- a/i18n/Czech.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Czech localization file - -namespace i2p -{ -namespace i18n -{ -namespace czech // language namespace -{ - // language name in lowercase - static std::string language = "czech"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; - } - - // 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", "vyprší platnost"}, - {"established", "vytvořeno"}, - {"unknown", "neznámý"}, - {"exploratory", "průzkumné"}, - {"Purple I2P Webconsole", "Purple I2P webová konzole"}, - {"i2pd webconsole", "i2pd webová konzole"}, - {"Main page", "Hlavní stránka"}, - {"Router commands", "Router příkazy"}, - {"Local Destinations", "Místní cíle"}, - {"LeaseSets", "Sety pronájmu"}, - {"Tunnels", "Tunely"}, - {"Transit Tunnels", "Tranzitní tunely"}, - {"Transports", "Transporty"}, - {"I2P tunnels", "I2P tunely"}, - {"SAM sessions", "SAM relace"}, - {"ERROR", "CHYBA"}, - {"OK", "OK"}, - {"Testing", "Testuji"}, - {"Firewalled", "Za Firewallem"}, - {"Unknown", "Neznámý"}, - {"Proxy", "Proxy"}, - {"Mesh", "Síť"}, - {"Clock skew", "Časová nesrovnalost"}, - {"Offline", "Offline"}, - {"Symmetric NAT", "Symetrický NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Žádné popisovače"}, - {"Uptime", "Doba provozu"}, - {"Network status", "Stav sítě"}, - {"Network status v6", "Stav sítě v6"}, - {"Stopping in", "Zastavuji za"}, - {"Family", "Rodina"}, - {"Tunnel creation success rate", "Úspěšnost vytváření tunelů"}, - {"Total tunnel creation success rate", "Celková míra úspěšnosti vytváření tunelů"}, - {"Received", "Přijato"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Odesláno"}, - {"Transit", "Tranzit"}, - {"Data path", "Cesta k datovým souborům"}, - {"Hidden content. Press on text to see.", "Skrytý obsah. Pro zobrazení klikněte sem."}, - {"Router Ident", "Routerová Identita"}, - {"Router Family", "Rodina routerů"}, - {"Router Caps", "Omezení Routerů"}, - {"Version", "Verze"}, - {"Our external address", "Naše externí adresa"}, - {"supported", "podporováno"}, - {"Routers", "Routery"}, - {"Floodfills", "Floodfilly"}, - {"Client Tunnels", "Klientské tunely"}, - {"Services", "Služby"}, - {"Enabled", "Zapnuto"}, - {"Disabled", "Vypnuto"}, - {"Encrypted B33 address", "Šifrovaná adresa B33"}, - {"Address registration line", "Registrační řádek adresy"}, - {"Domain", "Doména"}, - {"Generate", "Vygenerovat"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Poznámka: výsledný řetězec může být použit pouze pro registraci 2LD domén (example.i2p). Pro registraci subdomén použijte prosím i2pd-tools."}, - {"Address", "Adresa"}, - {"Type", "Typ"}, - {"EncType", "EncType"}, - {"Expire LeaseSet", "Zrušit platnost setu pronájmu"}, - {"Inbound tunnels", "Příchozí tunely"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Odchozí tunely"}, - {"Tags", "Štítky"}, - {"Incoming", "Příchozí"}, - {"Outgoing", "Odchozí"}, - {"Destination", "Destinace"}, - {"Amount", "Množství"}, - {"Incoming Tags", "Příchozí štítky"}, - {"Tags sessions", "Relace štítků"}, - {"Status", "Stav"}, - {"Local Destination", "Místní cíl"}, - {"Streams", "Toky"}, - {"Close stream", "Uzavřít tok"}, - {"Such destination is not found", "Takováto destinace nebyla nalezena"}, - {"I2CP session not found", "I2CP relace nenalezena"}, - {"I2CP is not enabled", "I2CP není zapnuto"}, - {"Invalid", "Neplatný"}, - {"Store type", "Druh uložení"}, - {"Expires", "Vyprší"}, - {"Non Expired Leases", "Pronájmy, kterým nevypršela platnost"}, - {"Gateway", "Brána"}, - {"TunnelID", "ID tunelu"}, - {"EndDate", "Datum ukončení"}, - {"floodfill mode is disabled", "režim floodfill je vypnut"}, - {"Queue size", "Velikost fronty"}, - {"Run peer test", "Spustit peer test"}, - {"Reload tunnels configuration", "Znovu načíst nastavení tunelů"}, - {"Decline transit tunnels", "Odmítnout tranzitní tunely"}, - {"Accept transit tunnels", "Přijmout tranzitní tunely"}, - {"Cancel graceful shutdown", "Zrušit hladké vypnutí"}, - {"Start graceful shutdown", "Zahájit hladké vypnutí"}, - {"Force shutdown", "Vynutit vypnutí"}, - {"Reload external CSS styles", "Znovu načíst externí CSS"}, - {"Note: any action done here are not persistent and not changes your config files.", "Poznámka: žádná vykonaná akce zde není trvalá a nemění konfigurační soubory."}, - {"Logging level", "Úroveň logování"}, - {"Transit tunnels limit", "Limit tranzitních tunelů"}, - {"Change", "Změnit"}, - {"Change language", "Změnit jazyk"}, - {"no transit tunnels currently built", "Žádný tranzitní tunel není momentálně vytvořen"}, - {"SAM disabled", "SAM vypnutý"}, - {"no sessions currently running", "Momentálně nejsou spuštěné žádné relace"}, - {"SAM session not found", "SAM relace nenalezena"}, - {"SAM Session", "SAM Relace"}, - {"Server Tunnels", "Server Tunely"}, - {"Client Forwards", "Přesměrování Klienta"}, - {"Server Forwards", "Přesměrování Serveru"}, - {"Unknown page", "Neznámá stránka"}, - {"Invalid token", "Neplatný token"}, - {"SUCCESS", "ÚSPĚCH"}, - {"Stream closed", "Tok uzavřen"}, - {"Stream not found or already was closed", "Tok nenalezen nebo byl již uzavřen"}, - {"Destination not found", "Destinace nenalezena"}, - {"StreamID can't be null", "StreamID nemůže být null"}, - {"Return to destination page", "Zpět na stránku destinací"}, - {"You will be redirected in %d seconds", "Budete přesměrováni za %d sekund"}, - {"LeaseSet expiration time updated", "Aktualizován čas vypršení platnosti setu pronájmu"}, - {"LeaseSet is not found or already expired", "Set pronájmu není k nalezení nebo již vypršela jeho platnost"}, - {"Transit tunnels count must not exceed %d", "Počet tranzitních tunelů nesmí překročit %d"}, - {"Back to commands list", "Zpět na seznam příkazů"}, - {"Register at reg.i2p", "Zaregistrovat na reg.i2p"}, - {"Description", "Popis"}, - {"A bit information about service on domain", "Trochu informací o službě na doméně"}, - {"Submit", "Odeslat"}, - {"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"}, - {"Domain must end with .i2p", "Doména musí končit s .i2p"}, - {"Unknown command", "Neznámý příkaz"}, - {"Command accepted", "Příkaz přijat"}, - {"Proxy error", "Chyba proxy serveru"}, - {"Proxy info", "Proxy informace"}, - {"Proxy error: Host not found", "Chyba proxy serveru: Hostitel nenalezen"}, - {"Remote host not found in router's addressbook", "Vzdálený hostitel nebyl nalezen v adresáři routeru"}, - {"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"}, - {"Invalid request", "Neplatný požadavek"}, - {"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"}, - {"Addresshelper is not supported", "Addresshelper není podporován"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Buďte opatrní: zdroj této URL může být škodlivý! Klikněte zde pro aktualizaci záznamu: Pokračovat."}, - {"Addresshelper forced update rejected", "Addresshelperem vynucená aktualizace zamítnuta"}, - {"To add host %s in router's addressbook, click here: Continue.", "Pro přidání hostitele %s do adresáře routeru, klikněte zde: Pokračovat."}, - {"Addresshelper request", "Požadavek Addresshelperu"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Hostitel %s přidán do adresáře routeru od pomocníka. Klikněte zde pro pokračování: Pokračovat."}, - {"Addresshelper adding", "Addresshelper přidávání"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Klikněte zde pro aktualizaci záznamu: Pokračovat."}, - {"Addresshelper update", "Addresshelper aktualizace"}, - {"Invalid request URI", "Neplatný URI požadavek"}, - {"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"}, - {"Outproxy failure", "Outproxy selhání"}, - {"Bad outproxy settings", "Špatné outproxy nastavení"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Hostitel %s není uvnitř I2P sítě a outproxy není nastavena"}, - {"Unknown outproxy URL", "Neznámá outproxy URL"}, - {"Cannot resolve upstream proxy", "Nelze rozluštit upstream proxy server"}, - {"Hostname is too long", "Název hostitele je příliš dlouhý"}, - {"Cannot connect to upstream SOCKS proxy", "Nelze se připojit k upstream SOCKS proxy serveru"}, - {"Cannot negotiate with SOCKS proxy", "Nelze vyjednávat se SOCKS proxy serverem"}, - {"CONNECT error", "Chyba PŘIPOJENÍ"}, - {"Failed to connect", "Připojení se nezdařilo"}, - {"SOCKS proxy error", "Chyba SOCKS proxy serveru"}, - {"Failed to send request to upstream", "Odeslání žádosti upstream serveru se nezdařilo"}, - {"No reply from SOCKS proxy", "Žádná odpověď od SOCKS proxy serveru"}, - {"Cannot connect", "Nelze se připojit"}, - {"HTTP out proxy not implemented", "HTTP out proxy není implementován"}, - {"Cannot connect to upstream HTTP proxy", "Nelze se připojit k upstream HTTP proxy serveru"}, - {"Host is down", "Hostitel je nedostupný"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Připojení k požadovanému hostiteli nelze vytvořit, může být nedostupný. Zkuste to, prosím, znovu později."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}}, - {"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}}, - {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, - {"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}}, - {"", {"", "", "", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/English.cpp b/i18n/English.cpp index eb7067e2..2670e984 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,10 +30,7 @@ namespace english // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { {"", ""}, }; @@ -45,7 +42,7 @@ namespace english // language namespace std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/French.cpp b/i18n/French.cpp index 2618772f..d41d215b 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2025, The PurpleI2P Project +* Copyright (c) 2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,196 +29,72 @@ namespace french // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f Kio"}, - {"%.2f MiB", "%.2f Mio"}, - {"%.2f GiB", "%.2f Gio"}, + {"KiB", "Kio"}, + {"MiB", "Mio"}, + {"GiB", "Gio"}, {"building", "En construction"}, - {"failed", "échoué"}, + {"failed", "echoué"}, {"expiring", "expiré"}, {"established", "établi"}, {"unknown", "inconnu"}, {"exploratory", "exploratoire"}, - {"Purple I2P Webconsole", "Console web Purple I2P"}, {"i2pd webconsole", "Console web i2pd"}, {"Main page", "Page principale"}, {"Router commands", "Commandes du routeur"}, - {"Local Destinations", "Destinatioans localeAlger"}, - {"LeaseSets", "Jeu de baux"}, + {"Local Destinations", "Destinations locales"}, {"Tunnels", "Tunnels"}, {"Transit Tunnels", "Tunnels transitoires"}, - {"Transports", "Transports"}, {"I2P tunnels", "Tunnels I2P"}, {"SAM sessions", "Sessions SAM"}, {"ERROR", "ERREUR"}, {"OK", "OK"}, - {"Testing", "Test en cours"}, {"Firewalled", "Derrière un pare-feu"}, - {"Unknown", "Inconnu"}, - {"Proxy", "Proxy"}, - {"Mesh", "Maillé"}, - {"Clock skew", "Décalage de l'horloge"}, + {"Error", "Erreur"}, {"Offline", "Hors ligne"}, - {"Symmetric NAT", "NAT symétrique"}, - {"Full cone NAT", "NAT à cône complet"}, - {"No Descriptors", "Aucuns Descripteurs"}, {"Uptime", "Temps de fonctionnement"}, {"Network status", "État du réseau"}, {"Network status v6", "État du réseau v6"}, {"Stopping in", "Arrêt dans"}, {"Family", "Famille"}, - {"Tunnel creation success rate", "Taux de création de tunnel réussie"}, - {"Total tunnel creation success rate", "Taux total de création de tunnel réussie"}, + {"Tunnel creation success rate", "Taux de succès de création de tunnels"}, {"Received", "Reçu"}, - {"%.2f KiB/s", "%.2f Kio/s"}, + {"KiB/s", "kio/s"}, {"Sent", "Envoyé"}, - {"Transit", "Transité"}, - {"Data path", "Emplacement des données"}, - {"Hidden content. Press on text to see.", "Contenu caché. Cliquez sur le texte pour afficher."}, + {"Transit", "Transit"}, + {"Hidden content. Press on text to see.", "Contenu caché. Cliquez sur le texte pour regarder."}, {"Router Ident", "Identifiant du routeur"}, {"Router Family", "Famille du routeur"}, - {"Router Caps", "Limiteurs du routeur"}, {"Version", "Version"}, {"Our external address", "Notre adresse externe"}, - {"supported", "supporté"}, - {"Routers", "Routeurs"}, - {"Floodfills", "Remplisseurs"}, {"Client Tunnels", "Tunnels clients"}, {"Services", "Services"}, {"Enabled", "Activé"}, {"Disabled", "Désactivé"}, {"Encrypted B33 address", "Adresse B33 chiffrée"}, - {"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"}, + {"ms", "ms"}, {"Outbound tunnels", "Tunnels sortants"}, - {"Tags", "Balises"}, - {"Incoming", "Entrant"}, - {"Outgoing", "Sortant"}, {"Destination", "Destination"}, - {"Amount", "Quantité"}, - {"Incoming Tags", "Balises entrantes"}, - {"Tags sessions", "Sessions des balises"}, - {"Status", "Statut"}, {"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"}, - {"Store type", "Type de stockage"}, - {"Expires", "Expire"}, - {"Non Expired Leases", "Baux non expirés"}, - {"Gateway", "Passerelle"}, - {"TunnelID", "ID du tunnel"}, - {"EndDate", "Date de fin"}, - {"floodfill mode is disabled", "le mode de remplissage est désactivé"}, - {"Queue size", "Longueur de la file"}, - {"Run peer test", "Lancer test des pairs"}, - {"Reload tunnels configuration", "Recharger la configuration des tunnels"}, - {"Decline transit tunnels", "Refuser les tunnels transitoires"}, - {"Accept transit tunnels", "Accepter les tunnels transitoires"}, - {"Cancel graceful shutdown", "Annuler l'arrêt gracieux"}, - {"Start graceful shutdown", "Démarrer l'arrêt gracieux"}, - {"Force shutdown", "Forcer l'arrêt"}, - {"Reload external CSS styles", "Rafraîchir les styles CSS externes"}, - {"Note: any action done here are not persistent and not changes your config files.", "Note : Toute action effectuée ici n'est pas permanente et ne modifie pas vos fichiers de configuration."}, - {"Logging level", "Niveau de journalisation"}, - {"Transit tunnels limit", "Limite sur les tunnels transitoires"}, - {"Change", "Changer"}, - {"Change language", "Changer la langue"}, - {"no transit tunnels currently built", "aucun tunnel transitoire présentement établi"}, - {"SAM disabled", "SAM désactivé"}, - {"no sessions currently running", "aucune session présentement en cours"}, - {"SAM session not found", "session SAM introuvable"}, - {"SAM Session", "Session SAM"}, - {"Server Tunnels", "Tunnels serveurs"}, - {"Client Forwards", "Transmission du client"}, - {"Server Forwards", "Transmission du serveur"}, - {"Unknown page", "Page inconnue"}, - {"Invalid token", "Jeton invalide"}, - {"SUCCESS", "SUCCÈS"}, - {"Stream closed", "Flux fermé"}, - {"Stream not found or already was closed", "Flux introuvable ou déjà fermé"}, - {"Destination not found", "Destination introuvable"}, - {"StreamID can't be null", "StreamID ne peut pas être vide"}, - {"Return to destination page", "Retourner à la page de destination"}, - {"You will be redirected in %d seconds", "Vous serez redirigé dans %d secondes"}, - {"LeaseSet expiration time updated", "Temps d'expiration du jeu de baux mis à jour"}, - {"LeaseSet is not found or already expired", "Le jeu de baux est introuvable ou a déjà expiré"}, - {"Transit tunnels count must not exceed %d", "Le nombre de tunnels de transit ne doit pas excéder %d"}, - {"Back to commands list", "Retour à la liste des commandes"}, - {"Register at reg.i2p", "Inscription à reg.i2p"}, - {"Description", "Description"}, - {"A bit information about service on domain", "Un peu d'information à propos des services disponibles dans le domaine"}, - {"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"}, - {"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"}, - {"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."}, - {"Addresshelper forced update rejected", "Mise à jour forcée des assistants d'adresses rejetée"}, - {"To add host %s in router's addressbook, click here: Continue.", "Pour ajouter l'hôte %s au carnet d'adresses du routeur, cliquez ici : Continuer."}, - {"Addresshelper request", "Demande à l'assistant d'adresse"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'hôte %s a été ajouté au carnet d'adresses du routeur depuis l'assistant. Cliquez ici pour continuer : Continuer."}, - {"Addresshelper adding", "Ajout de l'assistant d'adresse"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'hôte %s est déjà dans le carnet d'adresses du routeur. Cliquez ici pour mettre à jour le dossier : Continuer."}, - {"Addresshelper update", "Mise à jour de l'assistant d'adresse"}, - {"Invalid request URI", "URI de la requête invalide"}, - {"Can't detect destination host from request", "Impossible de détecter l'hôte de destination à partir de la requête"}, - {"Outproxy failure", "Échec de proxy de sortie"}, - {"Bad outproxy settings", "Mauvaise configuration du proxy de sortie"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Hôte %s pas dans le réseau I2P, mais le proxy de sortie n'est pas activé"}, - {"Unknown outproxy URL", "URL du proxy de sortie inconnu"}, - {"Cannot resolve upstream proxy", "Impossible de résoudre l'adresse du proxy en amont"}, - {"Hostname is too long", "Nom d'hôte trop long"}, - {"Cannot connect to upstream SOCKS proxy", "Impossible de se connecter au proxy SOCKS en amont"}, - {"Cannot negotiate with SOCKS proxy", "Impossible de négocier avec le proxy SOCKS"}, - {"CONNECT error", "Erreur de connexion"}, - {"Failed to connect", "Échec de connexion"}, - {"SOCKS proxy error", "Erreur de proxy SOCKS"}, - {"Failed to send request to upstream", "Erreur lors de l'envoie de la requête en amont"}, - {"No reply from SOCKS proxy", "Pas de réponse du proxy SOCKS"}, - {"Cannot connect", "Impossible de connecter"}, - {"HTTP out proxy not implemented", "Proxy de sortie HTTP non implémenté"}, - {"Cannot connect to upstream HTTP proxy", "Impossible de se connecter au proxy HTTP en amont"}, - {"Host is down", "Hôte hors service"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Impossible d'établir une connexion avec l'hôte, il est peut-être hors service. Veuillez réessayer plus tard."}, {"", ""}, }; static std::map> plurals { - {"%d days", {"%d jour", "%d jours"}}, - {"%d hours", {"%d heure", "%d heures"}}, - {"%d minutes", {"%d minute", "%d minutes"}}, - {"%d seconds", {"%d seconde", "%d secondes"}}, + {"days", {"jour", "jours"}}, + {"hours", {"heure", "heures"}}, + {"minutes", {"minute", "minutes"}}, + {"seconds", {"seconde", "secondes"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/German.cpp b/i18n/German.cpp index b3b35e99..fcef4cff 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2025, The PurpleI2P Project +* Copyright (c) 2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,25 +29,21 @@ namespace german // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, {"building", "In Bau"}, {"failed", "fehlgeschlagen"}, - {"expiring", "läuft ab"}, + {"expiring", "läuft ab in"}, {"established", "hergestellt"}, {"unknown", "Unbekannt"}, - {"exploratory", "erforschend"}, - {"Purple I2P Webconsole", "Purple I2P-Webkonsole"}, - {"i2pd webconsole", "i2pd-Webkonsole"}, + {"exploratory", "erforschende"}, + {"i2pd webconsole", "i2pd Webkonsole"}, {"Main page", "Startseite"}, - {"Router commands", "Routerbefehle"}, - {"Local Destinations", "Lokale Ziele"}, + {"Router commands", "Router Befehle"}, + {"Local Destinations", "Lokale Destination"}, {"LeaseSets", "LeaseSets"}, {"Tunnels", "Tunnel"}, {"Transit Tunnels", "Transittunnel"}, @@ -57,14 +53,14 @@ namespace german // language namespace {"ERROR", "FEHLER"}, {"OK", "OK"}, {"Testing", "Testen"}, - {"Firewalled", "Hinter einer Firewall"}, + {"Firewalled", "Hinter eine Firewall"}, {"Unknown", "Unbekannt"}, {"Proxy", "Proxy"}, {"Mesh", "Mesh"}, + {"Error", "Fehler"}, {"Clock skew", "Zeitabweichung"}, {"Offline", "Offline"}, {"Symmetric NAT", "Symmetrisches NAT"}, - {"No Descriptors", "Keine Beschreibungen"}, {"Uptime", "Laufzeit"}, {"Network status", "Netzwerkstatus"}, {"Network status v6", "Netzwerkstatus v6"}, @@ -72,7 +68,7 @@ namespace german // language namespace {"Family", "Familie"}, {"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"}, {"Received", "Eingegangen"}, - {"%.2f KiB/s", "%.2f KiB/s"}, + {"KiB/s", "KiB/s"}, {"Sent", "Gesendet"}, {"Transit", "Transit"}, {"Data path", "Datenpfad"}, @@ -85,33 +81,33 @@ namespace german // language namespace {"supported", "unterstützt"}, {"Routers", "Router"}, {"Floodfills", "Floodfills"}, - {"Client Tunnels", "Clienttunnel"}, + {"Client Tunnels", "Klienttunnel"}, {"Services", "Services"}, {"Enabled", "Aktiviert"}, {"Disabled", "Deaktiviert"}, - {"Encrypted B33 address", "Verschlüsselte B33-Adresse"}, - {"Address registration line", "Adressregistrierungszeile"}, + {"Encrypted B33 address", "Verschlüsselte B33 Adresse"}, + {"Address registration line", "Adresseregistrierungszeile"}, {"Domain", "Domain"}, {"Generate", "Generieren"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Hinweis: Der resultierende String kann nur für die Registrierung einer 2LD-Domain (beispiel.i2p) benutzt werden. Für die Registrierung von Subdomains kann i2pd-tools verwendet werden."}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Hinweis: Der resultierende String kann nur für die Registrierung einer 2LD Domain (beispiel.i2p) benutzt werden. Für die Registrierung von Subdomains kann i2pd-tools verwendet werden."}, {"Address", "Adresse"}, {"Type", "Typ"}, {"EncType", "Verschlüsselungstyp"}, {"Inbound tunnels", "Eingehende Tunnel"}, - {"%dms", "%dms"}, + {"ms", "ms"}, {"Outbound tunnels", "Ausgehende Tunnel"}, {"Tags", "Tags"}, {"Incoming", "Eingehend"}, {"Outgoing", "Ausgehend"}, - {"Destination", "Ziel"}, + {"Destination", "Destination"}, {"Amount", "Anzahl"}, {"Incoming Tags", "Eingehende Tags"}, - {"Tags sessions", "Tags-Sitzungen"}, + {"Tags sessions", "Tags Sitzungen"}, {"Status", "Status"}, - {"Local Destination", "Lokales Ziel"}, + {"Local Destination", "Lokale Destination"}, {"Streams", "Streams"}, {"Close stream", "Stream schließen"}, - {"I2CP session not found", "I2CP-Sitzung nicht gefunden"}, + {"I2CP session not found", "I2CP Sitzung nicht gefunden"}, {"I2CP is not enabled", "I2CP ist nicht aktiviert"}, {"Invalid", "Ungültig"}, {"Store type", "Speichertyp"}, @@ -120,100 +116,98 @@ 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"}, + {"not floodfill", "kein Floodfill"}, + {"Queue size", "Warteschlangengröße"}, + {"Run peer test", "Peer-Test ausführen"}, {"Decline transit tunnels", "Transittunnel ablehnen"}, {"Accept transit tunnels", "Transittunnel akzeptieren"}, - {"Cancel graceful shutdown", "Beende das kontrollierte Herunterfahren"}, + {"Cancel graceful shutdown", "Beende das kontrollierte herunterfahren"}, {"Start graceful shutdown", "Starte das kontrollierte Herunterfahren"}, {"Force shutdown", "Herunterfahren erzwingen"}, - {"Reload external CSS styles", "Lade externe CSS-Stile neu"}, + {"Reload external CSS styles", "Lade externe CSS-Styles neu"}, {"Note: any action done here are not persistent and not changes your config files.", "Hinweis: Alle hier durchgeführten Aktionen sind nicht dauerhaft und ändern die Konfigurationsdateien nicht."}, {"Logging level", "Protokollierungslevel"}, {"Transit tunnels limit", "Limit für Transittunnel"}, - {"Change", "Ändern"}, + {"Change", "Verändern"}, {"Change language", "Sprache ändern"}, {"no transit tunnels currently built", "derzeit keine Transittunnel aufgebaut"}, {"SAM disabled", "SAM deaktiviert"}, {"no sessions currently running", "Derzeit keine laufenden Sitzungen"}, - {"SAM session not found", "SAM-Sitzung nicht gefunden"}, - {"SAM Session", "SAM-Sitzung"}, + {"SAM session not found", "SAM Sitzung nicht gefunden"}, + {"SAM Session", "SAM Sitzung"}, {"Server Tunnels", "Servertunnel"}, - {"Client Forwards", "Client-Weiterleitungen"}, + {"Client Forwards", "Klient-Weiterleitungen"}, {"Server Forwards", "Server-Weiterleitungen"}, {"Unknown page", "Unbekannte Seite"}, {"Invalid token", "Ungültiger Token"}, {"SUCCESS", "ERFOLGREICH"}, {"Stream closed", "Stream geschlossen"}, {"Stream not found or already was closed", "Stream nicht gefunden oder bereits geschlossen"}, - {"Destination not found", "Ziel nicht gefunden"}, + {"Destination not found", "Destination 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"}, + {"Return to destination page", "Zurück zur Destination-Seite"}, + {"You will be redirected in 5 seconds", "Du wirst in 5 Sekunden weitergeleitet"}, + {"Transit tunnels count must not exceed 65535", "Es darf maximal 65535 Transittunnel geben"}, + {"Back to commands list", "Zurück zur Kommandoliste"}, {"Register at reg.i2p", "Auf reg.i2p registrieren"}, {"Description", "Beschreibung"}, - {"A bit information about service on domain", "Ein paar Informationen über den Service auf der Domain"}, - {"Submit", "Absenden"}, - {"Domain can't end with .b32.i2p", "Domain kann nicht auf .b32.i2p enden"}, - {"Domain must end with .i2p", "Domain muss auf .i2p enden"}, - {"Such destination is not found", "Ein solches Ziel konnte nicht gefunden werden"}, + {"A bit information about service on domain", "Ein bisschen Informationen über den Service auf der Domain"}, + {"Submit", "Einreichen"}, + {"Domain can't end with .b32.i2p", "Domain kann nicht mit .b32.i2p enden"}, + {"Domain must end with .i2p", "Domain muss mit .i2p enden"}, + {"Such destination is not found", "Eine solche Destination konnte nicht gefunden werden"}, {"Unknown command", "Unbekannter Befehl"}, {"Command accepted", "Befehl akzeptiert"}, {"Proxy error", "Proxy-Fehler"}, {"Proxy info", "Proxy-Info"}, {"Proxy error: Host not found", "Proxy-Fehler: Host nicht gefunden"}, - {"Remote host not found in router's addressbook", "Remote-Host nicht im Router-Adressbuch gefunden"}, - {"You may try to find this host on jump services below", "Vielleicht kannst du diesen Host auf einem der nachfolgenden Jump-Services finden"}, + {"Remote host not found in router's addressbook", "Remote-Host nicht im Router Adressbuch gefunden"}, + {"You may try to find this host on jump services below", "Vielleicht kannst du diesen Host auf einen der Jump-Services unten 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"}, + {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht interpretieren"}, + {"addresshelper is not supported", "addresshelper wird nicht unterstützt"}, + {"Host", "Host"}, + {"added to router's addressbook from helper", "vom Helfer zum Router Adressbuch hinzugefügt"}, + {"Click here to proceed:", "Klicke hier um fortzufahren:"}, + {"Continue", "Fortsetzen"}, + {"Addresshelper found", "Adresshelfer gefunden"}, + {"already in router's addressbook", "bereits im Adressbuch des Routers"}, + {"Click here to update record:", "Klicke hier, um den Eintrag zu aktualisieren:"}, + {"invalid request uri", "ungültige Anfrage-URI"}, + {"Can't detect destination host from request", "Kann Anhand der Anfrage den Destination-Host nicht erkennen"}, {"Outproxy failure", "Outproxy-Fehler"}, - {"Bad outproxy settings", "Ungültige Outproxy-Einstellungen"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s außerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, - {"Unknown outproxy URL", "Unbekannte Outproxy-URL"}, - {"Cannot resolve upstream proxy", "Kann den Upstream-Proxy nicht auflösen"}, - {"Hostname is too long", "Hostname zu lang"}, - {"Cannot connect to upstream SOCKS proxy", "Kann keine Verbindung zum Upstream-SOCKS-Proxy herstellen"}, - {"Cannot negotiate with SOCKS proxy", "Kann nicht mit SOCKS-Proxy verhandeln"}, + {"bad outproxy settings", "ungültige Outproxy-Einstellungen"}, + {"not inside I2P network, but outproxy is not enabled", "nicht innerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, + {"unknown outproxy url", "unbekannte Outproxy-URL"}, + {"cannot resolve upstream proxy", "kann den Upstream-Proxy nicht auflösen"}, + {"hostname too long", "Hostname zu lang"}, + {"cannot connect to upstream socks proxy", "Kann keine Verbindung zum Upstream-Socks-Proxy herstellen"}, + {"Cannot negotiate with socks proxy", "Kann nicht mit Socks-Proxy verhandeln"}, {"CONNECT error", "CONNECT-Fehler"}, - {"Failed to connect", "Verbindung konnte nicht hergestellt werden"}, - {"SOCKS proxy error", "SOCKS-Proxy-Fehler"}, - {"Failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, - {"No reply from SOCKS proxy", "Keine Antwort vom SOCKS-Proxy"}, - {"Cannot connect", "Kann nicht verbinden"}, - {"HTTP out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, - {"Cannot connect to upstream HTTP proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, + {"Failed to Connect", "Verbindung konnte nicht hergestellt werden"}, + {"socks proxy error", "Socks-Proxy-Fehler"}, + {"failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, + {"No Reply From socks proxy", "Keine Antwort vom Socks-Proxy"}, + {"cannot connect", "kann nicht verbinden"}, + {"http out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, + {"cannot connect to upstream http proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, {"Host is down", "Host ist offline"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Konnte keine Verbindung zum angefragten Host aufbauen, vielleicht ist er offline. Versuche es später noch mal."}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Konnte keine Verbindung zum angefragten Host aufbaunen, vielleicht ist es offline. Versuche es später noch einmal."}, {"", ""}, }; static std::map> plurals { - {"%d days", {"%d Tag", "%d Tage"}}, - {"%d hours", {"%d Stunde", "%d Stunden"}}, - {"%d minutes", {"%d Minute", "%d Minuten"}}, - {"%d seconds", {"%d Sekunde", "%d Sekunden"}}, + {"days", {"Tag", "Tage"}}, + {"hours", {"Stunde", "Stunden"}}, + {"minutes", {"Minute", "Minuten"}}, + {"seconds", {"Sekunde", "Sekunden"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Hebrew.cpp b/i18n/Hebrew.cpp deleted file mode 100644 index 7e56395f..00000000 --- a/i18n/Hebrew.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* -* 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 deleted file mode 100644 index 7ce9b65b..00000000 --- a/i18n/Hindi.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* 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 deleted file mode 100644 index 48a02357..00000000 --- a/i18n/I18N.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright (c) 2021-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "ClientContext.h" -#include "I18N_langs.h" -#include "I18N.h" - -namespace i2p -{ -namespace i18n -{ - void SetLanguage(const std::string &lang) - { - const auto it = i2p::i18n::languages.find(lang); - if (it == i2p::i18n::languages.end()) // fallback - { - i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); - setlocale(LC_NUMERIC, "english"); - } - else - { - i2p::client::context.SetLanguage (it->second.LocaleFunc()); - setlocale(LC_NUMERIC, lang.c_str()); // set decimal point based on language - } - } - - std::string_view translate (std::string_view arg) - { - return i2p::client::context.GetLanguage ()->GetString (arg); - } - - std::string translate (const std::string& arg, const std::string& arg2, const int n) - { - return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); - } -} // i18n -} // i2p diff --git a/i18n/I18N.h b/i18n/I18N.h index 00398f78..dd804926 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,136 +9,37 @@ #ifndef __I18N_H__ #define __I18N_H__ -#include -#include -#include -#include -#include +#include "ClientContext.h" namespace i2p { namespace i18n { - typedef std::map LocaleStrings; - class Locale + inline void SetLanguage(const std::string &lang) { - public: - Locale ( - const std::string& language, - const bool& rtl, - const LocaleStrings& strings, - const std::map>& plurals, - std::function formula - ): m_Language (language), m_RTL (rtl), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + const auto it = i2p::i18n::languages.find(lang); + if (it == i2p::i18n::languages.end()) // fallback + i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); + else + i2p::client::context.SetLanguage (it->second.LocaleFunc()); + } - // Get activated language name for webconsole - std::string GetLanguage() const - { - return m_Language; - } + inline std::string translate (const std::string& arg) + { + return i2p::client::context.GetLanguage ()->GetString (arg); + } - 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()) - { - return arg; - } - else - { - return it->second; - } - } - - std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const - { - const auto it = m_Plurals.find(arg2); - if (it == m_Plurals.end()) // not found, fallback to english - { - return n == 1 ? arg : arg2; - } - else - { - int form = m_Formula(n); - return it->second[form]; - } - } - - private: - const std::string m_Language; - const bool m_RTL; - const LocaleStrings m_Strings; - const std::map> m_Plurals; - std::function m_Formula; - }; - - void SetLanguage(const std::string &lang); - std::string_view translate (std::string_view arg); - std::string translate (const std::string& arg, const std::string& arg2, int n); + inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) + { + return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); + } } // i18n } // i2p -/** - * @brief Get translation of string - * @param arg String with message - */ -template -std::string_view tr (TValue&& arg) +template +std::string tr (TArgs&&... args) { - return i2p::i18n::translate(std::forward(arg)); -} - -/** - * @brief Get translation of string and format it - * @param arg String with message - * @param args Array of arguments for string formatting -*/ -template -std::string tr (TValue&& arg, TArgs&&... args) -{ - std::string tr_str = std::string (i2p::i18n::translate(std::forward(arg))); // TODO: - - size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - std::string str(size, 0); - std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); - - return str; -} - -/** - * @brief Get translation of string with plural forms - * @param arg String with message in singular form - * @param arg2 String with message in plural form - * @param n Integer, used for selection of form - */ -template -std::string ntr (TValue&& arg, TValue2&& arg2, int n) -{ - return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); -} - -/** - * @brief Get translation of string with plural forms and format it - * @param arg String with message in singular form - * @param arg2 String with message in plural form - * @param n Integer, used for selection of form - * @param args Array of arguments for string formatting - */ -template -std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) -{ - std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); - - size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - std::string str(size, 0); - std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); - - return str; + return i2p::i18n::translate(std::forward(args)...); } #endif // __I18N_H__ diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index b3d09af8..f3fc6691 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,12 +9,60 @@ #ifndef __I18N_LANGS_H__ #define __I18N_LANGS_H__ -#include "I18N.h" - namespace i2p { namespace i18n { + class Locale + { + public: + Locale ( + const std::string& language, + const std::map& strings, + const std::map>& plurals, + std::function formula + ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + + // Get activated language name for webconsole + std::string GetLanguage() const + { + return m_Language; + } + + std::string GetString (const std::string& arg) const + { + const auto it = m_Strings.find(arg); + if (it == m_Strings.end()) + { + return arg; + } + else + { + return it->second; + } + } + + std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const + { + const auto it = m_Plurals.find(arg2); + if (it == m_Plurals.end()) // not found, fallback to english + { + return n == 1 ? arg : arg2; + } + else + { + int form = m_Formula(n); + return it->second[form]; + } + } + + private: + const std::string m_Language; + const std::map m_Strings; + const std::map> m_Plurals; + std::function m_Formula; + }; + struct langData { std::string LocaleName; // localized name @@ -23,25 +71,15 @@ namespace i18n }; // Add localization here with language name as namespace - namespace afrikaans { std::shared_ptr GetLocale (); } - namespace armenian { std::shared_ptr GetLocale (); } - namespace chinese { std::shared_ptr GetLocale (); } - namespace czech { std::shared_ptr GetLocale (); } - 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 (); } - namespace russian { std::shared_ptr GetLocale (); } - namespace spanish { std::shared_ptr GetLocale (); } - namespace swedish { std::shared_ptr GetLocale (); } - namespace turkish { std::shared_ptr GetLocale (); } - namespace turkmen { std::shared_ptr GetLocale (); } - namespace ukrainian { std::shared_ptr GetLocale (); } - namespace uzbek { std::shared_ptr GetLocale (); } + namespace afrikaans { std::shared_ptr GetLocale (); } + namespace armenian { std::shared_ptr GetLocale (); } + namespace english { std::shared_ptr GetLocale (); } + namespace french { std::shared_ptr GetLocale (); } + namespace german { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } + namespace ukrainian { std::shared_ptr GetLocale (); } + namespace uzbek { std::shared_ptr GetLocale (); } /** * That map contains international language name lower-case, name in it's language and it's code @@ -49,23 +87,13 @@ namespace i18n static std::map languages { { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, - { "armenian", {"hայերէն", "hy", i2p::i18n::armenian::GetLocale} }, - { "chinese", {"简体字", "zh-CN", i2p::i18n::chinese::GetLocale} }, - { "czech", {"čeština", "cs", i2p::i18n::czech::GetLocale} }, + { "armenian", {"հայերէն", "hy", i2p::i18n::armenian::GetLocale} }, { "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} }, - { "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} }, - { "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} }, - { "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} }, - { "turkish", {"Türk dili", "tr", i2p::i18n::turkish::GetLocale} }, - { "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, - { "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, + { "russian", {"русский язык", "ru", i2p::i18n::russian::GetLocale} }, + { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, }; diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp deleted file mode 100644 index 339654ba..00000000 --- a/i18n/Italian.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Italian localization file - -namespace i2p -{ -namespace i18n -{ -namespace italian // language namespace -{ - // language name in lowercase - static std::string language = "italian"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - // 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", "in costruzione"}, - {"failed", "fallito"}, - {"expiring", "in scadenza"}, - {"established", "stabilita"}, - {"unknown", "sconosciuto"}, - {"exploratory", "esplorativo"}, - {"Purple I2P Webconsole", "Terminale web Purple I2P"}, - {"i2pd webconsole", "Terminal web i2pd"}, - {"Main page", "Pagina principale"}, - {"Router commands", "Comandi router"}, - {"Local Destinations", "Destinazioni locali"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Tunnel"}, - {"Transit Tunnels", "Tunnel di transito"}, - {"Transports", "Trasporti"}, - {"I2P tunnels", "Tunnel I2P"}, - {"SAM sessions", "Sessioni SAM"}, - {"ERROR", "ERRORE"}, - {"OK", "OK"}, - {"Testing", "Testando"}, - {"Firewalled", "Protetto da firewall"}, - {"Unknown", "Sconosciuto"}, - {"Proxy", "Proxy"}, - {"Mesh", "Mesh"}, - {"Clock skew", "Orologio disallineato"}, - {"Offline", "Disconnesso"}, - {"Symmetric NAT", "NAT simmetrico"}, - {"Full cone NAT", "Cono completo NAT"}, - {"No Descriptors", "Nessun descrittore"}, - {"Uptime", "In funzione da"}, - {"Network status", "Stato della rete"}, - {"Network status v6", "Stato della rete v6"}, - {"Stopping in", "Arresto in"}, - {"Family", "Famiglia"}, - {"Tunnel creation success rate", "Percentuale di tunnel creati con successo"}, - {"Total tunnel creation success rate", "Percentuale di successo totale nella creazione del tunnel"}, - {"Received", "Ricevuti"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Inviati"}, - {"Transit", "Transitati"}, - {"Data path", "Percorso dati"}, - {"Hidden content. Press on text to see.", "Contenuto nascosto. Premi sul testo per vedere."}, - {"Router Ident", "Identificativo del router"}, - {"Router Family", "Famiglia del router"}, - {"Router Caps", "Limiti del router"}, - {"Version", "Versione"}, - {"Our external address", "Il nostro indirizzo esterno"}, - {"supported", "supportato"}, - {"Routers", "Router"}, - {"Floodfills", "Floodfill"}, - {"Client Tunnels", "Tunnel client"}, - {"Services", "Servizi"}, - {"Enabled", "Abilitato"}, - {"Disabled", "Disabilitato"}, - {"Encrypted B33 address", "Indirizzo criptato B33"}, - {"Address registration line", "Linea di registrazione indirizzo"}, - {"Domain", "Dominio"}, - {"Generate", "Genera"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la stringa risultante può essere utilizzata solo per registrare domini 2LD (example.i2p). Per registrare i sottodomini, si prega di utilizzare i2pd-tools."}, - {"Address", "Indirizzo"}, - {"Type", "Tipologia"}, - {"EncType", "Tipo di crittografia"}, - {"Expire LeaseSet", "Scadenza LeaseSet"}, - {"Inbound tunnels", "Tunnel in entrata"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Tunnel in uscita"}, - {"Tags", "Tag"}, - {"Incoming", "In entrata"}, - {"Outgoing", "In uscita"}, - {"Destination", "Destinazione"}, - {"Amount", "Quantità"}, - {"Incoming Tags", "Tag in entrata"}, - {"Tags sessions", "Sessioni dei tag"}, - {"Status", "Stato"}, - {"Local Destination", "Destinazione locale"}, - {"Streams", "Flussi"}, - {"Close stream", "Interrompi il flusso"}, - {"Such destination is not found", "Questa destinazione non è stata trovata"}, - {"I2CP session not found", "Sessione I2CP non trovata"}, - {"I2CP is not enabled", "I2CP non è abilitato"}, - {"Invalid", "Invalido"}, - {"Store type", "Tipologia di archivio"}, - {"Expires", "Scade"}, - {"Non Expired Leases", "Lease non scaduti"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Data di fine"}, - {"floodfill mode is disabled", "la modalità floodfill è disabilitata"}, - {"Queue size", "Dimensione della coda"}, - {"Run peer test", "Esegui il test dei peer"}, - {"Reload tunnels configuration", "Ricarica la configurazione dei tunnel"}, - {"Decline transit tunnels", "Rifiuta tunnel di transito"}, - {"Accept transit tunnels", "Accetta tunnel di transito"}, - {"Cancel graceful shutdown", "Annulla l'interruzione controllata"}, - {"Start graceful shutdown", "Avvia l'interruzione controllata"}, - {"Force shutdown", "Forza l'arresto"}, - {"Reload external CSS styles", "Ricarica gli stili CSS esterni"}, - {"Note: any action done here are not persistent and not changes your config files.", "Nota: qualsiasi azione effettuata qui non è persistente e non modifica i file di configurazione."}, - {"Logging level", "Livello di log"}, - {"Transit tunnels limit", "Limite di tunnel di transito"}, - {"Change", "Modifica"}, - {"Change language", "Modifica linguaggio"}, - {"no transit tunnels currently built", "Attualmente non ci sono tunnel di transito instaurati"}, - {"SAM disabled", "SAM disabilitato"}, - {"no sessions currently running", "Attualmente non ci sono sessioni attive"}, - {"SAM session not found", "Sessione SAM non trovata"}, - {"SAM Session", "Sessione SAM"}, - {"Server Tunnels", "Tunnel server"}, - {"Client Forwards", "Client di inoltro"}, - {"Server Forwards", "Server di inoltro"}, - {"Unknown page", "Pagina sconosciuta"}, - {"Invalid token", "Token non valido"}, - {"SUCCESS", "SUCCESSO"}, - {"Stream closed", "Flusso terminato"}, - {"Stream not found or already was closed", "Il flusso non è stato trovato oppure è già stato terminato"}, - {"Destination not found", "Destinazione non trovata"}, - {"StreamID can't be null", "Lo StreamID non può essere null"}, - {"Return to destination page", "Ritorna alla pagina di destinazione"}, - {"You will be redirected in %d seconds", "Sarai reindirizzato tra %d secondi"}, - {"LeaseSet expiration time updated", "Tempo di scadenza LeaseSet aggiornato"}, - {"LeaseSet is not found or already expired", "LeaseSet non trovato o già scaduto"}, - {"Transit tunnels count must not exceed %d", "Il conteggio dei tunnel di transito non deve superare %d"}, - {"Back to commands list", "Ritorna alla lista dei comandi"}, - {"Register at reg.i2p", "Registra a reg.i2p"}, - {"Description", "Descrizione"}, - {"A bit information about service on domain", "Alcune informazioni riguardo il servizio sul dominio"}, - {"Submit", "Invia"}, - {"Domain can't end with .b32.i2p", "I domini non possono terminare con .b32.i2p"}, - {"Domain must end with .i2p", "I domini devono terminare con .i2p"}, - {"Unknown command", "Comando sconosciuto"}, - {"Command accepted", "Comando accettato"}, - {"Proxy error", "Errore del proxy"}, - {"Proxy info", "Informazioni del proxy"}, - {"Proxy error: Host not found", "Errore del proxy: Host non trovato"}, - {"Remote host not found in router's addressbook", "L'host remoto non è stato trovato nella rubrica del router"}, - {"You may try to find this host on jump services below", "Si può provare a trovare questo host sui servizi di salto qui sotto"}, - {"Invalid request", "Richiesta non valida"}, - {"Proxy unable to parse your request", "Il proxy non è in grado di elaborare la tua richiesta"}, - {"Addresshelper is not supported", "Addresshelper non è supportato"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Attenzione: la fonte di questo URL potrebbe essere dannosa! Fai clic qui per aggiornare il record: Continua."}, - {"Addresshelper forced update rejected", "Aggiornamento forzato dell'helper degli indirizzi rifiutato"}, - {"To add host %s in router's addressbook, click here: Continue.", "Per aggiungere host %s nella rubrica del router, clicca qui: Continua."}, - {"Addresshelper request", "Richiesta di indirizzo helper"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'host %s viene aggiunto alla rubrica del router dall'helper. Fai clic qui per procedere: Continua."}, - {"Addresshelper adding", "Aggiunta di Addresshelper"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Clicca qui per aggiornare il record: Continua."}, - {"Addresshelper update", "Aggiornamento dell'helper degli indirizzi"}, - {"Invalid request URI", "URI della richiesta non valido"}, - {"Can't detect destination host from request", "Impossibile determinare l'host di destinazione dalla richiesta"}, - {"Outproxy failure", "Fallimento del proxy di uscita"}, - {"Bad outproxy settings", "Impostazioni errate del proxy di uscita"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s non all'interno della rete I2P, ma il proxy di uscita non è abilitato"}, - {"Unknown outproxy URL", "URL del proxy di uscita sconosciuto"}, - {"Cannot resolve upstream proxy", "Impossibile identificare il flusso a monte del proxy"}, - {"Hostname is too long", "Il nome dell'host è troppo lungo"}, - {"Cannot connect to upstream SOCKS proxy", "Impossibile connettersi al flusso a monte del proxy SOCKS"}, - {"Cannot negotiate with SOCKS proxy", "Impossibile negoziare con il proxy SOCKS"}, - {"CONNECT error", "Errore di connessione"}, - {"Failed to connect", "Connessione fallita"}, - {"SOCKS proxy error", "Errore del proxy SOCKS"}, - {"Failed to send request to upstream", "Invio della richiesta a monte non riuscito"}, - {"No reply from SOCKS proxy", "Nessuna risposta dal proxy SOCKS"}, - {"Cannot connect", "Impossibile connettersi"}, - {"HTTP out proxy not implemented", "Proxy HTTP di uscita non implementato"}, - {"Cannot connect to upstream HTTP proxy", "Impossibile connettersi al flusso a monte del proxy HTTP"}, - {"Host is down", "L'host è offline"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Impossibile creare la connessione all'host richiesto, probabilmente è offline. Riprova più tardi."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d giorno", "%d giorni"}}, - {"%d hours", {"%d ora", "%d ore"}}, - {"%d minutes", {"%d minuto", "%d minuti"}}, - {"%d seconds", {"%d secondo", "%d secondi"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp deleted file mode 100644 index 97422e45..00000000 --- a/i18n/Polish.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Copyright (c) 2023-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Polish localization file - -namespace i2p -{ -namespace i18n -{ -namespace polish // language namespace -{ - // language name in lowercase - static std::string language = "polish"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); - } - - // 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, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp deleted file mode 100644 index 970a7b8f..00000000 --- a/i18n/Portuguese.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Copyright (c) 2023-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Portuguese localization file - -namespace i2p -{ -namespace i18n -{ -namespace portuguese // language namespace -{ - // language name in lowercase - static std::string language = "portuguese"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - // 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", "construindo"}, - {"failed", "falhou"}, - {"expiring", "expirando"}, - {"established", "estabelecido"}, - {"unknown", "desconhecido"}, - {"exploratory", "exploratório"}, - {"Purple I2P Webconsole", "Webconsole Purple I2P"}, - {"i2pd webconsole", "webconsole i2pd"}, - {"Main page", "Página Principal"}, - {"Router commands", "Comandos do Roteador"}, - {"Local Destinations", "Destinos Locais"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Túneis"}, - {"Transit Tunnels", "Túneis de Trânsito"}, - {"Transports", "Transportes"}, - {"I2P tunnels", "Túneis I2P"}, - {"SAM sessions", "Sessões do SAM"}, - {"ERROR", "ERRO"}, - {"OK", "OK"}, - {"Testing", "Testando"}, - {"Firewalled", "Sob Firewall"}, - {"Unknown", "Desconhecido"}, - {"Proxy", "Proxy"}, - {"Mesh", "Malha"}, - {"Clock skew", "Desvio de Relógio"}, - {"Offline", "Desligado"}, - {"Symmetric NAT", "NAT Simétrico"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Sem Descritores"}, - {"Uptime", "Tempo Ativo"}, - {"Network status", "Estado da rede"}, - {"Network status v6", "Estado da rede v6"}, - {"Stopping in", "Parando em"}, - {"Family", "Família"}, - {"Tunnel creation success rate", "Taxa de sucesso na criação de túneis"}, - {"Total tunnel creation success rate", "Taxa total de sucesso na criação de túneis"}, - {"Received", "Recebido"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Enviado"}, - {"Transit", "Trânsito"}, - {"Data path", "Diretório de dados"}, - {"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."}, - {"Router Ident", "Identidade do Roteador"}, - {"Router Family", "Família do Roteador"}, - {"Router Caps", "Limites do Roteador"}, - {"Version", "Versão"}, - {"Our external address", "Nosso endereço externo"}, - {"supported", "suportado"}, - {"Routers", "Roteadores"}, - {"Floodfills", "Modo Inundação"}, - {"Client Tunnels", "Túneis de Clientes"}, - {"Services", "Serviços"}, - {"Enabled", "Ativado"}, - {"Disabled", "Desativado"}, - {"Encrypted B33 address", "Endereço B33 criptografado"}, - {"Address registration line", "Linha de cadastro de endereço"}, - {"Domain", "Domínio"}, - {"Generate", "Gerar"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Nota: A string resultante só pode ser usada para registrar domínios 2LD (exemplo.i2p). Para registrar subdomínios por favor utilize o i2pd-tools."}, - {"Address", "Endereço"}, - {"Type", "Tipo"}, - {"EncType", "Tipo de Criptografia"}, - {"Expire LeaseSet", "Expirar LeaseSet"}, - {"Inbound tunnels", "Túneis de Entrada"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Túneis de Saída"}, - {"Tags", "Etiquetas"}, - {"Incoming", "Entradas"}, - {"Outgoing", "Saídas"}, - {"Destination", "Destinos"}, - {"Amount", "Quantidade"}, - {"Incoming Tags", "Etiquetas de Entrada"}, - {"Tags sessions", "Sessões de Etiquetas"}, - {"Status", "Estado"}, - {"Local Destination", "Destino Local"}, - {"Streams", "Fluxos"}, - {"Close stream", "Fechar fluxo"}, - {"Such destination is not found", "Tal destino não foi encontrado"}, - {"I2CP session not found", "Sessão do I2CP não encontrada"}, - {"I2CP is not enabled", "I2CP não está ativado"}, - {"Invalid", "Inválido"}, - {"Store type", "Tipo de armazenamento"}, - {"Expires", "Expira em"}, - {"Non Expired Leases", "Sessões não expiradas"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Data final"}, - {"floodfill mode is disabled", "Mode de inundação está desativado"}, - {"Queue size", "Tamanho da fila"}, - {"Run peer test", "Executar teste de peers"}, - {"Reload tunnels configuration", "Recarregar a configuração dos túneis"}, - {"Decline transit tunnels", "Negar túneis de trânsito"}, - {"Accept transit tunnels", "Aceitar túneis de trânsito"}, - {"Cancel graceful shutdown", "Cancelar desligamento gracioso"}, - {"Start graceful shutdown", "Iniciar desligamento gracioso"}, - {"Force shutdown", "Forçar desligamento"}, - {"Reload external CSS styles", "Recarregar estilos CSS externos"}, - {"Note: any action done here are not persistent and not changes your config files.", " Nota: Qualquer ação feita aqui não será permanente e não altera os seus arquivos de configuração."}, - {"Logging level", "Nível de registro"}, - {"Transit tunnels limit", "Limite de túneis de trânsito"}, - {"Change", "Mudar"}, - {"Change language", "Trocar idioma"}, - {"no transit tunnels currently built", "Nenhum túnel de trânsito construido no momento"}, - {"SAM disabled", "SAM desativado"}, - {"no sessions currently running", "Nenhuma sessão funcionando no momento"}, - {"SAM session not found", "Nenhuma sessão do SAM encontrada"}, - {"SAM Session", "Sessão do SAM"}, - {"Server Tunnels", "Túneis de Servidor"}, - {"Client Forwards", "Túneis de Cliente"}, - {"Server Forwards", "Encaminhamentos de Servidor"}, - {"Unknown page", "Página desconhecida"}, - {"Invalid token", "Token Inválido"}, - {"SUCCESS", "SUCESSO"}, - {"Stream closed", "Fluxo fechado"}, - {"Stream not found or already was closed", "Fluxo não encontrado ou já fechado"}, - {"Destination not found", "Destino não encontrado"}, - {"StreamID can't be null", "StreamID não pode ser nulo"}, - {"Return to destination page", "Retornar para à página de destino"}, - {"You will be redirected in %d seconds", "Você será redirecionado em %d segundos"}, - {"LeaseSet expiration time updated", "Tempo de validade do LeaseSet atualizado"}, - {"LeaseSet is not found or already expired", "LeaseSet não foi encontrado ou já expirou"}, - {"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"}, - {"Back to commands list", "Voltar para a lista de comandos"}, - {"Register at reg.i2p", "Registrar em reg.i2p"}, - {"Description", "Descrição"}, - {"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"}, - {"Submit", "Enviar"}, - {"Domain can't end with .b32.i2p", "O domínio não pode terminar com .b32.i2p"}, - {"Domain must end with .i2p", "O domínio não pode terminar com .i2p"}, - {"Unknown command", "Comando desconhecido"}, - {"Command accepted", "Comando aceito"}, - {"Proxy error", "Erro no proxy"}, - {"Proxy info", "Informações do proxy"}, - {"Proxy error: Host not found", "Erro no proxy: Host não encontrado"}, - {"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"}, - {"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos serviços de jump abaixo"}, - {"Invalid request", "Requisição inválida"}, - {"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"}, - {"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador. Cuidado: a fonte desta URL pode ser perigosa! Clique aqui para atualizar o registro: Continuar."}, - {"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"}, - {"To add host %s in router's addressbook, click here: Continue.", "Para adicionar o host %s ao catálogo de endereços do roteador, clique aqui: Continuar ."}, - {"Addresshelper request", "Requisição ao Auxiliar de Endereços"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para prosseguir: Continuar ."}, - {"Addresshelper adding", "Auxiliar de Endereço adicionando"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador . Clique aqui para atualizar o registro: Continuar."}, - {"Addresshelper update", "Atualização do Auxiliar de Endereços"}, - {"Invalid request URI", "A URI de requisição é inválida"}, - {"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"}, - {"Outproxy failure", "Falha no outproxy"}, - {"Bad outproxy settings", "Má configurações do outproxy"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"}, - {"Unknown outproxy URL", "URL de outproxy desconhecida"}, - {"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"}, - {"Hostname is too long", "O hostname é muito longo"}, - {"Cannot connect to upstream SOCKS proxy", "Não é possível se conectar ao proxy SOCKS de entrada"}, - {"Cannot negotiate with SOCKS proxy", "Não é possível negociar com o proxy SOCKS"}, - {"CONNECT error", "Erro de CONEXÃO"}, - {"Failed to connect", "Falha ao conectar"}, - {"SOCKS proxy error", "Erro no proxy SOCKS"}, - {"Failed to send request to upstream", "Falha ao enviar requisição para o fluxo de entrada"}, - {"No reply from SOCKS proxy", "Sem resposta do proxy SOCKS"}, - {"Cannot connect", "Impossível conectar"}, - {"HTTP out proxy not implemented", "proxy de saída HTTP não implementado"}, - {"Cannot connect to upstream HTTP proxy", "Não é possível conectar ao proxy HTTP de entrada"}, - {"Host is down", "Host está desligado"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Não é possível se conectar ao host requisitado, talvez ele esteja for do ar. Por favor, tente novamente mais tarde."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d Dia", "%d Dias"}}, - {"%d hours", {"%d hora", "%d horas"}}, - {"%d minutes", {"%d minuto", "%d minutos"}}, - {"%d seconds", {"%d Segundo", "%d segundos"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index e5bcb0b0..d7616e9e 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,17 @@ 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; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f КиБ"}, - {"%.2f MiB", "%.2f МиБ"}, - {"%.2f GiB", "%.2f ГиБ"}, + {"KiB", "КиБ"}, + {"MiB", "МиБ"}, + {"GiB", "ГиБ"}, {"building", "строится"}, {"failed", "неудачный"}, {"expiring", "истекает"}, {"established", "работает"}, {"unknown", "неизвестно"}, {"exploratory", "исследовательский"}, - {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, {"i2pd webconsole", "Веб-консоль i2pd"}, {"Main page", "Главная"}, {"Router commands", "Команды роутера"}, @@ -61,20 +57,18 @@ namespace russian // language namespace {"Unknown", "Неизвестно"}, {"Proxy", "Прокси"}, {"Mesh", "MESH-сеть"}, + {"Error", "Ошибка"}, {"Clock skew", "Не точное время"}, {"Offline", "Оффлайн"}, {"Symmetric NAT", "Симметричный NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Нет дескрипторов"}, {"Uptime", "В сети"}, {"Network status", "Сетевой статус"}, {"Network status v6", "Сетевой статус v6"}, {"Stopping in", "Остановка через"}, {"Family", "Семейство"}, {"Tunnel creation success rate", "Успешно построенных туннелей"}, - {"Total tunnel creation success rate", "Общий процент успешно построенных туннелей"}, {"Received", "Получено"}, - {"%.2f KiB/s", "%.2f КиБ/с"}, + {"KiB/s", "КиБ/с"}, {"Sent", "Отправлено"}, {"Transit", "Транзит"}, {"Data path", "Путь к данным"}, @@ -99,9 +93,8 @@ namespace russian // language namespace {"Address", "Адрес"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, - {"Expire LeaseSet", "Просрочить Лизсет"}, {"Inbound tunnels", "Входящие туннели"}, - {"%dms", "%dмс"}, + {"ms", "мс"}, {"Outbound tunnels", "Исходящие туннели"}, {"Tags", "Теги"}, {"Incoming", "Входящие"}, @@ -114,7 +107,6 @@ 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", "Некорректный"}, @@ -124,10 +116,9 @@ namespace russian // language namespace {"Gateway", "Шлюз"}, {"TunnelID", "ID туннеля"}, {"EndDate", "Заканчивается"}, - {"floodfill mode is disabled", "режим флудфила отключен"}, + {"not floodfill", "не флудфил"}, {"Queue size", "Размер очереди"}, {"Run peer test", "Запустить тестирование"}, - {"Reload tunnels configuration", "Перезагрузить конфигурацию туннелей"}, {"Decline transit tunnels", "Отклонять транзитные туннели"}, {"Accept transit tunnels", "Принимать транзитные туннели"}, {"Cancel graceful shutdown", "Отменить плавную остановку"}, @@ -155,10 +146,8 @@ namespace russian // language namespace {"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", "Лизсет не найден или время действия уже истекло"}, - {"Transit tunnels count must not exceed %d", "Число транзитных туннелей не должно превышать %d"}, + {"You will be redirected in 5 seconds", "Вы будете переадресованы через 5 секунд"}, + {"Transit tunnels count must not exceed 65535", "Число транзитных туннелей не должно превышать 65535"}, {"Back to commands list", "Вернуться к списку команд"}, {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, {"Description", "Описание"}, @@ -166,6 +155,7 @@ 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", "Ошибка прокси"}, @@ -175,33 +165,32 @@ namespace russian // language namespace {"You may try to find this host on jump services below", "Вы можете попробовать найти узел через джамп сервисы ниже"}, {"Invalid request", "Некорректный запрос"}, {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, - {"Addresshelper is not supported", "Addresshelper не поддерживается"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Будьте осторожны: источник данной ссылки может быть вредоносным! Нажмите здесь, чтобы обновить запись: Продолжить."}, - {"Addresshelper forced update rejected", "Принудительное обновление через Addresshelper отклонено"}, - {"To add host %s in router's addressbook, click here: Continue.", "Чтобы добавить узел %s в адресную книгу роутера, нажмите здесь: Продолжить."}, - {"Addresshelper request", "Запрос добавления Addresshelper"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Узел %s добавлен в адресную книгу роутера через хелпер. Нажмите здесь, чтобы продолжить: Продолжить."}, - {"Addresshelper adding", "Добавление Addresshelper"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Нажмите здесь, чтобы обновить запись: Продолжить."}, - {"Addresshelper update", "Обновление записи через Addresshelper"}, - {"Invalid request URI", "Некорректный URI запроса"}, + {"addresshelper is not supported", "addresshelper не поддерживается"}, + {"Host", "Узел"}, + {"added to router's addressbook from helper", "добавлен в адресную книгу роутера через хелпер"}, + {"Click here to proceed:", "Нажмите здесь, чтобы продолжить:"}, + {"Continue", "Продолжить"}, + {"Addresshelper found", "Найден addresshelper"}, + {"already in router's addressbook", "уже в адресной книге роутера"}, + {"Click here to update record:", "Нажмите здесь, чтобы обновить запись:"}, + {"invalid request uri", "некорректный URI запроса"}, {"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 прокси"}, + {"bad outproxy settings", "некорректные настройки внешнего прокси"}, + {"not inside I2P network, but outproxy is not enabled", "не в I2P сети, но внешний прокси не включен"}, + {"unknown outproxy url", "неизвестный URL внешнего прокси"}, + {"cannot resolve upstream proxy", "не удается определить вышестоящий прокси"}, + {"hostname too long", "имя хоста слишком длинное"}, + {"cannot connect to upstream socks proxy", "не удается подключиться к вышестоящему SOCKS прокси"}, + {"Cannot negotiate with socks proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, {"CONNECT error", "Ошибка CONNECT запроса"}, - {"Failed to connect", "Не удалось соединиться"}, - {"SOCKS proxy error", "Ошибка SOCKS прокси"}, - {"Failed to send request to upstream", "Не удалось отправить запрос вышестоящему прокси серверу"}, - {"No reply from SOCKS proxy", "Нет ответа от SOCKS прокси сервера"}, - {"Cannot connect", "Не удалось подключиться"}, - {"HTTP out proxy not implemented", "Поддержка внешнего HTTP прокси сервера не реализована"}, - {"Cannot connect to upstream HTTP proxy", "Не удалось подключиться к вышестоящему HTTP прокси серверу"}, + {"Failed to Connect", "Не удалось подключиться"}, + {"socks proxy error", "ошибка SOCKS прокси"}, + {"failed to send request to upstream", "не удалось отправить запрос вышестоящему прокси"}, + {"No Reply From socks proxy", "Нет ответа от SOCKS прокси сервера"}, + {"cannot connect", "не удалось подключиться"}, + {"http out proxy not implemented", "поддержка внешнего HTTP прокси сервера не реализована"}, + {"cannot connect to upstream http proxy", "не удалось подключиться к вышестоящему HTTP прокси серверу"}, {"Host is down", "Узел недоступен"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному узлу, возможно он не в сети. Попробуйте повторить запрос позже."}, {"", ""}, @@ -209,16 +198,16 @@ namespace russian // language namespace static std::map> plurals { - {"%d days", {"%d день", "%d дня", "%d дней"}}, - {"%d hours", {"%d час", "%d часа", "%d часов"}}, - {"%d minutes", {"%d минуту", "%d минуты", "%d минут"}}, - {"%d seconds", {"%d секунду", "%d секунды", "%d секунд"}}, + {"days", {"день", "дня", "дней"}}, + {"hours", {"час", "часа", "часов"}}, + {"minutes", {"минуту", "минуты", "минут"}}, + {"seconds", {"секунду", "секунды", "секунд"}}, {"", {"", "", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp deleted file mode 100644 index 5b51ce4d..00000000 --- a/i18n/Spanish.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Spanish localization file - -namespace i2p -{ -namespace i18n -{ -namespace spanish // language namespace -{ - // language name in lowercase - static std::string language = "spanish"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - // 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", "pendiente"}, - {"failed", "fallido"}, - {"expiring", "expiró"}, - {"established", "establecido"}, - {"unknown", "desconocido"}, - {"exploratory", "exploratorio"}, - {"Purple I2P Webconsole", "Consola web de Purple I2P"}, - {"i2pd webconsole", "Consola web de i2pd"}, - {"Main page", "Inicio"}, - {"Router commands", "Comandos de enrutador"}, - {"Local Destinations", "Destinos locales"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Túneles"}, - {"Transit Tunnels", "Túneles de Tránsito"}, - {"Transports", "Transportes"}, - {"I2P tunnels", "Túneles I2P"}, - {"SAM sessions", "Sesiones SAM"}, - {"ERROR", "ERROR"}, - {"OK", "VALE"}, - {"Testing", "Probando"}, - {"Firewalled", "Con cortafuegos"}, - {"Unknown", "Desconocido"}, - {"Proxy", "Proxy"}, - {"Mesh", "Malla"}, - {"Clock skew", "Reloj desfasado"}, - {"Offline", "Desconectado"}, - {"Symmetric NAT", "NAT simétrico"}, - {"Uptime", "Tiempo en línea"}, - {"Network status", "Estado de red"}, - {"Network status v6", "Estado de red v6"}, - {"Stopping in", "Parando en"}, - {"Family", "Familia"}, - {"Tunnel creation success rate", "Tasa de éxito de creación de túneles"}, - {"Received", "Recibido"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Enviado"}, - {"Transit", "Tránsito"}, - {"Data path", "Ruta de datos"}, - {"Hidden content. Press on text to see.", "Contenido oculto. Presione para ver."}, - {"Router Ident", "Ident del Enrutador"}, - {"Router Family", "Familia de enrutador"}, - {"Router Caps", "Atributos del Enrutador"}, - {"Version", "Versión"}, - {"Our external address", "Nuestra dirección externa"}, - {"supported", "soportado"}, - {"Routers", "Enrutadores"}, - {"Floodfills", "Inundaciones"}, - {"Client Tunnels", "Túneles de cliente"}, - {"Services", "Servicios"}, - {"Enabled", "Activado"}, - {"Disabled", "Desactivado"}, - {"Encrypted B33 address", "Dirección encriptada B33"}, - {"Address registration line", "Línea para registrar direcciones"}, - {"Domain", "Dominio"}, - {"Generate", "Generar"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la cadena resultante solo se puede usar para registrar dominios 2LD (ejemplo.i2p). Para registrar subdominios, por favor utilice i2pd-tools."}, - {"Address", "Dirección"}, - {"Type", "Tipo"}, - {"EncType", "TipoEncrip"}, - {"Inbound tunnels", "Túneles entrantes"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Túneles salientes"}, - {"Tags", "Etiquetas"}, - {"Incoming", "Entrante"}, - {"Outgoing", "Saliente"}, - {"Destination", "Destino"}, - {"Amount", "Cantidad"}, - {"Incoming Tags", "Etiquetas entrantes"}, - {"Tags sessions", "Sesiones de etiquetas"}, - {"Status", "Estado"}, - {"Local Destination", "Destino Local"}, - {"Streams", "Flujos"}, - {"Close stream", "Cerrar flujo"}, - {"I2CP session not found", "Sesión I2CP no encontrada"}, - {"I2CP is not enabled", "I2CP no está activado"}, - {"Invalid", "Inválido"}, - {"Store type", "Tipo de almacenamiento"}, - {"Expires", "Caduca"}, - {"Non Expired Leases", "Sesiones No Expiradas"}, - {"Gateway", "Puerta de enlace"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "FechaVenc"}, - {"Queue size", "Tamaño de cola"}, - {"Run peer test", "Ejecutar prueba de par"}, - {"Decline transit tunnels", "Rechazar túneles de tránsito"}, - {"Accept transit tunnels", "Aceptar túneles de tránsito"}, - {"Cancel graceful shutdown", "Cancelar apagado con gracia"}, - {"Start graceful shutdown", "Iniciar apagado con gracia"}, - {"Force shutdown", "Forzar apagado"}, - {"Reload external CSS styles", "Recargar estilos CSS externos"}, - {"Note: any action done here are not persistent and not changes your config files.", "Nota: cualquier acción hecha aquí no es persistente y no cambia tus archivos de configuración."}, - {"Logging level", "Nivel de registro de errores"}, - {"Transit tunnels limit", "Límite de túneles de tránsito"}, - {"Change", "Cambiar"}, - {"Change language", "Cambiar idioma"}, - {"no transit tunnels currently built", "no hay túneles de tránsito actualmente construidos"}, - {"SAM disabled", "SAM desactivado"}, - {"no sessions currently running", "no hay sesiones ejecutándose ahora"}, - {"SAM session not found", "Sesión SAM no encontrada"}, - {"SAM Session", "Sesión SAM"}, - {"Server Tunnels", "Túneles de Servidor"}, - {"Client Forwards", "Redirecciones de Cliente"}, - {"Server Forwards", "Redirecciones de Servidor"}, - {"Unknown page", "Página desconocida"}, - {"Invalid token", "Token inválido"}, - {"SUCCESS", "ÉXITO"}, - {"Stream closed", "Transmisión cerrada"}, - {"Stream not found or already was closed", "No se encontró la transmisión o ya se cerró"}, - {"Destination not found", "Destino no encontrado"}, - {"StreamID can't be null", "StreamID no puede ser nulo"}, - {"Return to destination page", "Volver a la página de destino"}, - {"Back to commands list", "Volver a lista de comandos"}, - {"Register at reg.i2p", "Registrar en reg.i2p"}, - {"Description", "Descripción"}, - {"A bit information about service on domain", "Un poco de información sobre el servicio en el dominio"}, - {"Submit", "Enviar"}, - {"Domain can't end with .b32.i2p", "El dominio no puede terminar con .b32.i2p"}, - {"Domain must end with .i2p", "El dominio debe terminar con .i2p"}, - {"Such destination is not found", "No se encontró el destino"}, - {"Unknown command", "Comando desconocido"}, - {"Command accepted", "Comando aceptado"}, - {"Proxy error", "Error de proxy"}, - {"Proxy info", "Información del proxy"}, - {"Proxy error: Host not found", "Error de proxy: Host no encontrado"}, - {"Remote host not found in router's addressbook", "Servidor remoto no encontrado en la libreta de direcciones del enrutador"}, - {"You may try to find this host on jump services below", "Puede intentar encontrar este dominio en los siguientes servicios de salto"}, - {"Invalid request", "Solicitud inválida"}, - {"Proxy unable to parse your request", "Proxy no puede procesar su solicitud"}, - {"Invalid request URI", "URI de solicitud inválida"}, - {"Can't detect destination host from request", "No se puede detectar el host de destino de la solicitud"}, - {"Outproxy failure", "Fallo en el proxy saliente"}, - {"Bad outproxy settings", "Configuración de outproxy incorrecta"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Dominio %s no está dentro de la red I2P, pero el proxy de salida no está activado"}, - {"Unknown outproxy URL", "URL de proxy outproxy desconocido"}, - {"Cannot resolve upstream proxy", "No se puede resolver el proxy de upstream"}, - {"Hostname is too long", "Nombre de dominio muy largo"}, - {"Cannot connect to upstream SOCKS proxy", "No se puede conectar al proxy SOCKS principal"}, - {"Cannot negotiate with SOCKS proxy", "No se puede negociar con el proxy SOCKS"}, - {"CONNECT error", "Error de CONNECT"}, - {"Failed to connect", "Error al conectar"}, - {"SOCKS proxy error", "Error de proxy SOCKS"}, - {"Failed to send request to upstream", "No se pudo enviar petición al principal"}, - {"No reply from SOCKS proxy", "Sin respuesta del proxy SOCKS"}, - {"Cannot connect", "No se puede conectar"}, - {"HTTP out proxy not implemented", "Proxy externo HTTP no implementado"}, - {"Cannot connect to upstream HTTP proxy", "No se puede conectar al proxy HTTP principal"}, - {"Host is down", "Servidor caído"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "No se puede crear la conexión al servidor solicitado, puede estar caído. Intente de nuevo más tarde."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d día", "%d días"}}, - {"%d hours", {"%d hora", "%d horas"}}, - {"%d minutes", {"%d minuto", "%d minutos"}}, - {"%d seconds", {"%d segundo", "%d segundos"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp deleted file mode 100644 index 1dcb7e61..00000000 --- a/i18n/Swedish.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* -* Copyright (c) 2023-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Swedish localization file - -namespace i2p -{ -namespace i18n -{ -namespace swedish // language namespace -{ - // language name in lowercase - static std::string language = "swedish"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - // 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", "bygger"}, - {"failed", "misslyckad"}, - {"expiring", "utgår"}, - {"established", "upprättad"}, - {"unknown", "okänt"}, - {"exploratory", "utforskande"}, - {"Purple I2P Webconsole", "Purple I2P Webbkonsoll"}, - {"i2pd webconsole", "i2pd-Webbkonsoll"}, - {"Main page", "Huvudsida"}, - {"Router commands", "Routerkommandon"}, - {"Local Destinations", "Lokala Platser"}, - {"LeaseSets", "Hyresuppsättningar"}, - {"Tunnels", "Tunnlar"}, - {"Transit Tunnels", "Förmedlande Tunnlar"}, - {"Transports", "Transporter"}, - {"I2P tunnels", "I2P-tunnlar"}, - {"SAM sessions", "SAM-perioder"}, - {"ERROR", "FEL"}, - {"OK", "OK"}, - {"Testing", "Prövar"}, - {"Firewalled", "Bakom Brandvägg"}, - {"Unknown", "Okänt"}, - {"Proxy", "Proxy"}, - {"Mesh", "Mesh"}, - {"Clock skew", "Tidsförskjutning"}, - {"Offline", "Nedkopplad"}, - {"Symmetric NAT", "Symmetrisk NAT"}, - {"Full cone NAT", "Full kon NAT"}, - {"No Descriptors", "Inga Beskrivningar"}, - {"Uptime", "Upptid"}, - {"Network status", "Nätverkstillstånd"}, - {"Network status v6", "Nätverkstillstånd v6"}, - {"Stopping in", "Avstängd om"}, - {"Family", "Familj"}, - {"Tunnel creation success rate", "Andel framgångsrika tunnlar"}, - {"Received", "Mottaget"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Skickat"}, - {"Transit", "Förmedlat"}, - {"Data path", "Sökväg"}, - {"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."}, - {"Router Ident", "Routeridentitet"}, - {"Router Family", "Routerfamilj"}, - {"Router Caps", "Routerbegränsningar"}, - {"Version", "Version"}, - {"Our external address", "Vår externa adress"}, - {"supported", "stöds"}, - {"Routers", "Routrar"}, - {"Floodfills", "Översvämningsfyllare"}, - {"Client Tunnels", "Klienttunnlar"}, - {"Services", "Tjänster"}, - {"Enabled", "Påslaget"}, - {"Disabled", "Avslaget"}, - {"Encrypted B33 address", "Krypterad B33-Adress"}, - {"Address registration line", "Adressregistreringsrad"}, - {"Domain", "Domän"}, - {"Generate", "Skapa"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uppmärksamma: den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."}, - {"Address", "Adress"}, - {"Type", "Typ"}, - {"EncType", "EncTyp"}, - {"Inbound tunnels", "Ingående Tunnlar"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Utgående Tunnlar"}, - {"Tags", "Taggar"}, - {"Incoming", "Ingående"}, - {"Outgoing", "Utgående"}, - {"Destination", "Plats"}, - {"Amount", "Mängd"}, - {"Incoming Tags", "Ingående Taggar"}, - {"Tags sessions", "Tagg-perioder"}, - {"Status", "Tillstånd"}, - {"Local Destination", "Lokal Plats"}, - {"Streams", "Strömmar"}, - {"Close stream", "Stäng strömmen"}, - {"Such destination is not found", "En sådan plats hittas ej"}, - {"I2CP session not found", "I2CP-period hittades inte"}, - {"I2CP is not enabled", "I2CP är inte påslaget"}, - {"Invalid", "Ogiltig"}, - {"Store type", "Lagringstyp"}, - {"Expires", "Utgångsdatum"}, - {"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "EndDate"}, - {"floodfill mode is disabled", "Floodfill läget är inaktiverat"}, - {"Queue size", "Köstorlek"}, - {"Run peer test", "Utför utsiktstest"}, - {"Reload tunnels configuration", "Ladda om tunnelkonfiguration"}, - {"Decline transit tunnels", "Avvisa förmedlande tunnlar"}, - {"Accept transit tunnels", "Tillåt förmedlande tunnlar"}, - {"Cancel graceful shutdown", "Avbryt välvillig avstängning"}, - {"Start graceful shutdown", "Påbörja välvillig avstängning"}, - {"Force shutdown", "Tvingad avstängning"}, - {"Reload external CSS styles", "Ladda om externa CSS-stilar"}, - {"Note: any action done here are not persistent and not changes your config files.", "Uppmärksamma: inga ändringar här är beständiga eller påverkar dina inställningsfiler."}, - {"Logging level", "Protokollförningsnivå"}, - {"Transit tunnels limit", "Begränsa förmedlande tunnlar"}, - {"Change", "Ändra"}, - {"Change language", "Ändra språk"}, - {"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"}, - {"SAM disabled", "SAM avslaget"}, - {"no sessions currently running", "inga perioder igång"}, - {"SAM session not found", "SAM-perioder hittades ej"}, - {"SAM Session", "SAM-period"}, - {"Server Tunnels", "Värdtunnlar"}, - {"Client Forwards", "Klientförpassningar"}, - {"Server Forwards", "Värdförpassningar"}, - {"Unknown page", "Okänd sida"}, - {"Invalid token", "Ogiltig polett"}, - {"SUCCESS", "FRAMGÅNG"}, - {"Stream closed", "Ström stängd"}, - {"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"}, - {"Destination not found", "Plats hittades ej"}, - {"StreamID can't be null", "Ström-ID kan inte vara null"}, - {"Return to destination page", "Återvänd till platssidan"}, - {"You will be redirected in %d seconds", "Du omdirigeras inom %d sekunder"}, - {"Transit tunnels count must not exceed %d", "Förmedlande tunnlar får inte överstiga %d"}, - {"Back to commands list", "Tillbaka till kommandolistan"}, - {"Register at reg.i2p", "Registrera vid reg.i2p"}, - {"Description", "Beskrivning"}, - {"A bit information about service on domain", "Ett stycke information om domänens tjänst"}, - {"Submit", "Skicka"}, - {"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"}, - {"Domain must end with .i2p", "Domänen måste sluta med .i2p"}, - {"Unknown command", "Okänt kommando"}, - {"Command accepted", "Kommando accepterades"}, - {"Proxy error", "Proxyfel"}, - {"Proxy info", "Proxyinfo"}, - {"Proxy error: Host not found", "Proxyfel: Värden hittades ej"}, - {"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"}, - {"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"}, - {"Invalid request", "Ogiltig förfrågan"}, - {"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"}, - {"Addresshelper is not supported", "Adresshjälparen stöds ej"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Var försiktig: källan till denna URL kan vara skadlig! Klicka här för att uppdatera registreringen: Fortsätt."}, - {"Addresshelper forced update rejected", "Tvingad uppdatering av adresshjälparen nekad"}, - {"To add host %s in router's addressbook, click here: Continue.", "För att lägga till värd %s i routerns adressbok, klicka här: Fortsätt."}, - {"Addresshelper request", "Adresshjälpare förfrågan"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Värd %s tillagd i routerns adressbok från hjälparen. Klicka här för att fortsätta: Fortsätt."}, - {"Addresshelper adding", "Adresshjälpare tilläggning"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Klicka här för att uppdatera registreringen: Fortsätt."}, - {"Addresshelper update", "Adresshjälpare uppdatering"}, - {"Invalid request URI", "Ogiltig förfrågnings-URI"}, - {"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"}, - {"Outproxy failure", "Utproxyfel"}, - {"Bad outproxy settings", "Ogiltig utproxyinställning"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Värd %s är inte inom I2P-näverket, men utproxy är inte påslaget"}, - {"Unknown outproxy URL", "okänt Utproxy-URL"}, - {"Cannot resolve upstream proxy", "Hittar inte uppströmsproxyt"}, - {"Hostname is too long", "Värdnamnet är för långt"}, - {"Cannot connect to upstream SOCKS proxy", "Kan inte ansluta till uppström SOCKS-proxy"}, - {"Cannot negotiate with SOCKS proxy", "Kan inte förhandla med SOCKSproxyt"}, - {"CONNECT error", "CONNECT-fel"}, - {"Failed to connect", "Anslutningen misslyckades"}, - {"SOCKS proxy error", "SOCKSproxyfel"}, - {"Failed to send request to upstream", "Förfrågan uppströms kunde ej skickas"}, - {"No reply from SOCKS proxy", "Fick inget svar från SOCKSproxyt"}, - {"Cannot connect", "Kan inte ansluta"}, - {"HTTP out proxy not implemented", "HTTP-Utproxy ej implementerat"}, - {"Cannot connect to upstream HTTP proxy", "Kan inte ansluta till uppströms HTTP-proxy"}, - {"Host is down", "Värden är nere"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d dag", "%d dagar"}}, - {"%d hours", {"%d timme", "%d timmar"}}, - {"%d minutes", {"%d minut", "%d minuter"}}, - {"%d seconds", {"%d sekund", "%d sekunder"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p - diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp deleted file mode 100644 index 139b004e..00000000 --- a/i18n/Turkish.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* -* Copyright (c) 2023-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "I18N.h" - -// Turkish localization file - -namespace i2p -{ -namespace i18n -{ -namespace turkish // language namespace -{ - // language name in lowercase - static std::string language = "turkish"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - // 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", "kuruluyor"}, - {"failed", "başarısız"}, - {"expiring", "süresi geçiyor"}, - {"established", "kurulmuş"}, - {"unknown", "bilinmeyen"}, - {"Purple I2P Webconsole", "Mor I2P Webkonsolu"}, - {"i2pd webconsole", "i2pd webkonsolu"}, - {"Main page", "Ana sayfa"}, - {"Router commands", "Router komutları"}, - {"Local Destinations", "Yerel Hedefler"}, - {"Tunnels", "Tüneller"}, - {"Transit Tunnels", "Transit Tünelleri"}, - {"Transports", "Taşıma"}, - {"I2P tunnels", "I2P tünelleri"}, - {"SAM sessions", "SAM oturumları"}, - {"ERROR", "HATA"}, - {"OK", "TAMAM"}, - {"Testing", "Test ediliyor"}, - {"Firewalled", "Güvenlik Duvarı Kısıtlaması"}, - {"Unknown", "Bilinmeyen"}, - {"Proxy", "Proxy"}, - {"Clock skew", "Saat sorunu"}, - {"Offline", "Çevrimdışı"}, - {"Symmetric NAT", "Simetrik NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Tanımlayıcı Yok"}, - {"Uptime", "Bağlantı süresi"}, - {"Network status", "Ağ durumu"}, - {"Network status v6", "Ağ durumu v6"}, - {"Family", "Aile"}, - {"Tunnel creation success rate", "Tünel oluşturma başarı oranı"}, - {"Received", "Alındı"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Gönderildi"}, - {"Transit", "Transit"}, - {"Data path", "Veri yolu"}, - {"Hidden content. Press on text to see.", "Gizlenmiş içerik. Görmek için yazıya tıklayınız."}, - {"Router Family", "Router Familyası"}, - {"Decline transit tunnels", "Transit tünellerini reddet"}, - {"Accept transit tunnels", "Transit tünellerini kabul et"}, - {"Cancel graceful shutdown", "Düzgün durdurmayı iptal Et"}, - {"Start graceful shutdown", "Düzgün durdurmayı başlat"}, - {"Force shutdown", "Durdurmaya zorla"}, - {"Reload external CSS styles", "Harici CSS stilini yeniden yükle"}, - {"Note: any action done here are not persistent and not changes your config files.", "Not: burada yapılan ayarların hiçbiri kalıcı değildir ve ayar dosyalarınızı değiştirmez."}, - {"Logging level", "Kayıt tutma seviyesi"}, - {"Transit tunnels limit", "Transit tünel limiti"}, - {"Change", "Değiştir"}, - {"Change language", "Dil değiştir"}, - {"no transit tunnels currently built", "kurulmuş bir transit tüneli bulunmamakta"}, - {"SAM disabled", "SAM devre dışı"}, - {"no sessions currently running", "hiçbir oturum şu anda çalışmıyor"}, - {"SAM session not found", "SAM oturumu bulunamadı"}, - {"SAM Session", "SAM oturumu"}, - {"Server Tunnels", "Sunucu Tünelleri"}, - {"Unknown page", "Bilinmeyen sayfa"}, - {"Invalid token", "Geçersiz token"}, - {"SUCCESS", "BAŞARILI"}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d gün", "%d gün"}}, - {"%d hours", {"%d saat", "%d saat"}}, - {"%d minutes", {"%d dakika", "%d dakika"}}, - {"%d seconds", {"%d saniye", "%d saniye"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 8cdcae52..356ada85 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,17 @@ namespace turkmen // language namespace return n != 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, {"building", "bina"}, {"failed", "şowsuz"}, {"expiring", "möhleti gutarýar"}, {"established", "işleýär"}, {"unknown", "näbelli"}, {"exploratory", "gözleg"}, - {"Purple I2P Webconsole", "Web konsoly Purple I2P"}, {"i2pd webconsole", "Web konsoly i2pd"}, {"Main page", "Esasy sahypa"}, {"Router commands", "Marşrutizator buýruklary"}, @@ -61,6 +57,7 @@ namespace turkmen // language namespace {"Unknown", "Näbelli"}, {"Proxy", "Proksi"}, {"Mesh", "MESH-tor"}, + {"Error", "Ýalňyşlyk"}, {"Clock skew", "Takyk wagt däl"}, {"Offline", "Awtonom"}, {"Symmetric NAT", "Simmetriklik NAT"}, @@ -71,7 +68,7 @@ namespace turkmen // language namespace {"Family", "Maşgala"}, {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, {"Received", "Alnan"}, - {"%.2f KiB/s", "%.2f KiB/s"}, + {"KiB/s", "KiB/s"}, {"Sent", "Ýerleşdirildi"}, {"Transit", "Tranzit"}, {"Data path", "Maglumat ýoly"}, @@ -97,7 +94,7 @@ namespace turkmen // language namespace {"Type", "Görnüş"}, {"EncType", "Şifrlemek görnüşi"}, {"Inbound tunnels", "Gelýän tuneller"}, - {"%dms", "%dms"}, + {"ms", "ms"}, {"Outbound tunnels", "Çykýan tuneller"}, {"Tags", "Bellikler"}, {"Incoming", "Gelýän"}, @@ -119,6 +116,7 @@ namespace turkmen // language namespace {"Gateway", "Derweze"}, {"TunnelID", "Tuneliň ID"}, {"EndDate", "Gutarýar"}, + {"not floodfill", "fludfil däl"}, {"Queue size", "Nobatyň ululygy"}, {"Run peer test", "Synag başlaň"}, {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, @@ -148,6 +146,8 @@ namespace turkmen // language namespace {"Destination not found", "Niýetlenen ýeri tapylmady"}, {"StreamID can't be null", "StreamID boş bolup bilmez"}, {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, + {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, {"Back to commands list", "Topar sanawyna dolan"}, {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, {"Description", "Beýany"}, @@ -165,24 +165,32 @@ namespace turkmen // language namespace {"You may try to find this host on jump services below", "Aşakdaky böküş hyzmatlarynda bu öý eýesini tapmaga synanyşyp bilersiňiz"}, {"Invalid request", "Nädogry haýyş"}, {"Proxy unable to parse your request", "Proksi haýyşyňyzy derňäp bilmeýär"}, - {"Invalid request URI", "Nädogry haýyş URI"}, + {"addresshelper is not supported", "Salgylandyryjy goldanok"}, + {"Host", "Adres"}, + {"added to router's addressbook from helper", "marşruteriň adresini kömekçiden goşdy"}, + {"Click here to proceed:", "Dowam etmek bu ýerde basyň:"}, + {"Continue", "Dowam et"}, + {"Addresshelper found", "Forgelper tapyldy"}, + {"already in router's addressbook", "marşruteriň adres kitaby"}, + {"Click here to update record:", "Recordazgyny täzelemek üçin bu ýerde basyň:"}, + {"invalid request uri", "nädogry haýyş URI"}, {"Can't detect destination host from request", "Haýyşdan barmaly ýerini tapyp bilemok"}, {"Outproxy failure", "Daşarky proksi ýalňyşlyk"}, - {"Bad outproxy settings", "Daşarky Daşarky proksi sazlamalary nädogry"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Adres %s I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, - {"Unknown outproxy URL", "Näbelli daşarky proksi URL"}, - {"Cannot resolve upstream proxy", "Has ýokary proksi kesgitläp bilmeýär"}, - {"Hostname is too long", "Hoster eýesi ady gaty uzyn"}, - {"Cannot connect to upstream SOCKS proxy", "Ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, - {"Cannot negotiate with SOCKS proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, + {"bad outproxy settings", "daşarky daşarky proksi sazlamalary nädogry"}, + {"not inside I2P network, but outproxy is not enabled", "I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, + {"unknown outproxy url", "näbelli daşarky proksi URL"}, + {"cannot resolve upstream proxy", "has ýokary proksi kesgitläp bilmeýär"}, + {"hostname too long", "hoster eýesi ady gaty uzyn"}, + {"cannot connect to upstream socks proxy", "ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, + {"Cannot negotiate with socks proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, {"CONNECT error", "Bagyr haýyşy säwligi"}, - {"Failed to connect", "Birikdirip bilmedi"}, - {"SOCKS proxy error", "SOCKS proksi ýalňyşlygy"}, - {"Failed to send request to upstream", "Öý eýesi proksi üçin haýyş iberip bilmedi"}, - {"No reply from SOCKS proxy", "Jorap SOCKS proksi serwerinden hiç hili jogap ýok"}, - {"Cannot connect", "Birikdirip bilmedi"}, - {"HTTP out proxy not implemented", "Daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, - {"Cannot connect to upstream HTTP proxy", "Ýokary jorap HTTP proksi bilen birigip bolmaýar"}, + {"Failed to Connect", "Birikdirip bilmedi"}, + {"socks proxy error", "socks proksi ýalňyşlygy"}, + {"failed to send request to upstream", "öý eýesi proksi üçin haýyş iberip bilmedi"}, + {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, + {"cannot connect", "birikdirip bilmedi"}, + {"http out proxy not implemented", "daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, + {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, {"Host is down", "Salgy elýeterli däl"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyşy soň gaýtalamaga synanyşyň."}, {"", ""}, @@ -190,16 +198,16 @@ namespace turkmen // language namespace static std::map> plurals { - {"%d days", {"%d gün", "%d gün"}}, - {"%d hours", {"%d sagat", "%d sagat"}}, - {"%d minutes", {"%d minut", "%d minut"}}, - {"%d seconds", {"%d sekunt", "%d sekunt"}}, + {"days", {"gün", "gün"}}, + {"hours", {"sagat", "sagat"}}, + {"minutes", {"minut", "minut"}}, + {"seconds", {"sekunt", "sekunt"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 3c3c44a4..abbe8f81 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,17 @@ 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; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f КіБ"}, - {"%.2f MiB", "%.2f МіБ"}, - {"%.2f GiB", "%.2f ГіБ"}, + {"KiB", "КіБ"}, + {"MiB", "МіБ"}, + {"GiB", "ГіБ"}, {"building", "будується"}, {"failed", "невдалий"}, {"expiring", "завершується"}, {"established", "працює"}, {"unknown", "невідомо"}, {"exploratory", "дослідницький"}, - {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, {"i2pd webconsole", "Веб-консоль i2pd"}, {"Main page", "Головна"}, {"Router commands", "Команди маршрутизатора"}, @@ -61,20 +57,18 @@ namespace ukrainian // language namespace {"Unknown", "Невідомо"}, {"Proxy", "Проксі"}, {"Mesh", "MESH-мережа"}, + {"Error", "Помилка"}, {"Clock skew", "Неточний час"}, {"Offline", "Офлайн"}, {"Symmetric NAT", "Симетричний NAT"}, - {"Full cone NAT", "Повний NAT"}, - {"No Descriptors", "Немає Описів"}, {"Uptime", "У мережі"}, {"Network status", "Мережевий статус"}, {"Network status v6", "Мережевий статус v6"}, {"Stopping in", "Зупинка через"}, {"Family", "Сімейство"}, {"Tunnel creation success rate", "Успішно побудованих тунелів"}, - {"Total tunnel creation success rate", "Загальна кількість створених тунелів"}, {"Received", "Отримано"}, - {"%.2f KiB/s", "%.2f КіБ/с"}, + {"KiB/s", "КіБ/с"}, {"Sent", "Відправлено"}, {"Transit", "Транзит"}, {"Data path", "Шлях до даних"}, @@ -99,9 +93,8 @@ namespace ukrainian // language namespace {"Address", "Адреса"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, - {"Expire LeaseSet", "Завершити LeaseSet"}, {"Inbound tunnels", "Вхідні тунелі"}, - {"%dms", "%dмс"}, + {"ms", "мс"}, {"Outbound tunnels", "Вихідні тунелі"}, {"Tags", "Теги"}, {"Incoming", "Вхідні"}, @@ -114,7 +107,6 @@ 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", "Некоректний"}, @@ -124,10 +116,9 @@ namespace ukrainian // language namespace {"Gateway", "Шлюз"}, {"TunnelID", "ID тунеля"}, {"EndDate", "Закінчується"}, - {"floodfill mode is disabled", "режим floodfill вимкнено"}, + {"not floodfill", "не флудфіл"}, {"Queue size", "Розмір черги"}, {"Run peer test", "Запустити тестування"}, - {"Reload tunnels configuration", "Перезавантажити налаштування тунелів"}, {"Decline transit tunnels", "Відхиляти транзитні тунелі"}, {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, {"Cancel graceful shutdown", "Скасувати плавну зупинку"}, @@ -155,10 +146,8 @@ namespace ukrainian // language namespace {"Destination not found", "Точка призначення не знайдена"}, {"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"}, + {"You will be redirected in 5 seconds", "Ви будете переадресовані через 5 секунд"}, + {"Transit tunnels count must not exceed 65535", "Кількість транзитних тунелів не повинна перевищувати 65535"}, {"Back to commands list", "Повернутися до списку команд"}, {"Register at reg.i2p", "Зареєструвати на reg.i2p"}, {"Description", "Опис"}, @@ -166,6 +155,7 @@ 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", "Помилка проксі"}, @@ -175,33 +165,32 @@ namespace ukrainian // language namespace {"You may try to find this host on jump services below", "Ви можете спробувати знайти дану адресу на джамп сервісах нижче"}, {"Invalid request", "Некоректний запит"}, {"Proxy unable to parse your request", "Проксі не може розібрати ваш запит"}, - {"Addresshelper is not supported", "Адресна книга не підтримується"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Будьте обережні: джерело цієї адреси може зашкодити! Натисніть тут, щоб оновити запис: Продовжити."}, - {"Addresshelper forced update rejected", "Адресна книга відхилила примусове оновлення"}, - {"To add host %s in router's addressbook, click here: Continue.", "Щоб додати хост %s в адресі маршрутизатора, натисніть тут: Продовжити."}, - {"Addresshelper request", "Запит на адресну сторінку"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Хост %s доданий в адресну книгу маршрутизатора від помічника. Натисніть тут, щоб продовжити: Продовжити."}, - {"Addresshelper adding", "Адреса додана"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Натисніть тут, щоб оновити запис: Продовжити."}, - {"Addresshelper update", "Оновлення адресної книги"}, - {"Invalid request URI", "Некоректний URI запиту"}, + {"addresshelper is not supported", "addresshelper не підтримується"}, + {"Host", "Адреса"}, + {"added to router's addressbook from helper", "доданий в адресну книгу маршрутизатора через хелпер"}, + {"Click here to proceed:", "Натисніть тут щоб продовжити:"}, + {"Continue", "Продовжити"}, + {"Addresshelper found", "Знайдено addresshelper"}, + {"already in router's addressbook", "вже в адресній книзі маршрутизатора"}, + {"Click here to update record:", "Натисніть тут щоб оновити запис:"}, + {"invalid request uri", "некоректний URI запиту"}, {"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 проксі"}, + {"bad outproxy settings", "некоректні налаштування зовнішнього проксі"}, + {"not inside I2P network, but outproxy is not enabled", "не в I2P мережі, але зовнішній проксі не включений"}, + {"unknown outproxy url", "невідомий URL зовнішнього проксі"}, + {"cannot resolve upstream proxy", "не вдається визначити висхідний проксі"}, + {"hostname too long", "ім'я вузла надто довге"}, + {"cannot connect to upstream socks proxy", "не вдається підключитися до висхідного SOCKS проксі"}, + {"Cannot negotiate with socks proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, {"CONNECT error", "Помилка CONNECT запиту"}, - {"Failed to connect", "Не вдалося підключитися"}, - {"SOCKS proxy error", "Помилка SOCKS проксі"}, - {"Failed to send request to upstream", "Не вдалося відправити запит висхідному проксі"}, - {"No reply from SOCKS proxy", "Немає відповіді від SOCKS проксі сервера"}, - {"Cannot connect", "Не вдалося підключитися"}, - {"HTTP out proxy not implemented", "Підтримка зовнішнього HTTP проксі сервера не реалізована"}, - {"Cannot connect to upstream HTTP proxy", "Не вдалося підключитися до висхідного HTTP проксі сервера"}, + {"Failed to Connect", "Не вдалося підключитися"}, + {"socks proxy error", "помилка SOCKS проксі"}, + {"failed to send request to upstream", "не вдалося відправити запит висхідному проксі"}, + {"No Reply From socks proxy", "Немає відповіді від SOCKS проксі сервера"}, + {"cannot connect", "не вдалося підключитися"}, + {"http out proxy not implemented", "підтримка зовнішнього HTTP проксі сервера не реалізована"}, + {"cannot connect to upstream http proxy", "не вдалося підключитися до висхідного HTTP проксі сервера"}, {"Host is down", "Вузол недоступний"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, {"", ""}, @@ -209,16 +198,16 @@ namespace ukrainian // language namespace static std::map> plurals { - {"%d days", {"%d день", "%d дня", "%d днів"}}, - {"%d hours", {"%d годину", "%d години", "%d годин"}}, - {"%d minutes", {"%d хвилину", "%d хвилини", "%d хвилин"}}, - {"%d seconds", {"%d секунду", "%d секунди", "%d секунд"}}, + {"days", {"день", "дня", "днів"}}, + {"hours", {"годину", "години", "годин"}}, + {"minutes", {"хвилину", "хвилини", "хвилин"}}, + {"seconds", {"секунду", "секунди", "секунд"}}, {"", {"", "", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp index 681a8e19..e750918f 100644 --- a/i18n/Uzbek.cpp +++ b/i18n/Uzbek.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2021-2025, The PurpleI2P Project +* Copyright (c) 2021-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,17 @@ namespace uzbek // language namespace return n > 1 ? 1 : 0; } - // Right to Left language? - static bool rtl = false; - - static const LocaleStrings strings + static std::map strings { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, {"building", "yaratilmoqda"}, {"failed", "muvaffaqiyatsiz"}, {"expiring", "muddati tugaydi"}, {"established", "aloqa o'rnatildi"}, {"unknown", "noma'lum"}, {"exploratory", "tadqiqiy"}, - {"Purple I2P Webconsole", "Veb-konsoli Purple I2P"}, {"i2pd webconsole", "i2pd veb-konsoli"}, {"Main page", "Asosiy sahifa"}, {"Router commands", "Router buyruqlari"}, @@ -61,20 +57,18 @@ namespace uzbek // language namespace {"Unknown", "Notanish"}, {"Proxy", "Proksi"}, {"Mesh", "Mesh To'r"}, + {"Error", "Xato"}, {"Clock skew", "Aniq vaqt emas"}, {"Offline", "Oflayn"}, {"Symmetric NAT", "Simmetrik NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Deskriptorlar yo'q"}, {"Uptime", "Ish vaqti"}, {"Network status", "Tarmoq holati"}, {"Network status v6", "Tarmoq holati v6"}, {"Stopping in", "Ichida to'xtatish"}, {"Family", "Oila"}, {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, - {"Total tunnel creation success rate", "Tunnel yaratishning umumiy muvaffaqiyat darajasi"}, {"Received", "Qabul qilindi"}, - {"%.2f KiB/s", "%.2f KiB/s"}, + {"KiB/s", "KiB/s"}, {"Sent", "Yuborilgan"}, {"Transit", "Tranzit"}, {"Data path", "Ma'lumotlar joylanishi"}, @@ -99,9 +93,8 @@ namespace uzbek // language namespace {"Address", "Manzil"}, {"Type", "Turi"}, {"EncType", "ShifrlashTuri"}, - {"Expire LeaseSet", "LeaseSet muddati tugaydi"}, {"Inbound tunnels", "Kirish tunnellari"}, - {"%dms", "%dms"}, + {"ms", "ms"}, {"Outbound tunnels", "Chiquvchi tunnellar"}, {"Tags", "Teglar"}, {"Incoming", "Kiruvchi"}, @@ -114,7 +107,6 @@ 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"}, @@ -124,10 +116,9 @@ namespace uzbek // language namespace {"Gateway", "Kirish yo'li"}, {"TunnelID", "TunnelID"}, {"EndDate", "Tugash Sanasi"}, - {"floodfill mode is disabled", "floodfill rejimi o'chirilgan"}, + {"not floodfill", "floodfill emas"}, {"Queue size", "Navbat hajmi"}, {"Run peer test", "Sinovni boshlang"}, - {"Reload tunnels configuration", "Tunnel konfiguratsiyasini qayta yuklash"}, {"Decline transit tunnels", "Tranzit tunnellarini rad etish"}, {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qilish"}, @@ -155,10 +146,8 @@ namespace uzbek // language namespace {"Destination not found", "Yo'nalish topilmadi"}, {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, {"Return to destination page", "Manzilgoh sahifasiga qaytish"}, - {"You will be redirected in %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"}, + {"You will be redirected in 5 seconds", "Siz 5 soniya ichida qayta yo'naltirilasiz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tunnellar soni 65535 dan oshmasligi kerak"}, {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, {"Description", "Tavsif"}, @@ -166,6 +155,7 @@ 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"}, @@ -175,33 +165,32 @@ namespace uzbek // language namespace {"You may try to find this host on jump services below", "Siz xost quyida o'tish xizmatlari orqali topishga harakat qilishingiz mumkin"}, {"Invalid request", "Noto‘g‘ri so‘rov"}, {"Proxy unable to parse your request", "Proksi sizning so'rovingizni aniqlab ololmayapti"}, - {"Addresshelper is not supported", "Addresshelper qo'llab-quvvatlanmaydi"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Ehtiyot bo'ling: bu URL manbasi zararli bo'lishi mumkin! Yozuvni yangilash uchun bu yerni bosing: Davom etish."}, - {"Addresshelper forced update rejected", "Addresshelperni majburiy yangilash rad etildi"}, - {"To add host %s in router's addressbook, click here: Continue.", "Routerning manzillar kitobiga %s xostini qo'shish uchun bu yerni bosing: Davom etish."}, - {"Addresshelper request", "Addresshelper so'rovi"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Yordamchidan router manzillar kitobiga %s xost qo‘shildi. Davom etish uchun bu yerga bosing: Davom etish."}, - {"Addresshelper adding", "Addresshelperni qo'shish"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Yozuvni yangilash uchun shu yerni bosing: Davom etish."}, - {"Addresshelper update", "Addresshelperni yangilash"}, - {"Invalid request URI", "Noto'g'ri URI so'rovi"}, + {"addresshelper is not supported", "addresshelper qo'llab -quvvatlanmaydi"}, + {"Host", "Xost"}, + {"added to router's addressbook from helper", "'helper'dan routerning 'addressbook'ga qo'shildi"}, + {"Click here to proceed:", "Davom etish uchun shu yerni bosing:"}, + {"Continue", "Davom etish"}, + {"Addresshelper found", "Addresshelper topildi"}, + {"already in router's addressbook", "allaqachon 'addressbook'da yozilgan"}, + {"Click here to update record:", "Yozuvni yangilash uchun shu yerni bosing:"}, + {"invalid request uri", "noto'g'ri URI so'rovi"}, {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, - {"Bad outproxy settings", "Noto'g'ri tashqi proksi-server sozlamalari"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Xost %s I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, - {"Unknown outproxy URL", "Noma'lum outproxy URL"}, - {"Cannot resolve upstream proxy", "Yuqoridagi 'proxy-server'ni aniqlab olib bolmayapti"}, - {"Hostname is too long", "Xost nomi juda uzun"}, - {"Cannot connect to upstream SOCKS proxy", "Yuqori 'SOCKS proxy'ga ulanib bo'lmayapti"}, - {"Cannot negotiate with SOCKS proxy", "'SOCKS proxy' bilan muzokara olib bo'lmaydi"}, + {"bad outproxy settings", "noto'g'ri tashqi proksi-server sozlamalari"}, + {"not inside I2P network, but outproxy is not enabled", "I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, + {"unknown outproxy url", "noma'lum outproxy url"}, + {"cannot resolve upstream proxy", "yuqoridagi 'proxy-server'ni aniqlab olib bolmayapti"}, + {"hostname too long", "xost nomi juda uzun"}, + {"cannot connect to upstream socks proxy", "yuqori 'socks proxy'ga ulanib bo'lmayapti"}, + {"Cannot negotiate with socks proxy", "'Socks proxy' bilan muzokara olib bo'lmaydi"}, {"CONNECT error", "CONNECT xatosi"}, - {"Failed to connect", "Ulanib bo'lmayapti"}, - {"SOCKS proxy error", "'SOCKS proxy' xatosi"}, - {"Failed to send request to upstream", "Yuqori proksi-serveriga so'rovni uborib bo'lmadi"}, - {"No reply from SOCKS proxy", "'SOCKS proxy'dan javob yo'q"}, - {"Cannot connect", "Ulanib bo'lmaydi"}, - {"HTTP out proxy not implemented", "Tashqi HTTP proksi-serverni qo'llab-quvvatlash amalga oshirilmagan"}, - {"Cannot connect to upstream HTTP proxy", "Yuqori 'HTTP proxy'ga ulanib bo'lmayapti"}, + {"Failed to Connect", "Ulanib bo'lmayapti"}, + {"socks proxy error", "'socks proxy' xatosi"}, + {"failed to send request to upstream", "yuqori http proksi-serveriga so'rovni uborib bo'lmadi"}, + {"No Reply From socks proxy", "'Socks proxy'dan javob yo'q"}, + {"cannot connect", "ulanib bo'lmaydi"}, + {"http out proxy not implemented", "tashqi HTTP proksi-serverni qo'llab-quvvatlash amalga oshirilmagan"}, + {"cannot connect to upstream http proxy", "yuqori http 'proxy-server'iga ulanib bo'lmayapti"}, {"Host is down", "Xost ishlamayapti"}, {"Can't create connection to requested host, it may be down. Please try again later.", "Talab qilingan xost bilan aloqa o'rnatilmadi, u ishlamay qolishi mumkin. Iltimos keyinroq qayta urinib ko'ring."}, {"", ""}, @@ -209,16 +198,16 @@ namespace uzbek // language namespace static std::map> plurals { - {"%d days", {"%d kun", "%d kun"}}, - {"%d hours", {"%d soat", "%d soat"}}, - {"%d minutes", {"%d daqiqa", "%d daqiqa"}}, - {"%d seconds", {"%d soniya", "%d soniya"}}, + {"days", {"kun", "kun"}}, + {"hours", {"soat", "soat"}}, + {"minutes", {"daqiqa", "daqiqa"}}, + {"seconds", {"soniya", "soniya"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(language, rtl, strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index bc9da4fb..94446e86 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,7 @@ namespace i2p { namespace data { - static constexpr char T32[32] = + static const 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 constexpr char T64[64] = + static const char T64[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', @@ -54,17 +54,19 @@ namespace data { return T64; } - + /* * Reverse Substitution Table (built in run time) */ + static char iT64[256]; static int isFirstTime = 1; /* * Padding */ - static constexpr char P64 = '='; + + static char P64 = '='; /* * @@ -74,112 +76,131 @@ namespace data * Converts binary encoded data to BASE64 format. * */ - std::string ByteStreamToBase64 (// base64 encoded string - const uint8_t * InBuffer, // Input buffer, binary data - size_t InCount // Number of bytes in the input buffer + + 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 */ ) { 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; - size_t outCount = m ? (4 * (n + 1)) : (4 * n); + if (!m) + outCount = 4 * n; + else + outCount = 4 * (n + 1); - std::string out; - out.reserve (outCount); + if (outCount > len) return 0; + + pd = (unsigned char *)OutBuffer; for ( i = 0; i < n; i++ ) { acc_1 = *ps++; acc_2 = (acc_1 << 4) & 0x30; - acc_1 >>= 2; // base64 digit #1 - out.push_back (T64[acc_1]); + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; acc_1 = *ps++; - acc_2 |= acc_1 >> 4; // base64 digit #2 - out.push_back (T64[acc_2]); + acc_2 |= acc_1 >> 4; /* base64 digit #2 */ + *pd++ = T64[acc_2]; acc_1 &= 0x0f; acc_1 <<= 2; acc_2 = *ps++; - 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]); + acc_1 |= acc_2 >> 6; /* base64 digit #3 */ + *pd++ = T64[acc_1]; + acc_2 &= 0x3f; /* base64 digit #4 */ + *pd++ = T64[acc_2]; } if ( m == 1 ) { acc_1 = *ps++; - 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); + 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; } else if ( m == 2 ) { acc_1 = *ps++; acc_2 = (acc_1 << 4) & 0x3f; - acc_1 >>= 2; // base64 digit #1 - out.push_back (T64[acc_1]); + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; acc_1 = *ps++; - acc_2 |= acc_1 >> 4; // base64 digit #2 - out.push_back (T64[acc_2]); + acc_2 |= acc_1 >> 4; /* base64 digit #2 */ + *pd++ = T64[acc_2]; acc_1 &= 0x0f; - acc_1 <<= 2; // base64 digit #3 - out.push_back (T64[acc_1]); - out.push_back (P64); + acc_1 <<= 2; /* base64 digit #3 */ + *pd++ = T64[acc_1]; + *pd++ = P64; } - return out; - } - + return outCount; + } + /* * * Base64ToByteStream * ------------------ * - * Converts BASE64 encoded string to binary format. If input buffer is + * Converts BASE64 encoded data to binary format. If input buffer is * not properly padded, buffer of negative length is returned * */ - 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 + + 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 */ ) { + unsigned char * ps; unsigned char * pd; unsigned char acc_1; unsigned char acc_2; + int i; + int n; + int m; size_t outCount; - if (base64Str.empty () || base64Str[0] == P64) return 0; - auto d = std::div (base64Str.length (), 4); - if (!d.rem) - outCount = 3 * d.quot; + if (isFirstTime) + iT64Build(); + + n = InCount / 4; + m = InCount % 4; + + if (InCount && !m) + outCount = 3 * n; else return 0; - if (isFirstTime) iT64Build(); + ps = (unsigned char *)(InBuffer + InCount - 1); + while ( *ps-- == P64 ) + outCount--; + ps = (unsigned char *)InBuffer; + + if (outCount > len) + return 0; - 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 (int i = 0; i < d.quot; i++) + for ( i = 0; i < n; i++ ) { - acc_1 = iT64[int(*ps++)]; - acc_2 = iT64[int(*ps++)]; + acc_1 = iT64[*ps++]; + acc_2 = iT64[*ps++]; acc_1 <<= 2; acc_1 |= acc_2 >> 4; *pd++ = acc_1; @@ -187,30 +208,45 @@ namespace data break; acc_2 <<= 4; - acc_1 = iT64[int(*ps++)]; + acc_1 = iT64[*ps++]; acc_2 |= acc_1 >> 2; *pd++ = acc_2; if (pd >= endOfOutBuffer) break; - acc_2 = iT64[int(*ps++)]; + acc_2 = iT64[*ps++]; acc_2 |= acc_1 << 6; *pd++ = acc_2; } return outCount; - } - - std::string ToBase64Standard (std::string_view in) + } + + size_t Base64EncodingBufferSize (const size_t input_size) { - auto str = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length ()); + 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; // replace '-' by '+' and '~' by '/' - for (auto& ch: str) - if (ch == '-') - ch = '+'; - else if (ch == '~') - ch = '/'; - return str; + 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; } /* @@ -231,12 +267,13 @@ namespace data iT64[(int)P64] = 0; } - size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen) + size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen) { - unsigned int tmp = 0, bits = 0; + int tmp = 0, bits = 0; size_t ret = 0; - for (auto ch: base32Str) + for (size_t i = 0; i < len; i++) { + char ch = inBuf[i]; if (ch >= '2' && ch <= '7') // digit ch = (ch - '2') + 26; // 26 means a-z else if (ch >= 'a' && ch <= 'z') @@ -256,15 +293,13 @@ namespace data tmp <<= 5; } return ret; - } - - std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len) + } + + size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen) { - 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) + size_t ret = 0, pos = 1; + int bits = 8, tmp = inBuf[0]; + while (ret < outLen && (bits > 0 || pos < len)) { if (bits < 5) { @@ -284,9 +319,10 @@ namespace data bits -= 5; int ind = (tmp >> bits) & 0x1F; - out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2')); + outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2'); + ret++; } - return out; - } + return ret; + } } } diff --git a/libi2pd/Base.h b/libi2pd/Base.h index 945dc8b3..79152e02 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,42 +11,25 @@ #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); +#include +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 (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'); - } + 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); /** * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes */ - inline size_t Base64EncodingBufferSize(size_t input_size) - { - auto d = std::div (input_size, 3); - if (d.rem) d.quot++; - return 4 * d.quot; - } + size_t Base64EncodingBufferSize(const size_t input_size); + + std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization - 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 2006462a..65e5f78c 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,17 +1,15 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ -#include #include // for crc32 #include #include #include -#include #include #include "Base.h" #include "Crypto.h" @@ -67,10 +65,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 (group, p, x, y, NULL); + EC_POINT_set_affine_coordinates_GFp (group, p, x, y, NULL); EC_POINT * p1 = BlindPublicKeyECDSA (group, p, seed); EC_POINT_free (p); - EC_POINT_get_affine_coordinates (group, p1, x, y, NULL); + EC_POINT_get_affine_coordinates_GFp (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); @@ -90,7 +88,7 @@ namespace data BN_CTX_free (ctx); BN_free (a1); BIGNUM * x = BN_new(), * y = BN_new(); - EC_POINT_get_affine_coordinates (group, p, x, y, NULL); + EC_POINT_get_affine_coordinates_GFp (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); @@ -137,7 +135,7 @@ namespace data //---------------------------------------------------------- const uint8_t B33_TWO_BYTES_SIGTYPE_FLAG = 0x01; - // const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now + const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now const uint8_t B33_PER_CLIENT_AUTH_FLAG = 0x04; BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, bool clientAuth): @@ -154,11 +152,11 @@ namespace data m_BlindedSigType = m_SigType; } - BlindedPublicKey::BlindedPublicKey (std::string_view b33): + BlindedPublicKey::BlindedPublicKey (const std::string& 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, addr, 40); + size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); if (l < 32) { LogPrint (eLogError, "Blinding: Malformed b33 ", b33); @@ -200,7 +198,7 @@ namespace data std::string BlindedPublicKey::ToB33 () const { if (m_PublicKey.size () > 32) return ""; // assume 25519 - uint8_t addr[35]; + uint8_t addr[35]; char str[60]; // TODO: define actual length uint8_t flags = 0; if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; addr[0] = flags; // flags @@ -210,7 +208,8 @@ 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); - return ByteStreamToBase32 (addr, m_PublicKey.size () + 3); + auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); + return std::string (str, str + l); } void BlindedPublicKey::GetCredential (uint8_t * credential) const @@ -297,14 +296,12 @@ namespace data void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const { - EVP_MD_CTX *ctx = EVP_MD_CTX_new (); - - EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); - EVP_DigestUpdate (ctx, p.c_str (), p.length ()); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, p.c_str (), p.length ()); for (const auto& it: bufs) - EVP_DigestUpdate (ctx, it.first, it.second); - EVP_DigestFinal_ex (ctx, (uint8_t * )hash, nullptr); - EVP_MD_CTX_free (ctx); + SHA256_Update (&ctx, it.first, it.second); + SHA256_Final (hash, &ctx); } i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const @@ -323,12 +320,11 @@ namespace data if (publicKeyLength) { auto stA1 = htobe16 (m_BlindedSigType); - 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); + 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); } else LogPrint (eLogError, "Blinding: Blinded key type ", (int)m_BlindedSigType, " is not supported"); diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index fc11f613..c78db003 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include #include -#include #include #include "Identity.h" @@ -24,7 +23,7 @@ namespace data public: BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p + BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p std::string ToB33 () const; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp new file mode 100644 index 00000000..de077e60 --- /dev/null +++ b/libi2pd/BloomFilter.cpp @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "BloomFilter.h" +#include "I2PEndian.h" +#include +#include + +namespace i2p +{ +namespace util +{ + + /** @brief decaying bloom filter implementation */ + class DecayingBloomFilter : public IBloomFilter + { + public: + + DecayingBloomFilter(const std::size_t size) + { + m_Size = size; + m_Data = new uint8_t[size]; + } + + /** @brief implements IBloomFilter::~IBloomFilter */ + ~DecayingBloomFilter() + { + delete [] m_Data; + } + + /** @brief implements IBloomFilter::Add */ + bool Add(const uint8_t * data, std::size_t len) + { + std::size_t idx; + uint8_t mask; + Get(data, len, idx, mask); + if(m_Data[idx] & mask) return false; // filter hit + m_Data[idx] |= mask; + return true; + } + + /** @brief implements IBloomFilter::Decay */ + void Decay() + { + // reset bloom filter buffer + memset(m_Data, 0, m_Size); + } + + private: + /** @brief get bit index for for data */ + void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm) + { + bm = 1; + uint8_t digest[32]; + // TODO: use blake2 because it's faster + SHA256(data, len, digest); + uint64_t i = buf64toh(digest); + idx = i % m_Size; + bm <<= (i % 8); + } + + uint8_t * m_Data; + std::size_t m_Size; + }; + + + BloomFilterPtr BloomFilter(std::size_t capacity) + { + return std::make_shared(capacity); + } +} +} diff --git a/libi2pd/BloomFilter.h b/libi2pd/BloomFilter.h new file mode 100644 index 00000000..ade854e4 --- /dev/null +++ b/libi2pd/BloomFilter.h @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef BLOOM_FILTER_H_ +#define BLOOM_FILTER_H_ +#include +#include + +namespace i2p +{ +namespace util +{ + + /** @brief interface for bloom filter */ + struct IBloomFilter + { + + /** @brief destructor */ + virtual ~IBloomFilter() {}; + /** @brief add entry to bloom filter, return false if filter hit otherwise return true */ + virtual bool Add(const uint8_t * data, std::size_t len) = 0; + /** @brief optionally decay old entries */ + virtual void Decay() = 0; + }; + + typedef std::shared_ptr BloomFilterPtr; + + /** @brief create bloom filter */ + BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8); + +} +} + +#endif diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp new file mode 100644 index 00000000..0804e2ac --- /dev/null +++ b/libi2pd/CPU.cpp @@ -0,0 +1,58 @@ +/* +* 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 3fc38d47..f021bccb 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,16 +9,15 @@ #ifndef LIBI2PD_CPU_H #define LIBI2PD_CPU_H -#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 +namespace i2p +{ +namespace cpu +{ + extern bool aesni; + extern bool avx; + + void Detect(bool AesSwitch, bool AvxSwitch, bool force); +} +} #endif diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp new file mode 100644 index 00000000..66bc135f --- /dev/null +++ b/libi2pd/ChaCha20.cpp @@ -0,0 +1,137 @@ +/* +* 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 new file mode 100644 index 00000000..4364024b --- /dev/null +++ b/libi2pd/ChaCha20.h @@ -0,0 +1,72 @@ +/* +* 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 d87b3ba1..87a9e091 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,7 +45,7 @@ namespace config { ("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)") ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") - ("host", value()->default_value(""), "External IP") + ("host", value()->default_value("0.0.0.0"), "External IP") ("ifname", value()->default_value(""), "Network interface to bind to") ("ifname4", value()->default_value(""), "Network interface to bind to for ipv4") ("ifname6", value()->default_value(""), "Network interface to bind to for ipv6") @@ -64,7 +64,7 @@ namespace config { ("bandwidth", value()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") - ("ssu", bool_switch()->default_value(false), "Ignored. Always false") + ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Ignored") @@ -76,17 +76,11 @@ 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)") -#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") + ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") + ("limits.ntcpsoft", value()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)") + ("limits.ntcphard", value()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") + ("limits.ntcpthreads", value()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)") ; options_description httpserver("HTTP Server options"); @@ -101,8 +95,6 @@ namespace config { ("http.hostname", value()->default_value("localhost"), "Expected hostname for WebUI") ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") ("http.lang", value()->default_value("english"), "WebUI language (default: english )") - ("http.showTotalTCSR", value()->default_value(false), "Show additional value with total TCSR since router's start (default: false)") - ("http.theme", value()->default_value("light"), "Theme for http web console") ; options_description httpproxy("HTTP Proxy options"); @@ -123,18 +115,9 @@ 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") -#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.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("httpproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") - ("httpproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec") - ("httpproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec") - ("httpproxy.i2p.streaming.profile", value()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") - ("httpproxy.i2p.streaming.maxWindowSize", value()->default_value("512"), "HTTP Proxy stream max window size. 512 by default") ; options_description socksproxy("SOCKS Proxy options"); @@ -157,39 +140,15 @@ 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") -#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.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ("socksproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") - ("socksproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec") - ("socksproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec") - ("socksproxy.i2p.streaming.profile", value()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") - ("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 TCP port") - ("sam.portudp", value()->default_value(0), "SAM listen UDP port") + ("sam.port", value()->default_value(7656), "SAM listen port") ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") ; @@ -206,8 +165,6 @@ 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"); @@ -233,7 +190,7 @@ namespace config { options_description precomputation("Precomputation options"); precomputation.add_options() ("precomputation.elgamal", -#if (defined(_M_AMD64) || defined(__x86_64__)) +#if defined(__x86_64__) value()->default_value(false), #else value()->default_value(true), @@ -245,7 +202,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(""), "Ignored. Always empty") + ("reseed.floodfill", value()->default_value(""), "Path to router info of floodfill to reseed from") ("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") @@ -253,26 +210,26 @@ 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://coconut.incognet.io/," + "https://i2p.novg.net/," + "https://banana.incognet.io/," "https://reseed-pl.i2pd.xyz/," - "https://www2.mk16.de/," - "https://i2p.novg.net/," - "https://reseed.stormycloud.org/" + "https://www2.mk16.de/" ), "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") ; options_description addressbook("AddressBook options"); addressbook.add_options() - ("addressbook.enabled", value()->default_value(true), "Enable address book lookups and subscritions (default: enabled)") ("addressbook.defaulturl", value()->default_value( "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" ), "AddressBook subscription URL for initial setup") @@ -315,20 +272,15 @@ namespace config { ; options_description ssu2("SSU2 Options"); - ssu2.add_options() - ("ssu2.enabled", value()->default_value(true), "Enable SSU2 (default: enabled)") - ("ssu2.published", value()->default_value(true), "Publish SSU2 (default: enabled)") + ntcp2.add_options() + ("ssu2.enabled", value()->default_value(false), "Enable SSU2 (default: disabled)") + ("ssu2.published", value()->default_value(false), "Publish SSU2 (default: disabled)") ("ssu2.port", value()->default_value(0), "Port to listen for incoming SSU2 packets (default: auto)") - ("ssu2.mtu4", value()->default_value(0), "MTU for ipv4 address (default: detect)") - ("ssu2.mtu6", value()->default_value(0), "MTU for ipv6 address (default: detect)") - ("ssu2.proxy", value()->default_value(""), "Socks5 proxy URL for SSU2 transport") - ("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"); nettime.add_options() - ("nettime.enabled", value()->default_value(false), "Enable NTP time sync (default: disabled)") + ("nettime.enabled", value()->default_value(false), "Disable time sync (default: disabled)") ("nettime.ntpservers", value()->default_value( "0.pool.ntp.org," "1.pool.ntp.org," @@ -345,11 +297,11 @@ namespace config { ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; - options_description cpuext("CPU encryption extensions options. Deprecated"); + options_description cpuext("CPU encryption extensions options"); cpuext.add_options() - ("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") + ("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") ; options_description meshnets("Meshnet transports options"); @@ -358,20 +310,12 @@ namespace config { ("meshnets.yggaddress", value()->default_value(""), "Yggdrasil address to publish") ; -#ifdef __linux__ - options_description unix_specific("UNIX-specific options"); - unix_specific.add_options() - ("unix.handle_sigtstp", bool_switch()->default_value(false), "Handle SIGTSTP and SIGCONT signals (default: disabled)") - ; -#endif - m_OptionsDesc .add(general) .add(limits) .add(httpserver) .add(httpproxy) .add(socksproxy) - .add(shareddest) .add(sam) .add(bob) .add(i2cp) @@ -389,9 +333,6 @@ namespace config { .add(persist) .add(cpuext) .add(meshnets) -#ifdef __linux__ - .add(unix_specific) -#endif ; } diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index dd43d045..d7fb965e 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,12 +16,13 @@ #include #include "TunnelBase.h" #include +#if OPENSSL_HKDF #include -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#include #endif -#include "CPU.h" +#if !OPENSSL_AEAD_CHACHA20_POLY1305 +#include "ChaCha20.h" +#include "Poly1305.h" +#endif #include "Crypto.h" #include "Ed25519.h" #include "I2PEndian.h" @@ -31,7 +32,7 @@ namespace i2p { namespace crypto { - constexpr uint8_t elgp_[256]= + const 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, @@ -51,9 +52,9 @@ namespace crypto 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - constexpr int elgg_ = 2; + const int elgg_ = 2; - constexpr uint8_t dsap_[128]= + const 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, @@ -65,13 +66,13 @@ namespace crypto 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 }; - constexpr uint8_t dsaq_[20]= + const uint8_t dsaq_[20]= { 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, 0x68, 0x40, 0x46, 0xb7 }; - constexpr uint8_t dsag_[128]= + const 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, @@ -83,7 +84,7 @@ namespace crypto 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 }; - constexpr int rsae_ = 65537; + const int rsae_ = 65537; struct CryptoConstants { @@ -148,37 +149,6 @@ 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 (); @@ -186,14 +156,11 @@ namespace crypto DSA_set0_key (dsa, NULL, NULL); return dsa; } -#endif - + // DH/ElGamal -#if !IS_X86_64 const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; -#endif const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; @@ -272,15 +239,69 @@ namespace crypto static BIGNUM * (* g_ElggTable)[255] = nullptr; +// DH + + DHKeys::DHKeys () + { + m_DH = DH_new (); + DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); + DH_set0_key (m_DH, NULL, NULL); + } + + DHKeys::~DHKeys () + { + DH_free (m_DH); + } + + void DHKeys::GenerateKeys () + { + BIGNUM * priv_key = NULL, * pub_key = NULL; +#if !defined(__x86_64__) // use short exponent for non x64 + priv_key = BN_new (); + BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); +#endif + if (g_ElggTable) + { +#if defined(__x86_64__) + priv_key = BN_new (); + BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); +#endif + auto ctx = BN_CTX_new (); + pub_key = ElggPow (priv_key, g_ElggTable, ctx); + DH_set0_key (m_DH, pub_key, priv_key); + BN_CTX_free (ctx); + } + else + { + DH_set0_key (m_DH, NULL, priv_key); + DH_generate_key (m_DH); + DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key); + } + + bn2buf (pub_key, m_PublicKey, 256); + } + + void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) + { + BIGNUM * pk = BN_bin2bn (pub, 256, NULL); + DH_compute_key (shared, pk, m_DH); + BN_free (pk); + } + // x25519 X25519Keys::X25519Keys () { +#if OPENSSL_X25519 m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL); m_Pkey = nullptr; +#else + m_Ctx = BN_CTX_new (); +#endif } X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub) { +#if OPENSSL_X25519 m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); if (pub) @@ -290,16 +311,29 @@ 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); @@ -311,11 +345,16 @@ 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; @@ -323,17 +362,25 @@ 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,6 +390,11 @@ 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 @@ -356,7 +408,7 @@ namespace crypto BIGNUM * b1 = BN_CTX_get (ctx); BIGNUM * b = BN_CTX_get (ctx); // select random k -#if IS_X86_64 +#if defined(__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 @@ -423,7 +475,7 @@ namespace crypto void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub) { -#if IS_X86 || defined(_MSC_VER) +#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) RAND_bytes (priv, 256); #else // lower 226 bits (28 bytes and 2 bits) only. short exponent @@ -456,14 +508,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 (curve, p, x, y, nullptr); + EC_POINT_get_affine_coordinates_GFp (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 (curve, p, x, y, nullptr); + EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); bn2buf (y, iv, len); @@ -476,8 +528,9 @@ namespace crypto // encrypt CBCEncryption encryption; encryption.SetKey (shared); + encryption.SetIV (iv); encrypted[257] = 0; - encryption.Encrypt (m, 256, iv, encrypted + 258); + encryption.Encrypt (m, 256, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); BN_CTX_free (ctx); @@ -496,11 +549,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 (curve, p, x, y, nullptr)) + if (EC_POINT_set_affine_coordinates_GFp (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 (curve, s, x, y, nullptr); + EC_POINT_get_affine_coordinates_GFp (curve, s, x, y, nullptr); EC_POINT_free (s); uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); @@ -510,7 +563,8 @@ namespace crypto uint8_t m[256]; CBCDecryption decryption; decryption.SetKey (shared); - decryption.Decrypt (encrypted + 258, 256, iv, m); + decryption.SetIV (iv); + decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -547,115 +601,507 @@ namespace crypto BN_CTX_free (ctx); } +// HMAC + const uint64_t IPAD = 0x3636363636363636; + const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; + + + static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; + static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; + + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) + // key is 32 bytes + // digest is 16 bytes + // block size is 64 bytes + { + uint64_t buf[256]; + uint64_t hash[12]; // 96 bytes +#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) + if(i2p::cpu::avx) + { + __asm__ + ( + "vmovups %[key], %%ymm0 \n" + "vmovups %[ipad], %%ymm1 \n" + "vmovups %%ymm1, 32(%[buf]) \n" + "vxorps %%ymm0, %%ymm1, %%ymm1 \n" + "vmovups %%ymm1, (%[buf]) \n" + "vmovups %[opad], %%ymm1 \n" + "vmovups %%ymm1, 32(%[hash]) \n" + "vxorps %%ymm0, %%ymm1, %%ymm1 \n" + "vmovups %%ymm1, (%[hash]) \n" + "vzeroall \n" // end of AVX + "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes + : + : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), + [buf]"r"(buf), [hash]"r"(hash) + : "memory", "%xmm0" // TODO: change to %ymm0 later + ); + } + else +#endif + { + // ikeypad + buf[0] = key.GetLL ()[0] ^ IPAD; + buf[1] = key.GetLL ()[1] ^ IPAD; + buf[2] = key.GetLL ()[2] ^ IPAD; + buf[3] = key.GetLL ()[3] ^ IPAD; + buf[4] = IPAD; + buf[5] = IPAD; + buf[6] = IPAD; + buf[7] = IPAD; + // okeypad + hash[0] = key.GetLL ()[0] ^ OPAD; + hash[1] = key.GetLL ()[1] ^ OPAD; + hash[2] = key.GetLL ()[2] ^ OPAD; + hash[3] = key.GetLL ()[3] ^ OPAD; + hash[4] = OPAD; + hash[5] = OPAD; + hash[6] = OPAD; + hash[7] = OPAD; + // fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P) + memset (hash + 10, 0, 16); + } + + // concatenate with msg + memcpy (buf + 8, msg, len); + // calculate first hash + MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes + + // calculate digest + MD5((uint8_t *)hash, 96, digest); + } + // AES - ECBEncryption::ECBEncryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - ECBEncryption::~ECBEncryption () - { - 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 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 - ECBDecryption::ECBDecryption () +#ifdef __AES__ + void ECBCryptoAESNI::ExpandKey (const AESKey& 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); + __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 + ); } +#endif - CBCEncryption::CBCEncryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - CBCEncryption::~CBCEncryption () +#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) { - 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) +#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); + } + } + +#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) + { +#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); + } + } + +#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); + } + } + + void ECBDecryption::SetKey (const AESKey& key) + { +#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) { // len/16 - 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); + int numBlocks = len >> 4; + if (numBlocks > 0) + Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } - CBCDecryption::CBCDecryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); + 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 () + + void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * 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) +#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) { - // 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); + 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); } void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { - 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 +#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 + } } void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) { - 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 +#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 + } } // AEAD/ChaCha20/Poly1305 - 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) + 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 (!ctx || len < msgLen) return false; + if (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); @@ -663,20 +1109,11 @@ 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, &outlen); + EVP_EncryptFinal_ex(ctx, buf, &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)); @@ -685,103 +1122,144 @@ namespace crypto EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen); ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0; } - return ret; - } - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) - { - EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new (); - auto ret = AEADChaCha20Poly1305 (ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, encrypt); EVP_CIPHER_CTX_free (ctx); +#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; } - 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) + void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac) { if (bufs.empty ()) return; +#if OPENSSL_AEAD_CHACHA20_POLY1305 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); + 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(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); + EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second); + EVP_EncryptFinal_ex(ctx, NULL, &outlen); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); + EVP_CIPHER_CTX_free (ctx); +#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 } - 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) + void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) { +#if OPENSSL_AEAD_CHACHA20_POLY1305 + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); uint32_t iv[4]; iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv); int outlen = 0; EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen); EVP_EncryptFinal_ex(ctx, NULL, &outlen); - } - - void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) - { - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); - ChaCha20 (ctx, msg, msgLen, key, nonce, out); EVP_CIPHER_CTX_free (ctx); +#else + chacha::Chacha20State state; + chacha::Chacha20Init (state, nonce, key, 1); + if (out != msg) memcpy (out, msg, msgLen); + chacha::Chacha20Encrypt (state, out, msgLen); +#endif } - - ChaCha20Context::ChaCha20Context () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - ChaCha20Context::~ChaCha20Context () - { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) - { - ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out); - } - - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, std::string_view info, + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& 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()); @@ -799,141 +1277,118 @@ 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.data (), info.length ()); + EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), 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) { - 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); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (m_H, &ctx); } void NoiseSymmetricState::MixHash (const std::vector >& bufs) { - EVP_MD_CTX *ctx = EVP_MD_CTX_new (); - EVP_DigestInit_ex(ctx, EVP_sha256 (), NULL); - EVP_DigestUpdate (ctx, m_H, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); for (const auto& it: bufs) - EVP_DigestUpdate (ctx, it.first, it.second); - EVP_DigestFinal_ex (ctx, m_H, nullptr); - EVP_MD_CTX_free (ctx); + SHA256_Update (&ctx, it.first, it.second); + SHA256_Final (m_H, &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; } - bool NoiseSymmetricState::Encrypt (const uint8_t * in, uint8_t * out, size_t len) + static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck, + const uint8_t * hh, const uint8_t * 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; + // 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) } - 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 constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - static constexpr uint8_t hh[32] = + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static const 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) - state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 + InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 } void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) { - static constexpr uint8_t protocolNameHash[32] = + static const 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 constexpr uint8_t hh[32] = + static const 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) - state.Init (protocolNameHash, hh, pub); + InitNoiseState (state, protocolNameHash, hh, pub); } void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) { - static constexpr uint8_t protocolNameHash[32] = + static const 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 constexpr uint8_t hh[32] = + static const 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) - state.Init (protocolNameHash, hh, pub); + InitNoiseState (state, protocolNameHash, hh, pub); } void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) { - static constexpr uint8_t protocolNameHash[32] = + static const 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 constexpr uint8_t hh[32] = + static const 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) - state.Init (protocolNameHash, hh, pub); + InitNoiseState (state, protocolNameHash, hh, pub); } - + // init and terminate /* std::vector > m_OpenSSLMutexes; @@ -948,15 +1403,19 @@ namespace crypto } }*/ - void InitCrypto (bool precomputation) + void InitCrypto (bool precomputation, bool aesni, bool avx, bool force) { + 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 IS_X86_64 +#if defined(__x86_64__) g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); #else @@ -971,7 +1430,7 @@ namespace crypto if (g_ElggTable) { DestroyElggTable (g_ElggTable, -#if IS_X86_64 +#if defined(__x86_64__) ELGAMAL_FULL_EXPONENT_NUM_BYTES #else ELGAMAL_SHORT_EXPONENT_NUM_BYTES diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 3b9dfb93..c6dcd2cc 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -22,17 +21,33 @@ #include #include #include +#include #include #include "Base.h" #include "Tag.h" +#include "CPU.h" // recognize openssl version and features -#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 +#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 #endif namespace i2p @@ -42,15 +57,29 @@ namespace crypto bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); // DSA -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr); -#else DSA * CreateDSA (); -#endif // RSA const BIGNUM * GetRSAE (); + // DH + class DHKeys + { + public: + + DHKeys (); + ~DHKeys (); + + void GenerateKeys (); + const uint8_t * GetPublicKey () const { return m_PublicKey; }; + void Agree (const uint8_t * pub, uint8_t * shared); + + private: + + DH * m_DH; + uint8_t m_PublicKey[256]; + }; + // x25519 class X25519Keys { @@ -72,8 +101,13 @@ 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 }; @@ -87,71 +121,147 @@ namespace crypto bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); + // HMAC + typedef i2p::data::Tag<32> MACKey; + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); + // AES - typedef i2p::data::Tag<32> AESKey; - - class ECBEncryption + struct ChipherBlock { - public: + uint8_t buf[16]; - ECBEncryption (); - ~ECBEncryption (); - - void SetKey (const uint8_t * key) { m_Key = key; }; - void Encrypt(const uint8_t * in, uint8_t * out); - - private: - - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + 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]; + } + } }; - class ECBDecryption + typedef i2p::data::Tag<32> AESKey; + + template + class AESAlignedBuffer // 16 bytes alignment { public: - ECBDecryption (); - ~ECBDecryption (); - - void SetKey (const uint8_t * key) { m_Key = key; }; - void Decrypt (const uint8_t * in, uint8_t * out); - + 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: - - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + + 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); + + void Encrypt(const ChipherBlock * in, ChipherBlock * out); + + private: + AES_KEY m_Key; + }; + +#ifdef __AES__ + class ECBDecryption: public ECBCryptoAESNI +#else + class ECBDecryption +#endif + { + public: + + void SetKey (const AESKey& key); + void Decrypt (const ChipherBlock * in, ChipherBlock * out); + private: + AES_KEY m_Key; }; class CBCEncryption { public: - CBCEncryption (); - ~CBCEncryption (); + 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; } - 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: - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + AESAlignedBuffer<16> m_LastBlock; + + ECBEncryption m_ECBEncryption; }; class CBCDecryption { public: - 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); + 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; } private: - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + AESAlignedBuffer<16> m_IV; + ECBDecryption m_ECBDecryption; }; class TunnelEncryption // with double IV encryption @@ -191,88 +301,112 @@ 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 - class AEADChaCha20Poly1305Encryptor - { - public: + void AEADChaCha20Poly1305Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad - 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, std::string_view 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, const std::string& 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); + void InitCrypto (bool precomputation, bool aesni, bool avx, bool force); 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 1a448ed1..ad986129 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, 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 (m_Curve, m_PublicKey, x, y, nullptr)) + if (!EC_POINT_set_affine_coordinates_GFp (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 (curve, p, x, y, NULL); + EC_POINT_get_affine_coordinates_GFp (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 (curve->GetGroup (), m_PublicKey, x, y, nullptr)) + if (!EC_POINT_set_affine_coordinates_GFp (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 (curve->GetGroup (), p, x, y, NULL); + EC_POINT_get_affine_coordinates_GFp (curve->GetGroup (), p, x, y, NULL); bn2buf (x, pub, 32); bn2buf (y, pub + 32, 32); RAND_bytes (pub + 64, 192); @@ -181,21 +181,5 @@ 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 099bdd56..a7d86d09 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include #include "Crypto.h" -#include "Identity.h" namespace i2p { @@ -158,50 +157,7 @@ namespace crypto X25519Keys m_StaticKeys; }; - 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 (); - }; + void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); } } diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index eff2533a..30635b09 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,10 +18,8 @@ namespace i2p { namespace datagram { - 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) + DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): + m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { if (m_Gzip) m_Deflator.reset (new i2p::data::GzipDeflator); @@ -60,56 +58,25 @@ namespace datagram { if (session) { - std::shared_ptr msg; - switch (session->GetVersion ()) + if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { - 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 - - break; - } - default: - LogPrint (eLogError, "Datagram: datagram type ", (int)session->GetVersion (), " is not supported"); + uint8_t hash[32]; + SHA256(payload, len, hash); + m_Owner->Sign (hash, 32, m_Signature.data ()); } - if (msg) session->SendMsg(msg); + else + m_Owner->Sign (payload, len, m_Signature.data ()); + + 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); } } 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, i2p::client::PROTOCOL_TYPE_RAW, !session->IsRatchets ())); // raw + session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw } void DatagramDestination::FlushSendQueue (std::shared_ptr session) @@ -118,50 +85,27 @@ namespace datagram session->FlushSendQueue (); } - void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, - const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { 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 (from) + if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { - 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); - } + 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 session = ObtainSession (identity.GetIdentHash()); - if (ls) session->SetRemoteLeaseSet (ls); + auto h = identity.GetIdentHash(); + auto session = ObtainSession(h); session->Ack(); auto r = FindReceiver(toPort); if(r) @@ -175,234 +119,33 @@ namespace datagram void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - auto r = FindRawReceiver(toPort); - - if (r) - r (fromPort, toPort, buf, len); + if (m_RawReceiver) + m_RawReceiver (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 = nullptr; + Receiver r = m_Receiver; auto itr = m_ReceiversByPorts.find(port); if (itr != m_ReceiversByPorts.end()) r = itr->second; - else { - r = m_DefaultReceiver; - } return r; } - DatagramDestination::RawReceiver DatagramDestination::FindRawReceiver(uint16_t port) - { - std::lock_guard lock(m_RawReceiversMutex); - RawReceiver r = nullptr; - auto itr = m_RawReceiversByPorts.find(port); - if (itr != m_RawReceiversByPorts.end()) - r = itr->second; - else { - r = m_DefaultRawReceiver; - } - return r; - } - - void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, - const uint8_t * buf, size_t len, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from) + void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw) { // unzip it uint8_t uncompressed[MAX_DATAGRAM_SIZE]; size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE); if (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); - }; + if (isRaw) + HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen); + else + HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); } else LogPrint (eLogWarning, "Datagram: decompression failed"); @@ -410,7 +153,7 @@ namespace datagram std::shared_ptr DatagramDestination::CreateDataMessage ( const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, uint8_t protocolType, bool checksum) + uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); @@ -426,8 +169,8 @@ namespace datagram { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, fromPort); // source port - htobe16buf (buf + 6, toPort); // destination port - buf[9] = protocolType; // raw or datagram protocol + htobe16buf (buf + 6, toPort); // destination port + buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol msg->len += size + 4; msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } @@ -462,16 +205,14 @@ 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.emplace (identity, session); - } - else + m_Sessions[identity] = session; + } else { session = itr->second; + } return session; } @@ -487,8 +228,9 @@ namespace datagram DatagramSession::DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent) : - m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), - m_LastUse (0), m_LastFlush (0), m_RequestingLS (false), m_Version (eDatagramV1) + m_LocalDestination(localDestination), + m_RemoteIdent(remoteIdent), + m_RequestingLS(false) { } @@ -508,12 +250,8 @@ 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 || - m_LastUse > m_LastFlush + DATAGRAM_MAX_FLUSH_INTERVAL) - { + if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); - m_LastFlush = m_LastUse; - } } DatagramSession::Info DatagramSession::GetSessionInfo() const @@ -546,7 +284,7 @@ namespace datagram if(path) path->updateTime = i2p::util::GetSecondsSinceEpoch (); if (IsRatchets ()) - SendMsg (nullptr); // send empty message in case if we don't have some data to send + SendMsg (nullptr); // send empty message in case if we have some data to send } std::shared_ptr DatagramSession::GetSharedRoutingPath () @@ -579,19 +317,15 @@ namespace datagram if (!found) { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (m_RoutingSession) - { - m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL); - if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) - m_PendingRoutingSessions.push_back (m_RoutingSession); - } + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) + m_PendingRoutingSessions.push_back (m_RoutingSession); } } auto path = m_RoutingSession->GetSharedRoutingPath(); - if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) + if (path && m_RoutingSession->IsRatchets () && + m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) { - LogPrint (eLogDebug, "Datagram: path reset"); m_RoutingSession->SetSharedRoutingPath (nullptr); path = nullptr; } @@ -619,14 +353,7 @@ namespace datagram auto sz = ls.size(); if (sz) { - int idx = -1; - if (m_LocalDestination) - { - auto pool = m_LocalDestination->GetTunnelPool (); - if (pool) - idx = pool->GetRng ()() % sz; - } - if (idx < 0) idx = rand () % sz; + auto idx = rand() % sz; path->remoteLease = ls[idx]; } else @@ -652,14 +379,7 @@ namespace datagram auto sz = ls.size(); if (sz) { - int idx = -1; - if (m_LocalDestination) - { - auto pool = m_LocalDestination->GetTunnelPool (); - if (pool) - idx = pool->GetRng ()() % sz; - } - if (idx < 0) idx = rand () % sz; + auto idx = rand() % sz; path->remoteLease = ls[idx]; } else @@ -705,7 +425,7 @@ namespace datagram if (m) send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m}); } - routingPath->outboundTunnel->SendTunnelDataMsgs(send); + routingPath->outboundTunnel->SendTunnelDataMsg(send); } m_SendQueue.clear(); } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index f962d402..a55c8edf 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,12 +15,10 @@ #include #include #include "Base.h" -#include "Gzip.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" #include "Garlic.h" -#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -32,6 +30,8 @@ namespace datagram { // milliseconds for max session idle time const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; + // milliseconds for how long we try sticking to a dead routing path before trying to switch + const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000; // milliseconds interval a routing path is used before switching const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; // milliseconds before lease expire should we try switching leases @@ -42,20 +42,7 @@ 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 { @@ -76,12 +63,8 @@ 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 (); } - void SetRemoteLeaseSet (std::shared_ptr ls) { m_RemoteLeaseSet = ls; } + bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } - DatagramVersion GetVersion () const { return m_Version; } - void SetVersion (DatagramVersion version) { m_Version = version; } - struct Info { std::shared_ptr IBGW; @@ -114,9 +97,8 @@ namespace datagram std::shared_ptr m_RoutingSession; std::vector > m_PendingRoutingSessions; std::vector > m_SendQueue; - uint64_t m_LastUse, m_LastFlush; // milliseconds + uint64_t m_LastUse; bool m_RequestingLS; - DatagramVersion m_Version; }; typedef std::shared_ptr DatagramSession_ptr; @@ -129,7 +111,7 @@ namespace datagram public: - DatagramDestination (std::shared_ptr owner, bool gzip, DatagramVersion version); + DatagramDestination (std::shared_ptr owner, bool gzip); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); @@ -141,14 +123,16 @@ 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, uint8_t protocolType, i2p::garlic::ECIESX25519AEADRatchetSession * from); + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); - void SetReceiver (const Receiver& receiver, uint16_t port); - void ResetReceiver (uint16_t port); + void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; + void ResetReceiver () { m_Receiver = nullptr; }; - void SetRawReceiver (const RawReceiver& receiver, uint16_t port); - void ResetRawReceiver (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; }; std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); @@ -160,37 +144,25 @@ 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, uint8_t protocolType, bool checksum = true); + uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true); - void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, - i2p::garlic::ECIESX25519AEADRatchetSession * from); + void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - 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; - - 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; + std::map m_ReceiversByPorts; - 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 ae6e46ef..719f5830 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,10 +11,8 @@ #include #include #include -#include #include #include "Crypto.h" -#include "ECIESX25519AEADRatchetSession.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" @@ -25,7 +23,7 @@ namespace i2p { namespace client { - LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service, + LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map * params): m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), @@ -39,7 +37,6 @@ 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 { @@ -94,8 +91,10 @@ namespace client } it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); if (it != params->end ()) - // override isPublic - m_IsPublic = GetBoolParamValue (it->second); + { + // oveeride isPublic + m_IsPublic = (it->second != "true"); + } it = params->find (I2CP_PARAM_LEASESET_TYPE); if (it != params->end ()) m_LeaseSetType = std::stoi(it->second); @@ -109,7 +108,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); @@ -118,13 +117,10 @@ namespace client m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>()); if (m_LeaseSetPrivKey->FromBase64 (it->second) != 32) { - LogPrint(eLogCritical, "Destination: Invalid value i2cp.leaseSetPrivKey: ", it->second); + LogPrint(eLogError, "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) @@ -132,7 +128,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, isHighBandwidth); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -168,7 +164,7 @@ namespace client LoadTags (); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -187,24 +183,6 @@ 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"); @@ -213,7 +191,7 @@ namespace client m_IsPublic = itr->second != "true"; } - int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0; + int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency; std::map intOpts = { {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, @@ -284,6 +262,17 @@ 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; } @@ -312,7 +301,7 @@ namespace client if (m_IsPublic) { auto s = shared_from_this (); - boost::asio::post (m_Service, [s](void) + m_Service.post ([s](void) { s->m_PublishVerificationTimer.cancel (); s->Publish (); @@ -340,7 +329,7 @@ namespace client memcpy (data.k, key, 32); memcpy (data.t, tag, 32); auto s = shared_from_this (); - boost::asio::post (m_Service, [s,data](void) + m_Service.post ([s,data](void) { s->AddSessionKey (data.k, data.t); }); @@ -357,7 +346,7 @@ namespace client memcpy (data.k, key, 32); data.t = tag; auto s = shared_from_this (); - boost::asio::post (m_Service, [s,data](void) + m_Service.post ([s,data](void) { s->AddECIESx25519Key (data.k, data.t); }); @@ -365,57 +354,35 @@ namespace client void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr 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); - }); + m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); } void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); - boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID)); + m_Service.post (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, nullptr); + LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); } - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, - size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) + bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { switch (typeID) { case eI2NPData: - HandleDataMessage (payload, len, from); + HandleDataMessage (payload, len); break; case eI2NPDeliveryStatus: + // we assume tunnel tests non-encrypted HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET)); break; - case eI2NPTunnelTest: - if (m_Pool) - m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)); - break; case eI2NPDatabaseStore: - HandleDatabaseStoreMessage (payload, len, from); + HandleDatabaseStoreMessage (payload, len); break; case eI2NPDatabaseSearchReply: HandleDatabaseSearchReplyMessage (payload, len); @@ -430,14 +397,8 @@ namespace client return true; } - void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, - i2p::garlic::ECIESX25519AEADRatchetSession * from) + void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) { - if (len < DATABASE_STORE_HEADER_SIZE) - { - LogPrint (eLogError, "Destination: Database store msg is too short ", len); - return; - } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) @@ -445,14 +406,8 @@ namespace client LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore"); offset += 36; } - if (offset > len || len > i2p::data::MAX_LS_BUFFER_SIZE + offset) - { - LogPrint (eLogError, "Destination: Database store message is too long ", len); - return; - } i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET); std::shared_ptr leaseSet; - std::shared_ptr request; switch (buf[DATABASE_STORE_TYPE_OFFSET]) { case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1 @@ -467,7 +422,7 @@ namespace client leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) { - leaseSet->Update (buf + offset, len - offset, shared_from_this(), true); + leaseSet->Update (buf + offset, len - offset); if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else @@ -486,29 +441,13 @@ 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, 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 ()) + leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2 + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) { if (leaseSet->GetIdentHash () != GetIdentHash ()) { LogPrint (eLogDebug, "Destination: New remote LeaseSet added"); - m_RemoteLeaseSets.insert_or_assign (key, leaseSet); - if (from) - from->SetDestination (key); + m_RemoteLeaseSets[key] = leaseSet; } else LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped"); @@ -524,60 +463,31 @@ namespace client case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5 { auto it2 = m_LeaseSetRequests.find (key); - if (it2 != m_LeaseSetRequests.end ()) + if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) { - request = it2->second; - m_LeaseSetRequests.erase (it2); - if (request->requestedBlindedKey) + auto ls2 = std::make_shared (buf + offset, len - offset, + it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ()); + if (ls2->IsValid ()) { - 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 - { - // 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"); + m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key + m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup + leaseSet = ls2; } } else - LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2"); + LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2"); break; } default: LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped"); } - if (!request) + auto it1 = m_LeaseSetRequests.find (key); + if (it1 != m_LeaseSetRequests.end ()) { - 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); + it1->second->requestTimeoutTimer.cancel (); + if (it1->second) it1->second->Complete (leaseSet); + m_LeaseSetRequests.erase (it1); } } @@ -590,43 +500,38 @@ namespace client if (it != m_LeaseSetRequests.end ()) { auto request = it->second; - for (int i = 0; i < num; i++) + bool found = false; + if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) { - i2p::data::IdentHash peerHash (buf + 33 + i*32); - if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) + for (int i = 0; i < num; i++) { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); - i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory + 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; } } - SendNextLeaseSetRequest (key, request); + if (!found) + { + LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); + request->Complete (nullptr); + m_LeaseSetRequests.erase (key); + } } 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) @@ -635,8 +540,7 @@ namespace client m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; // schedule verification - m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + - (m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0))); + 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)); } @@ -644,12 +548,9 @@ namespace client i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); } - void LeaseSetDestination::SetLeaseSetUpdated (bool post) + void LeaseSetDestination::SetLeaseSetUpdated () { - if (post) - boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); }); - else - UpdateLeaseSet (); + UpdateLeaseSet (); } void LeaseSetDestination::Publish () @@ -675,7 +576,12 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills); + 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); if (!floodfill) { LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); @@ -686,39 +592,26 @@ namespace client auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); if (!outbound || !inbound) { - if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ()) + 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) { - 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) + outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + if (outbound) { - 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"); + 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 more floodfills found"); + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); } else - LogPrint (eLogDebug, "Destination: No tunnels in pool"); - + LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); 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; } } @@ -726,19 +619,10 @@ 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)); - 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.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); - outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg); + outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); m_LastSubmissionTime = ts; } @@ -748,9 +632,22 @@ 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; - Publish (); + 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)); + + } } } } @@ -801,10 +698,10 @@ namespace client if (!m_Pool || !IsReady ()) { if (requestComplete) - boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);}); + m_Service.post ([requestComplete](void){requestComplete (nullptr);}); return false; } - boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr)); + m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr)); return true; } @@ -813,7 +710,7 @@ namespace client if (!dest || !m_Pool || !IsReady ()) { if (requestComplete) - boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);}); + m_Service.post ([requestComplete](void){requestComplete (nullptr);}); return false; } auto storeHash = dest->GetStoreHash (); @@ -821,17 +718,17 @@ namespace client if (leaseSet) { if (requestComplete) - boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);}); + m_Service.post ([requestComplete, leaseSet](void){requestComplete (leaseSet);}); return true; } - boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest)); + m_Service.post (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 (); - boost::asio::post (m_Service, [dest, notify, s](void) + m_Service.post ([dest, notify, s](void) { auto it = s->m_LeaseSetRequests.find (dest); if (it != s->m_LeaseSetRequests.end ()) @@ -851,7 +748,7 @@ namespace client void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr requestedBlindedKey) { - std::unordered_set excluded; + std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded); if (floodfill) { @@ -859,24 +756,16 @@ namespace client request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2 if (requestComplete) request->requestComplete.push_back (requestComplete); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); + auto ts = i2p::util::GetSecondsSinceEpoch (); auto ret = m_LeaseSetRequests.insert (std::pair >(dest,request)); if (ret.second) // inserted { request->requestTime = ts; if (!SendLeaseSetRequest (dest, floodfill, request)) { - // try another - LogPrint (eLogWarning, "Destination: Couldn't send LeaseSet request to ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another"); - request->excluded.insert (floodfill->GetIdentHash ()); - floodfill = i2p::data::netdb.GetClosestFloodfill (dest, request->excluded); - if (!SendLeaseSetRequest (dest, floodfill, request)) - { - // request failed - LogPrint (eLogWarning, "Destination: LeaseSet request for ", dest.ToBase32 (), " was not sent"); - m_LeaseSetRequests.erase (ret.first); - if (requestComplete) requestComplete (nullptr); - } + // request failed + m_LeaseSetRequests.erase (ret.first); + if (requestComplete) requestComplete (nullptr); } } else // duplicate @@ -903,11 +792,11 @@ namespace client std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) - request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); // outbound from floodfill - if (!request->replyTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible inbound tunnels found"); + request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); + if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) - request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); // inbound from floodfill - if (!request->outboundTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible outbound tunnels found"); + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); + if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) { @@ -923,18 +812,9 @@ namespace client AddECIESx25519Key (replyKey, replyTag); else AddSessionKey (replyKey, replyTag); - - 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 ( + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, + request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); + request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock { @@ -942,7 +822,7 @@ namespace client nextFloodfill->GetIdentHash (), 0, msg } }); - request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT)); + request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer, shared_from_this (), std::placeholders::_1, dest)); } @@ -959,7 +839,7 @@ namespace client if (it != m_LeaseSetRequests.end ()) { bool done = false; - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT) { auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded); @@ -996,8 +876,7 @@ namespace client CleanupExpiredTags (); CleanupRemoteLeaseSets (); CleanupDestination (); - m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT + - (m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0))); + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -1011,7 +890,7 @@ namespace client { if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired { - LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); } else @@ -1019,17 +898,20 @@ namespace client } } - ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, + 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, bool isPublic, const std::map * params): LeaseSetDestination (service, isPublic, params), - 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) + m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), + m_DatagramDestination (nullptr), m_RefCounter (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 @@ -1048,15 +930,7 @@ namespace client { try { - 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); - } + encryptionKeyTypes.insert (std::stoi(it1)); } catch (std::exception& ex) { @@ -1067,24 +941,29 @@ namespace client } } // if no param or valid crypto type use from identity + bool isSingleKey = false; if (encryptionKeyTypes.empty ()) - 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 + { + isSingleKey = true; + encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ()); + } for (auto& it: encryptionKeyTypes) { - auto encryptionKey = std::make_shared (it); + auto encryptionKey = new EncryptionKey (it); if (IsPublic ()) - PersistTemporaryKeys (encryptionKey); + PersistTemporaryKeys (encryptionKey, isSingleKey); else encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); - 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 (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 (IsPublic ()) @@ -1098,29 +977,10 @@ 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 = GetBoolParamValue (it->second); - it = params->find (I2CP_PARAM_STREAMING_DONT_SIGN); - if (it != params->end ()) - m_IsStreamingDontSign = GetBoolParamValue (it->second); - + m_IsStreamingAnswerPings = (it->second == "true"); + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet @@ -1133,12 +993,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 (eLogCritical, "Destination: No auth keys read for auth type: ", authType); + LogPrint (eLogError, "Destination: No auth keys read for auth type ", authType); m_AuthKeys = nullptr; } } @@ -1147,7 +1007,7 @@ namespace client } catch (std::exception & ex) { - LogPrint(eLogCritical, "Destination: Unable to parse parameters for destination: ", ex.what()); + LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } } @@ -1166,34 +1026,25 @@ namespace client void ClientDestination::Stop () { - LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p"); + LeaseSetDestination::Stop (); 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, - i2p::garlic::ECIESX25519AEADRatchetSession * from) + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); if(length > len - 4) @@ -1210,35 +1061,33 @@ namespace client case PROTOCOL_TYPE_STREAMING: { // streaming protocol - 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); + auto dest = GetStreamingDestination (toPort); + if (dest) + dest->HandleDataMessagePayload (buf, length); 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, buf[9], from); + m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); 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, uint16_t port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) { if (!streamRequestComplete) { @@ -1247,13 +1096,7 @@ namespace client } auto leaseSet = FindLeaseSet (dest); if (leaseSet) - { - auto stream = CreateStream (leaseSet, port); - boost::asio::post (GetService (), [streamRequestComplete, stream]() - { - streamRequestComplete(stream); - }); - } + streamRequestComplete(CreateStream (leaseSet, port)); else { auto s = GetSharedFromThis (); @@ -1268,7 +1111,7 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port) { if (!streamRequestComplete) { @@ -1286,42 +1129,7 @@ namespace client }); } - template - std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, uint16_t port) - { - volatile bool done = false; - std::shared_ptr stream; - std::condition_variable streamRequestComplete; - std::mutex streamRequestCompleteMutex; - CreateStream ( - [&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) - { - stream = s; - std::unique_lock l(streamRequestCompleteMutex); - streamRequestComplete.notify_all (); - done = true; - }, - dest, port); - while (!done) - { - std::unique_lock l(streamRequestCompleteMutex); - if (!done) - streamRequestComplete.wait (l); - } - return stream; - } - - std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, uint16_t port) - { - return CreateStreamSync (dest, port); - } - - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr dest, uint16_t port) - { - return CreateStreamSync (dest, port); - } - - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, uint16_t port) + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); @@ -1358,7 +1166,7 @@ namespace client }); } - std::shared_ptr ClientDestination::GetStreamingDestination (uint16_t port) const + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const { if (port) { @@ -1366,9 +1174,8 @@ namespace client if (it != m_StreamingDestinationsByPorts.end ()) return it->second; } - else // if port is zero, use default destination - return m_StreamingDestination; - return nullptr; + // if port is zero or not found, use default destination + return m_StreamingDestination; } void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) @@ -1396,7 +1203,7 @@ namespace client m_StreamingDestination->AcceptOnce (acceptor); } - std::shared_ptr ClientDestination::CreateStreamingDestination (uint16_t port, bool gzip) + std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) { auto dest = std::make_shared (GetSharedFromThis (), port, gzip); if (port) @@ -1406,7 +1213,7 @@ namespace client return dest; } - std::shared_ptr ClientDestination::RemoveStreamingDestination (uint16_t port) + std::shared_ptr ClientDestination::RemoveStreamingDestination (int port) { if (port) { @@ -1421,11 +1228,10 @@ namespace client return nullptr; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip, - i2p::datagram::DatagramVersion version) + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) { - if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip, version); + if (m_DatagramDestination == nullptr) + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); return m_DatagramDestination; } @@ -1443,56 +1249,32 @@ namespace client return ret; } - void ClientDestination::PersistTemporaryKeys (std::shared_ptr keys) + void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey) { if (!keys) return; std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat"); + std::string path = i2p::fs::DataDirPath("destinations", + isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat")); std::ifstream f(path, std::ifstream::binary); - 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); + + if (f) { + f.read ((char *)keys->pub, 256); + f.read ((char *)keys->priv, 256); + return; } - 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 ()); + LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p"); + memset (keys->priv, 0, 256); + memset (keys->pub, 0, 256); keys->GenerateKeys (); - + // TODO:: persist crypto key type std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); - if (f1) - { - f1.write ((char *)keys->pub.data (), keys->pub.size ()); - f1.write ((char *)keys->priv.data (), keys->priv.size ()); + if (f1) { + f1.write ((char *)keys->pub, 256); + f1.write ((char *)keys->priv, 256); + return; } - if (!f1) - LogPrint(eLogError, "Destination: Can't save keys to ", path); + LogPrint(eLogError, "Destinations: Can't save keys to ", path); } void ClientDestination::CreateNewLeaseSet (const std::vector >& tunnels) @@ -1500,10 +1282,9 @@ namespace client std::shared_ptr leaseSet; if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) { - auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); - if (it != m_EncryptionKeys.end ()) + if (m_StandardEncryptionKey) { - leaseSet = std::make_shared (GetIdentity (), it->second->pub.data (), tunnels); + leaseSet = std::make_shared (GetIdentity (), m_StandardEncryptionKey->pub, tunnels); // sign Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); } @@ -1513,40 +1294,18 @@ namespace client else { // standard LS2 (type 3) first - 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 - } + 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} ); + 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 (), publishedTimestamp, isPublishedEncrypted); + m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted); if (isPublishedEncrypted) // encrypt if type 5 ls2 = std::make_shared (ls2, m_Keys, GetAuthType (), m_AuthKeys); leaseSet = ls2; - m_LastPublishedTimestamp = publishedTimestamp; } SetLeaseSet (leaseSet); } @@ -1558,22 +1317,11 @@ namespace client bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - 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); + 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); else LogPrint (eLogError, "Destinations: Decryptor is not set"); return false; @@ -1581,26 +1329,14 @@ namespace client bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { -#if __cplusplus >= 202002L // C++20 - return m_EncryptionKeys.contains (keyType); -#else - return m_EncryptionKeys.count (keyType) > 0; -#endif + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; } - 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 { - auto it = m_EncryptionKeys.find (keyType); - if (it != m_EncryptionKeys.end ()) - return it->second->pub.data (); - return nullptr; + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; + return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; } void ClientDestination::ReadAuthKey (const std::string& group, const std::map * params) @@ -1615,7 +1351,7 @@ namespace client if (pubKey.FromBase64 (it.second.substr (pos+1))) m_AuthKeys->push_back (pubKey); else - LogPrint (eLogCritical, "Destination: Unexpected auth key: ", it.second.substr (pos+1)); + LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1)); } } } @@ -1634,8 +1370,6 @@ 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 f7ba269f..4b08ec51 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,15 +14,13 @@ #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" @@ -37,17 +35,13 @@ namespace client const uint8_t PROTOCOL_TYPE_STREAMING = 6; const uint8_t PROTOCOL_TYPE_DATAGRAM = 17; const uint8_t PROTOCOL_TYPE_RAW = 18; - const 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_CONFIRMATION_TIMEOUT = 5; // in seconds + const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish const int PUBLISH_MIN_INTERVAL = 20; // in seconds const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically - 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 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 unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; // I2CP @@ -89,22 +83,9 @@ 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, @@ -114,8 +95,8 @@ namespace client // leaseSet = nullptr means not found struct LeaseSetRequest { - LeaseSetRequest (boost::asio::io_context& service): requestTime (0), requestTimeoutTimer (service) {}; - std::unordered_set excluded; + LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {}; + std::set excluded; uint64_t requestTime; boost::asio::deadline_timer requestTimeoutTimer; std::list requestComplete; @@ -132,10 +113,10 @@ namespace client public: - LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map * params = nullptr); + LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map * params = nullptr); ~LeaseSetDestination (); const std::string& GetNickname () const { return m_Nickname; }; - auto& GetService () { return m_Service; }; + boost::asio::io_service& GetService () { return m_Service; }; virtual void Start (); virtual void Stop (); @@ -152,15 +133,15 @@ namespace client void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr dest, bool notify = true); // implements GarlicDestination - std::shared_ptr GetLeaseSet () override; - std::shared_ptr GetTunnelPool () const override { return m_Pool; } + std::shared_ptr GetLeaseSet (); + std::shared_ptr GetTunnelPool () const { return m_Pool; } // override GarlicDestination - 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 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 IsPublic () const { return m_IsPublic; }; void SetPublic (bool pub) { m_IsPublic = pub; }; @@ -168,21 +149,18 @@ namespace client protected: // implements GarlicDestination - 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 HandleI2NPMessage (const uint8_t * buf, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); 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, i2p::garlic::ECIESX25519AEADRatchetSession * from) = 0; + virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; - + private: void UpdateLeaseSet (); @@ -191,34 +169,31 @@ 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, i2p::garlic::ECIESX25519AEADRatchetSession * from); + void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len); 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_context& m_Service; + boost::asio::io_service& m_Service; mutable std::mutex m_RemoteLeaseSetsMutex; - std::unordered_map > m_RemoteLeaseSets; - std::unordered_map > m_LeaseSetRequests; + std::map > m_RemoteLeaseSets; + std::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::unordered_set m_ExcludedFloodfills; // for publishing + std::set m_ExcludedFloodfills; // for publishing boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_PublishDelayTimer, m_CleanupTimer; @@ -237,14 +212,25 @@ 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_context& service, const i2p::data::PrivateKeys& keys, + ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); - void Start () override; - void Stop () override; + void Start (); + void Stop (); 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); }; @@ -255,15 +241,13 @@ namespace client int GetRefCounter () const { return m_RefCounter; }; // streaming - 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); + std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional + std::shared_ptr GetStreamingDestination (int port = 0) const; + std::shared_ptr RemoveStreamingDestination (int port); // following methods operate with default streaming destination - 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 CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); + std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); void SendPing (const i2p::data::IdentHash& to); void SendPing (std::shared_ptr to); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); @@ -271,60 +255,45 @@ 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::DatagramVersion version = i2p::datagram::eDatagramV1); + i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination - 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; + 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; protected: - // GarlicDestionation - i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override; - // LeaseSetDestination - void CleanupDestination () override; - i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; } + void CleanupDestination (); // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from) override; - void CreateNewLeaseSet (const std::vector >& tunnels) override; - + void HandleDataMessage (const uint8_t * buf, size_t len); + void CreateNewLeaseSet (const std::vector >& tunnels); + private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } - void PersistTemporaryKeys (std::shared_ptr keys); + void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); void ReadAuthKey (const std::string& group, const std::map * params); - template - std::shared_ptr CreateStreamSync (const Dest& dest, uint16_t port); - private: i2p::data::PrivateKeys m_Keys; - std::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::unique_ptr m_StandardEncryptionKey; + std::unique_ptr m_ECIESx25519EncryptionKey; + + int m_StreamingAckDelay; + bool m_IsStreamingAnswerPings; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; - std::shared_ptr m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels i2p::datagram::DatagramDestination * m_DatagramDestination; int m_RefCounter; // how many clients(tunnels) use this destination - uint64_t m_LastPublishedTimestamp; boost::asio::deadline_timer m_ReadyChecker; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index fea10cd9..e240e925 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include "Log.h" #include "util.h" #include "Crypto.h" -#include "PostQuantum.h" #include "Elligator.h" #include "Tag.h" #include "I2PEndian.h" @@ -95,17 +94,6 @@ 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) @@ -129,12 +117,6 @@ 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) { @@ -174,12 +156,12 @@ namespace garlic return false; } if (m_Destination) - m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr); + m_Destination->HandleECIESx25519GarlicClove (buf + offset, size); return true; } ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): - GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0) + GarlicRoutingSession (owner, true) { if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; @@ -241,104 +223,33 @@ 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]; - bool decrypted = false; - auto cryptoType = GetOwner ()->GetRatchetsHighestCryptoType (); -#if OPENSSL_PQ - if (cryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // we support post quantum + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { - 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; - } - } + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } + MixKey (sharedSecret); // decrypt flags/static - uint8_t fs[32]; - if (!Decrypt (buf, fs, 32)) + 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 { LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); return false; @@ -350,19 +261,21 @@ namespace garlic bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); if (isStatic) { - // static key, fs is apk - SetRemoteStaticKey (cryptoType, fs); - if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk) + // 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) { 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 (!Decrypt (buf, payload.data (), len - 16)) + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); return false; @@ -398,7 +311,7 @@ namespace garlic { case eECIESx25519BlkGalicClove: if (GetOwner ()) - GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this); + GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); break; case eECIESx25519BlkNextKey: LogPrint (eLogDebug, "Garlic: Next key"); @@ -414,17 +327,15 @@ namespace garlic auto offset1 = offset; for (auto i = 0; i < numAcks; i++) { - 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 + offset1 += 2; // tagsetid + MessageConfirmed (bufbe16toh (buf + offset1)); offset1 += 2; // N } break; } case eECIESx25519BlkAckRequest: { LogPrint (eLogDebug, "Garlic: Ack request"); - if (receiveTagset) - m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); + m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); break; } case eECIESx25519BlkTermination: @@ -479,6 +390,7 @@ 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 @@ -490,14 +402,15 @@ 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); @@ -511,9 +424,7 @@ 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; } } @@ -550,16 +461,7 @@ namespace garlic offset += 32; // KDF1 -#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 + 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) @@ -568,32 +470,18 @@ 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 (m_RemoteStaticKeyType); + fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); else { memset (out + offset, 0, 32); // all zeros flags section fs = out + offset; } - if (!Encrypt (fs, out + offset, 32)) + if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); return false; @@ -604,11 +492,13 @@ namespace garlic // KDF2 if (isStatic) { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk) - MixKey (sharedSecret); + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) + MixKey (sharedSecret); } + else + CreateNonce (1, nonce); // encrypt payload - if (!Encrypt (payload, out + offset, len)) + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -646,7 +536,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) @@ -657,33 +547,16 @@ 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 (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + 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) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; @@ -704,7 +577,6 @@ 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"); @@ -726,34 +598,16 @@ 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) - 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) + 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) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; } - MixHash (out + offset, 16); // h = SHA256(h || ciphertext) + MixHash (out + 40, 16); // h = SHA256(h || ciphertext) // encrypt payload - 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 + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); return false; @@ -785,30 +639,13 @@ namespace garlic return false; } MixKey (sharedSecret); -#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) + GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) MixKey (sharedSecret); - + + uint8_t nonce[12]; + CreateNonce (0, nonce); // calculate hash for zero length - if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only + 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 { LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); return false; @@ -833,7 +670,6 @@ 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"); @@ -843,8 +679,7 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; - // don't delete m_EpehemralKey and m_PQKeys because delayed NSR's might come - // done in CleanupReceiveNSRKeys called from NSR tagset destructor + //m_EphemeralKeys = nullptr; // TODO: delete after a while m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } @@ -859,8 +694,6 @@ 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 @@ -868,7 +701,8 @@ namespace garlic if (!tag) { LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); - owner->RemoveECIESx25519Session (m_RemoteStaticKey); + if (GetOwner ()) + GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); return false; } memcpy (out, &tag, 8); @@ -876,7 +710,7 @@ namespace garlic // ciphertext = ENCRYPT(k, n, payload, ad) uint8_t key[32]; m_SendTagset->GetSymmKey (index, key); - if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8)) + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -895,35 +729,33 @@ namespace garlic uint8_t * payload = buf + 8; uint8_t key[32]; receiveTagset->GetSymmKey (index, key); - auto owner = GetOwner (); - if (!owner) return true; // drop message - - if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16)) + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } HandlePayload (payload, len - 16, receiveTagset, index); - - int moreTags = 0; - if (owner->GetNumRatchetInboundTags () > 0) // override in settings? + if (GetOwner ()) { - if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2) - moreTags = owner->GetNumRatchetInboundTags (); - index -= owner->GetNumRatchetInboundTags (); // trim behind + 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); } - 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; } @@ -937,14 +769,10 @@ namespace garlic m_State = eSessionStateEstablished; m_NSRSendTagset = nullptr; m_EphemeralKeys = nullptr; -#if OPENSSL_PQ - m_PQKeys = nullptr; - m_NSREncodedPQKey = nullptr; -#endif +#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; +#endif case eSessionStateEstablished: - if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ()) - m_SendReverseKey = false; // tag received on new tagset if (receiveTagset->IsNS ()) { // our of sequence NSR @@ -971,12 +799,7 @@ 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 @@ -991,28 +814,16 @@ 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)) @@ -1039,14 +850,13 @@ 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 () + LEASESET_CONFIRMATION_TIMEOUT) + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) { // resubmit non-confirmed LeaseSet SetLeaseSetUpdateStatus (eLeaseSetUpdated); @@ -1058,28 +868,13 @@ namespace garlic payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; if (!first) { - // ack request for LeaseSet - m_AckRequestMsgID = m_SendTagset->GetMsgID (); - sendAckRequest = true; - // update LeaseSet status + // ack request SetLeaseSetUpdateStatus (eLeaseSetSubmitted); - SetLeaseSetUpdateMsgID (m_AckRequestMsgID); + SetLeaseSetUpdateMsgID (m_SendTagset->GetNextIndex ()); 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) @@ -1131,15 +926,16 @@ namespace garlic } // LeaseSet if (leaseSet) - offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); - // ack request - if (sendAckRequest) { - payload[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (payload + offset, 1); offset += 2; - payload[offset] = 0; offset++; // flags - m_LastAckRequestSendTime = ts; - } + 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 + } + } // msg if (msg) offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); @@ -1174,6 +970,7 @@ namespace garlic memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } + m_SendReverseKey = false; } if (m_SendForwardKey) { @@ -1269,8 +1066,6 @@ 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 } @@ -1352,9 +1147,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 ((msg ? msg->GetPayloadLength () : 0) + 128); + auto m = NewI2NPMessage (); 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; @@ -1372,21 +1167,15 @@ 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 ((msg ? msg->GetPayloadLength () : 0) + 128); + auto m = NewI2NPMessage (); 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; @@ -1415,12 +1204,6 @@ 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 5fa947b7..301f597a 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,12 +14,10 @@ #include #include #include -#include #include #include #include "Identity.h" #include "Crypto.h" -#include "PostQuantum.h" #include "Garlic.h" #include "Tag.h" @@ -32,14 +30,10 @@ 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_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_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 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 = 800; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ @@ -63,8 +57,6 @@ 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; @@ -81,8 +73,8 @@ namespace garlic { public: - ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false); - ~ReceiveRatchetTagSet () override; + ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false): + m_Session (session), m_IsNS (isNS) {}; bool IsNS () const { return m_IsNS; }; std::shared_ptr GetSession () { return m_Session; }; @@ -94,8 +86,7 @@ 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; @@ -110,10 +101,9 @@ namespace garlic SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key); - 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; } - + bool IsIndexExpired (int index) const { return false; }; + bool HandleNextMessage (uint8_t * buf, size_t len, int index); + private: GarlicDestination * m_Destination; @@ -157,7 +147,6 @@ 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: @@ -166,42 +155,34 @@ 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) override; + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } - 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 SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } + void Terminate () { m_IsTerminated = true; } - void SetDestination (const i2p::data::IdentHash& dest) + void SetDestination (const i2p::data::IdentHash& dest) // TODO: { 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); } - 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 - + + 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; }; + 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 @@ -226,30 +207,20 @@ 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;// must be set for NS if outgoing and NSR if incoming - std::list > m_AckRequests; // incoming (tagsetid, index) + std::unique_ptr m_Destination;// TODO: might not need it + std::list > m_AckRequests; // (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 @@ -274,8 +245,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 55d7711d..0c6eb4f7 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,12 +1,12 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* 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 +#include #include "Log.h" #include "Crypto.h" #include "Ed25519.h" @@ -134,27 +134,22 @@ namespace crypto { BN_CTX * bnCtx = BN_CTX_new (); // calculate r - 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 + 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 uint8_t digest[64]; - unsigned int dl = 64; - EVP_DigestFinal_ex (ctx, digest, &dl); - EVP_MD_CTX_destroy (ctx); + SHA512_Final (digest, &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 - 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); + 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); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key @@ -174,15 +169,13 @@ namespace crypto uint8_t T[80]; RAND_bytes (T, 80); // calculate r = H*(T || publickey || 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 + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, T, 80); + SHA512_Update (&ctx, publicKeyEncoded, 32); + SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; - unsigned int dl = 64; - EVP_DigestFinal_ex (ctx, digest, &dl); - EVP_MD_CTX_destroy (ctx); + SHA512_Final (digest, &ctx); BIGNUM * r = DecodeBN<64> (digest); BN_mod (r, r, l, bnCtx); // % l EncodeBN (r, digest, 32); @@ -190,14 +183,11 @@ 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 - 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); + 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); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l BIGNUM * a = DecodeBN (privateKey); @@ -423,7 +413,7 @@ namespace crypto BIGNUM * y = BN_new (); BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); BIGNUM * x = RecoverX (y, ctx); - if ((bool)BN_is_bit_set (x, 0) != isHighestBitSet) + if (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 @@ -467,6 +457,86 @@ 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 9c0ad801..470d802f 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -84,7 +84,10 @@ 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 @@ -112,6 +115,11 @@ 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 3f5fc6b9..f6653e55 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,17 +7,7 @@ */ #include - -#if defined(MAC_OSX) -#if !STD_FILESYSTEM -#include -#endif -#include -#endif - -#if defined(__HAIKU__) -#include -#endif +#include #ifdef _WIN32 #include @@ -30,14 +20,6 @@ #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"; @@ -67,59 +49,21 @@ namespace fs { const std::string GetUTF8DataDir () { #ifdef _WIN32 - 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; + 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; #else return dataDir; // linux, osx, android uses UTF-8 by default #endif } void DetectDataDir(const std::string & cmdline_param, bool isService) { - // with 'datadir' option if (cmdline_param != "") { dataDir = cmdline_param; return; } - -#if !defined(MAC_OSX) && !defined(ANDROID) - // with 'service' option - if (isService) { -#ifdef _WIN32 - wchar_t commonAppData[MAX_PATH]; - if(SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 0, commonAppData) != S_OK) - { -#ifdef WIN32_APP - MessageBox(NULL, TEXT("Unable to get common AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); -#else - fprintf(stderr, "Error: Unable to get common AppData path!"); -#endif - exit(1); - } - else - { -#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; -#endif - return; - } -#endif - - // detect directory as usual #ifdef _WIN32 wchar_t localAppData[MAX_PATH]; @@ -135,14 +79,10 @@ namespace fs { } else { -#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) - auto execPath = fs_lib::path(localAppData).parent_path(); -#else - auto execPath = fs_lib::wpath(localAppData).parent_path(); -#endif + auto execPath = boost::filesystem::wpath(localAppData).parent_path(); // if config file exists in .exe's folder use it - if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string + if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string { dataDir = execPath.string (); } else // otherwise %appdata% @@ -158,11 +98,7 @@ namespace fs { } else { -#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) - dataDir = fs_lib::path(localAppData).string() + "\\" + appName; -#else - dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName; -#endif + dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; } } } @@ -172,26 +108,21 @@ 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 (fs_lib::exists(ext)) + if (boost::filesystem::exists(ext)) { dataDir = std::string (ext) + "/" + appName; return; } -#endif // ANDROID - // use /home/user/.i2pd or /tmp/i2pd +#endif + // otherwise use /data/files char *home = getenv("HOME"); - if (home != NULL && strlen(home) > 0) { + if (isService) { + dataDir = "/var/lib/" + appName; + } else if (home != NULL && strlen(home) > 0) { dataDir = std::string(home) + "/." + appName; } else { dataDir = "/tmp/" + appName; @@ -216,16 +147,16 @@ namespace fs { } bool Init() { - if (!fs_lib::exists(dataDir)) - fs_lib::create_directory(dataDir); + if (!boost::filesystem::exists(dataDir)) + boost::filesystem::create_directory(dataDir); std::string destinations = DataDirPath("destinations"); - if (!fs_lib::exists(destinations)) - fs_lib::create_directory(destinations); + if (!boost::filesystem::exists(destinations)) + boost::filesystem::create_directory(destinations); std::string tags = DataDirPath("tags"); - if (!fs_lib::exists(tags)) - fs_lib::create_directory(tags); + if (!boost::filesystem::exists(tags)) + boost::filesystem::create_directory(tags); else i2p::garlic::CleanUpTagsFiles (); @@ -233,13 +164,13 @@ namespace fs { } bool ReadDir(const std::string & path, std::vector & files) { - if (!fs_lib::exists(path)) + if (!boost::filesystem::exists(path)) return false; - fs_lib::directory_iterator it(path); - fs_lib::directory_iterator end; + boost::filesystem::directory_iterator it(path); + boost::filesystem::directory_iterator end; for ( ; it != end; it++) { - if (!fs_lib::is_regular_file(it->status())) + if (!boost::filesystem::is_regular_file(it->status())) continue; files.push_back(it->path().string()); } @@ -248,42 +179,29 @@ namespace fs { } bool Exists(const std::string & path) { - return fs_lib::exists(path); + return boost::filesystem::exists(path); } uint32_t GetLastUpdateTime (const std::string & path) { - if (!fs_lib::exists(path)) + if (!boost::filesystem::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 (!fs_lib::exists(path)) + if (!boost::filesystem::exists(path)) return false; - return fs_lib::remove(path); + return boost::filesystem::remove(path); } bool CreateDirectory (const std::string& path) { - if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path))) + if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path))) return true; - return fs_lib::create_directory(path); + return boost::filesystem::create_directory(path); } void HashedStorage::SetPlace(const std::string &path) { @@ -291,30 +209,16 @@ namespace fs { } bool HashedStorage::Init(const char * chars, size_t count) { - if (!fs_lib::exists(root)) { - fs_lib::create_directories(root); + if (!boost::filesystem::exists(root)) { + boost::filesystem::create_directories(root); } for (size_t i = 0; i < count; i++) { auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; - if (fs_lib::exists(p)) + if (boost::filesystem::exists(p)) continue; -#if TARGET_OS_SIMULATOR - // ios simulator fs says it is case sensitive, but it is not - boost::system::error_code ec; - if (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)) + if (boost::filesystem::create_directory(p)) continue; /* ^ throws exception on failure */ -#endif return false; } return true; @@ -335,9 +239,9 @@ namespace fs { void HashedStorage::Remove(const std::string & ident) { std::string path = Path(ident); - if (!fs_lib::exists(path)) + if (!boost::filesystem::exists(path)) return; - fs_lib::remove(path); + boost::filesystem::remove(path); } void HashedStorage::Traverse(std::vector & files) { @@ -348,12 +252,12 @@ namespace fs { void HashedStorage::Iterate(FilenameVisitor v) { - fs_lib::path p(root); - fs_lib::recursive_directory_iterator it(p); - fs_lib::recursive_directory_iterator end; + boost::filesystem::path p(root); + boost::filesystem::recursive_directory_iterator it(p); + boost::filesystem::recursive_directory_iterator end; for ( ; it != end; it++) { - if (!fs_lib::is_regular_file( it->status() )) + if (!boost::filesystem::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 7af8f494..7911c6a0 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,16 +15,6 @@ #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 3a4e8890..9a0700d0 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,6 +7,7 @@ */ #include +#include #include #include "Crypto.h" #include "FS.h" @@ -24,8 +25,6 @@ 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) @@ -48,29 +47,48 @@ namespace data cn += 3; char * family = strstr (cn, ".family"); if (family) family[0] = 0; - 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) - { - EVP_PKEY_free (pkey); - LogPrint (eLogError, "Family: Duplicated family name ", cn); - } - } - else - LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); - } } + 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) + { + int curve = EC_GROUP_get_curve_name (group); + if (curve == NID_X9_62_prime256v1) + { + uint8_t signingKey[64]; + BIGNUM * x = BN_new(), * y = BN_new(); + EC_POINT_get_affine_coordinates_GFp (group, + EC_KEY_get0_public_key (ecKey), x, y, NULL); + i2p::crypto::bn2buf (x, signingKey, 32); + i2p::crypto::bn2buf (y, signingKey + 32, 32); + BN_free (x); BN_free (y); + verifier = std::make_shared(); + verifier->SetPublicKey (signingKey); + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } + EC_KEY_free (ecKey); + } + break; + } + default: + LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); + } + EVP_PKEY_free (pkey); + if (verifier && cn) + m_SigningKeys.emplace (cn, std::make_pair(verifier, m_SigningKeys.size () + 1)); } SSL_free (ssl); } @@ -103,37 +121,23 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - std::string_view signature, const char * key) const + const char * signature, const char * key) const { uint8_t buf[100], signatureBuf[64]; - size_t len = family.length (); + size_t len = family.length (), signatureLen = strlen (signature); 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 () && 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; - } - } + if (it != m_SigningKeys.end ()) + return it->second.first->Verify (buf, len, signatureBuf); // TODO: process key return true; } @@ -156,40 +160,34 @@ namespace data { SSL * ssl = SSL_new (ctx); EVP_PKEY * pkey = SSL_get_privatekey (ssl); - 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) + EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); + if (ecKey) { - 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"); - + 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"); + } + } SSL_free (ssl); } else diff --git a/libi2pd/Family.h b/libi2pd/Family.h index fcf61082..b19ea142 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,9 +11,8 @@ #include #include -#include #include -#include +#include "Signature.h" #include "Identity.h" namespace i2p @@ -29,7 +28,7 @@ namespace data ~Families (); void LoadCertificates (); bool VerifyFamily (const std::string& family, const IdentHash& ident, - std::string_view signature, const char * key = nullptr) const; + const char * signature, const char * key = nullptr) const; FamilyID GetFamilyID (const std::string& family) const; private: @@ -38,7 +37,7 @@ namespace data private: - std::map > m_SigningKeys; // family -> (verification pkey, id) + std::map, FamilyID> > m_SigningKeys; // family -> (verifier, id) }; std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index a61359f1..9daea1f0 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, 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_IsWithJava (false), m_NumSentPackets (0) + m_LeaseSetUpdateMsgID (0) { } @@ -45,17 +45,22 @@ namespace garlic { if (!m_SharedRoutingPath) return nullptr; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - if (!m_SharedRoutingPath->outboundTunnel->IsEstablished () || + if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED || + !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; @@ -75,7 +80,7 @@ namespace garlic void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts) { - if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASESET_CONFIRMATION_TIMEOUT) + if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) { if (GetOwner ()) GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); @@ -160,7 +165,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_IV = iv; + m_Encryption.SetIV (iv); buf += 514; len += 514; } @@ -170,7 +175,7 @@ namespace garlic memcpy (buf, tag, 32); uint8_t iv[32]; // IV is first 16 bytes SHA256(tag, 32, iv); - m_IV = iv; + m_Encryption.SetIV (iv); buf += 32; len += 32; } @@ -210,7 +215,7 @@ namespace garlic size_t rem = blockSize % 16; if (rem) blockSize += (16-rem); //padding - m_Encryption.Encrypt(buf, blockSize, m_IV, buf); + m_Encryption.Encrypt(buf, blockSize, buf); return blockSize; } @@ -227,7 +232,7 @@ namespace garlic if (GetOwner ()) { // resubmit non-confirmed LeaseSet - if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT) + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) { SetLeaseSetUpdateStatus (eLeaseSetUpdated); SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed @@ -426,8 +431,7 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0), - m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard { } @@ -498,8 +502,7 @@ namespace garlic buf += 4; // length bool found = false; - bool supportsRatchets = SupportsRatchets (); - if (supportsRatchets) + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // try ECIESx25519 tag found = HandleECIESx25519TagMessage (buf, length); if (!found) @@ -515,7 +518,8 @@ namespace garlic { uint8_t iv[32]; // IV is first 16 bytes SHA256(buf, 32, iv); - decryption->Decrypt (buf + 32, length - 32, iv, buf + 32); + decryption->SetIV (iv); + decryption->Decrypt (buf + 32, length - 32, buf + 32); HandleAESBlock (buf + 32, length - 32, decryption, msg->from); found = true; } @@ -533,23 +537,43 @@ namespace garlic auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - decryption->Decrypt(buf + 514, length - 514, iv, buf + 514); + decryption->SetIV (iv); + decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } - else if (supportsRatchets) + else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { // otherwise ECIESx25519 - 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 + auto session = std::make_shared (this, false); // incoming + if (!session->HandleNextMessage (buf, length, nullptr, 0)) + { + // try to generate more tags for last tagset + if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) + { + uint64_t missingTag; memcpy (&missingTag, buf, 8); + auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); + LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags"); + for (int i = 0; i < maxTags; i++) + { + auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); + if (!nextTag) + { + LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); + break; + } + if (nextTag == missingTag) + { + LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) + found = true; + break; + } + } + if (!found) m_LastTagset = nullptr; + } + if (!found) LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); } - else - LogPrint (eLogWarning, "Garlic: Incoming sessions come too often"); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -564,7 +588,9 @@ namespace garlic auto it = m_ECIESx25519Tags.find (tag); if (it != m_ECIESx25519Tags.end ()) { - if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index)) + if (it->second.tagset->HandleNextMessage (buf, len, it->second.index)) + m_LastTagset = it->second.tagset; + else LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it); return true; @@ -683,7 +709,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->SendTunnelDataMsgTo (gwHash, gwTunnel, msg); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -745,38 +771,32 @@ namespace garlic } std::shared_ptr GarlicDestination::GetRoutingSession ( - std::shared_ptr destination, bool attachLeaseSet, - bool requestNewIfNotFound) + std::shared_ptr destination, bool attachLeaseSet) { - if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && + SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { - 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 ()) + 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 ())) { - session = it->second; - if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) - { - LogPrint (eLogDebug, "Garlic: Session restarted"); - requestNewIfNotFound = true; // it's not a new session - session = nullptr; - } + LogPrint (eLogDebug, "Garlic: Session restarted"); + 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; } - else - LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ()); + if (!session) + { + session = std::make_shared (this, true); + session->SetRemoteStaticKey (staticKey); + } + if (destination->IsDestination ()) + session->SetDestination (destination->GetIdentHash ()); // TODO: remove + return session; } else { @@ -867,7 +887,8 @@ namespace garlic } else { - if (it->second.tagset->IsSessionTerminated ()) + auto session = it->second.tagset->GetSession (); + if (!session || session->IsTerminated()) { it = m_ECIESx25519Tags.erase (it); numExpiredTags++; @@ -878,6 +899,8 @@ 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) @@ -911,7 +934,7 @@ namespace garlic } } - void GarlicDestination::SetLeaseSetUpdated (bool post) + void GarlicDestination::SetLeaseSetUpdated () { { std::unique_lock l(m_SessionsMutex); @@ -1004,8 +1027,7 @@ namespace garlic i2p::fs::Remove (it); } - void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, - ECIESX25519AEADRatchetSession * from) + void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) { const uint8_t * buf1 = buf; uint8_t flag = buf[0]; buf++; // flag @@ -1015,7 +1037,9 @@ 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: { @@ -1025,7 +1049,7 @@ namespace garlic buf += 4; // expiration ptrdiff_t offset = buf - buf1; if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from); + HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); else LogPrint (eLogError, "Garlic: Clove is too long"); break; @@ -1051,7 +1075,7 @@ namespace garlic { auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) - tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -1109,17 +1133,5 @@ 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 1fdffdae..b926abda 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, 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 LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds - const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds - const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds + 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 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,14 +111,13 @@ 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 and ECIESX25519AEADRatchetSession + virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession 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; @@ -131,12 +130,6 @@ 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; } @@ -159,8 +152,6 @@ 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: @@ -215,7 +206,6 @@ namespace garlic std::map > m_UnconfirmedTagsMsgs; // msgID->tags i2p::crypto::CBCEncryption m_Encryption; - i2p::data::Tag<16> m_IV; public: @@ -231,7 +221,7 @@ namespace garlic struct ECIESX25519AEADRatchetIndexTagset { int index; - ReceiveRatchetTagSetPtr tagset; // null if used + ReceiveRatchetTagSetPtr tagset; }; class GarlicDestination: public i2p::data::LocalDestination @@ -246,17 +236,11 @@ 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, bool requestNewIfNotFound = true); + std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); 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 @@ -266,36 +250,30 @@ 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, ECIESX25519AEADRatchetSession * from); + void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); uint8_t * GetPayloadBuffer (); virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); - virtual void SetLeaseSetUpdated (bool post = false); + virtual void SetLeaseSetUpdated (); 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, ECIESX25519AEADRatchetSession * from) = 0; + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 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); @@ -308,18 +286,15 @@ 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 6bce3428..2dafc9ae 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 (m_Group, P, x, y, ctx); + EC_POINT_set_affine_coordinates_GFp (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 (m_Group, p, x, y, nullptr); + return EC_POINT_get_affine_coordinates_GFp (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 (m_Group, p, x, y, nullptr); + EC_POINT_set_affine_coordinates_GFp (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 (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) + if (EC_POINT_set_compressed_coordinates_GFp (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 4be8684c..07c6a96e 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -57,8 +57,7 @@ namespace data if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) return outLen - m_Inflator.avail_out; // else - if (err) - LogPrint (eLogError, "Gzip: Inflate error ", err); + LogPrint (eLogError, "Gzip: Inflate error ", err); return 0; } } @@ -129,8 +128,7 @@ namespace data return outLen - m_Deflator.avail_out; } // else - if (err) - LogPrint (eLogError, "Gzip: Deflate error ", err); + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } @@ -139,7 +137,7 @@ namespace data if (m_IsDirty) deflateReset (&m_Deflator); m_IsDirty = true; size_t offset = 0; - int err = 0; + int err; for (const auto& it: bufs) { m_Deflator.next_in = const_cast(it.first); @@ -160,8 +158,7 @@ namespace data offset = outLen - m_Deflator.avail_out; } // else - if (err) - LogPrint (eLogError, "Gzip: Deflate error ", err); + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 8c7c8491..e994b9b3 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,6 @@ #include #include #include -#include #include "util.h" #include "Base.h" #include "HTTP.h" @@ -19,68 +18,58 @@ namespace i2p { namespace http { - // list of valid HTTP methods - static constexpr std::array HTTP_METHODS = - { + const std::vector 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 }; - - // list of valid HTTP versions - static constexpr std::array HTTP_VERSIONS = - { + const std::vector HTTP_VERSIONS = { "HTTP/1.0", "HTTP/1.1" }; - - static constexpr std::array weekdays = - { + const std::vector weekdays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - - static constexpr std::array months = - { + const std::vector months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - static inline bool is_http_version(std::string_view str) - { + inline bool is_http_version(const std::string & str) { return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); } - static inline bool is_http_method(std::string_view str) - { + inline bool is_http_method(const std::string & str) { return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); } - - 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) - { + + 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) { count++; - if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter - tokens.push_back (line.substr (0, pos)); - line = line.substr (pos + 1); + if (limit > 0 && count >= limit) + delim = '\n'; /* reset delimiter */ + if (!std::getline(ss, token, delim)) + break; + tokens.push_back(token); } - if (!line.empty ()) tokens.push_back (line); } - - static std::pair parse_header_line(std::string_view line) + + static std::pair parse_header_line(const std::string& 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::pair{"", ""}; // no ':' found + return std::make_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::pair{"", ""}; // no following space, but something else + return std::make_pair("", ""); // no following space, but something else } - return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))}; + return std::make_pair(line.substr(0, pos), line.substr(pos + len)); } void gen_rfc7231_date(std::string & out) { @@ -94,31 +83,25 @@ namespace http out = buf; } - bool URL::parse(const char *str, std::size_t len) - { - return parse({str, len ? len : strlen(str)}); + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); } - bool URL::parse(std::string_view url) - { - if (url.empty ()) return false; + bool URL::parse(const std::string& url) { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) - { + if(url.at(0) != '/' || pos_p > 0) { std::size_t pos_s = 0; - /* schema */ 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) { @@ -130,36 +113,28 @@ namespace http } pos_p = pos_c + 1; } - /* hostname[:port][/path] */ - if (url.at(pos_p) == '[') // ipv6 + if (url[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 = ipv6 ? - url.substr(pos_p + 1, url.length() - 1) : - url.substr(pos_p, std::string::npos); + host = url.substr(pos_p, std::string::npos); return true; } else if (url.at(pos_c) == ':') { - host = ipv6 ? - url.substr(pos_p + 1, pos_c - pos_p - 2) : - url.substr(pos_p, pos_c - pos_p); + host = url.substr(pos_p, pos_c - pos_p); /* port[/path] */ pos_p = pos_c + 1; pos_c = url.find('/', pos_p); - std::string_view port_str = (pos_c == std::string::npos) + std::string port_str = (pos_c == std::string::npos) ? url.substr(pos_p, std::string::npos) : url.substr(pos_p, pos_c - pos_p); /* stoi throws exception on failure, we don't need it */ - port = 0; for (char c : port_str) { if (c < '0' || c > '9') return false; @@ -171,9 +146,7 @@ namespace http pos_p = pos_c; } else { /* start of path part found */ - host = ipv6 ? - url.substr(pos_p + 1, pos_c - pos_p - 2) : - url.substr(pos_p, pos_c - pos_p); + host = url.substr(pos_p, pos_c - pos_p); pos_p = pos_c; } } @@ -186,7 +159,6 @@ namespace http return true; } else if (url.at(pos_c) == '?') { /* found query part */ - hasquery = true; path = url.substr(pos_p, pos_c - pos_p); pos_p = pos_c + 1; pos_c = url.find('#', pos_p); @@ -209,9 +181,8 @@ 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(); @@ -239,25 +210,15 @@ namespace http } else if (user != "") { out += user + "@"; } - if (ipv6) { - if (port) { - out += "[" + host + "]:" + std::to_string(port); - } else { - out += "[" + host + "]"; - } + if (port) { + out += host + ":" + std::to_string(port); } else { - if (port) { - out += host + ":" + std::to_string(port); - } else { - out += host; - } + out += host; } } out += path; - if (hasquery) // add query even if it was empty - out += "?"; if (query != "") - out += query; + out += "?" + query; if (frag != "") out += "#" + frag; return out; @@ -268,7 +229,7 @@ namespace http return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) { + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { add_header(name, value.c_str(), replace); } @@ -287,13 +248,12 @@ namespace http headers.erase(name); } - int HTTPReq::parse(const char *buf, size_t len) - { - return parse({buf, len}); + int HTTPReq::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); } - int HTTPReq::parse(std::string_view str) - { + int HTTPReq::parse(const std::string& 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; @@ -302,14 +262,11 @@ namespace http if (eoh == std::string::npos) return 0; /* str not contains complete request */ - while ((eol = str.find(CRLF, pos)) != std::string::npos) - { - if (expect == REQ_LINE) - { - std::string_view line = str.substr(pos, eol - pos); - std::vector tokens; + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == REQ_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; strsplit(line, tokens, ' '); - if (tokens.size() != 3) return -1; if (!is_http_method(tokens[0])) @@ -326,18 +283,18 @@ namespace http } else { - std::string_view line = str.substr(pos, eol - pos); + std::string 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 + CRLF.length(); + pos = eol + strlen(CRLF); if (pos >= eoh) break; } - return eoh + HTTP_EOH.length(); + return eoh + strlen(HTTP_EOH); } void HTTPReq::write(std::ostream & o) @@ -381,7 +338,7 @@ namespace http } } - std::string HTTPReq::GetHeader (std::string_view name) const + std::string HTTPReq::GetHeader (const std::string& name) const { for (auto& it : headers) if (it.first == name) @@ -389,14 +346,6 @@ namespace http return ""; } - size_t HTTPReq::GetNumHeaders (std::string_view name) const - { - size_t num = 0; - for (auto& it : headers) - if (it.first == name) num++; - return num; - } - bool HTTPRes::is_chunked() const { auto it = headers.find("Transfer-Encoding"); @@ -432,13 +381,12 @@ namespace http return length; } - int HTTPRes::parse(const char *buf, size_t len) - { - return parse({buf,len}); + int HTTPRes::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); } - int HTTPRes::parse(std::string_view str) - { + int HTTPRes::parse(const std::string& 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; @@ -446,41 +394,35 @@ 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_view line = str.substr(pos, eol - pos); - std::vector tokens; + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == RES_LINE) { + std::string 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; - auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); - if (res.ec != std::errc()) - return -1; + code = atoi(tokens[1].c_str()); if (code < 100 || code >= 600) return -1; /* all ok */ version = tokens[0]; status = tokens[2]; expect = HEADER_LINE; - } - else - { - std::string_view line = str.substr(pos, eol - pos); + } else { + std::string 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 + CRLF.length(); + pos = eol + strlen(CRLF); if (pos >= eoh) break; } - return eoh + HTTP_EOH.length(); + return eoh + strlen(HTTP_EOH); } std::string HTTPRes::to_string() { @@ -505,11 +447,9 @@ namespace http return ss.str(); } - std::string_view HTTPCodeToStatus(int code) - { - std::string_view ptr; - switch (code) - { + const char * HTTPCodeToStatus(int code) { + const char *ptr; + switch (code) { case 105: ptr = "Name Not Resolved"; break; /* success */ case 200: ptr = "OK"; break; @@ -536,14 +476,14 @@ namespace http return ptr; } - std::string UrlDecode(std::string_view data, bool allow_null) + std::string UrlDecode(const std::string& data, bool allow_null) { std::string decoded(data); size_t pos = 0; while ((pos = decoded.find('%', pos)) != std::string::npos) { - char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16); - if (!c && !allow_null) + char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); + if (c == '\0' && !allow_null) { pos += 3; continue; diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index c65c1ce4..9445a01a 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,15 +14,16 @@ #include #include #include -#include #include namespace i2p { namespace http { - 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 */ + 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 */ struct URL { @@ -32,19 +33,17 @@ namespace http std::string host; unsigned short int port; std::string path; - bool hasquery; std::string query; std::string frag; - bool ipv6; - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), query(""), frag(""), ipv6(false) {}; + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; /** * @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 (std::string_view url); + bool parse (const std::string& url); /** * @brief Parse query part of url to key/value map @@ -68,7 +67,7 @@ namespace http { std::map headers; - void add_header(const char *name, const std::string & value, bool replace = false); + void add_header(const char *name, 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 +90,7 @@ namespace http * @note Positive return value is a size of header */ int parse(const char *buf, size_t len); - int parse(std::string_view buf); + int parse(const std::string& buf); /** @brief Serialize HTTP request to string */ std::string to_string(); @@ -101,9 +100,7 @@ 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 (std::string_view name) const; - size_t GetNumHeaders (std::string_view name) const; - size_t GetNumHeaders () const { return headers.size (); }; + std::string GetHeader (const std::string& name) const; }; struct HTTPRes : HTTPMsg { @@ -127,7 +124,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_view buf); + int parse(const std::string& buf); /** * @brief Serialize HTTP response to string @@ -152,7 +149,7 @@ namespace http * @param code HTTP code [100, 599] * @return Immutable string with status */ - std::string_view HTTPCodeToStatus(int code); + const char * HTTPCodeToStatus(int code); /** * @brief Replaces %-encoded characters in string with their values @@ -160,7 +157,7 @@ namespace http * @param null If set to true - decode also %00 sequence, otherwise - skip * @return Decoded string */ - std::string UrlDecode(std::string_view data, bool null = false); + std::string UrlDecode(const std::string& data, bool null = false); /** * @brief Merge HTTP response content with Transfer-Encoding: chunked diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e97a3596..fec96939 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,15 +10,20 @@ #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 "TransitTunnel.h" +#include "Transports.h" +#include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "I2NPProtocol.h" #include "version.h" +using namespace i2p::transport; + namespace i2p { std::shared_ptr NewI2NPMessage () @@ -31,11 +36,6 @@ 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,10 +43,7 @@ namespace i2p std::shared_ptr NewI2NPMessage (size_t len) { - 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 (); + return (len < I2NP_MAX_SHORT_MESSAGE_SIZE - I2NP_HEADER_SIZE - 2) ? NewI2NPShortMessage () : NewI2NPMessage (); } void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID, bool checksum) @@ -67,15 +64,11 @@ namespace i2p SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); } - bool I2NPMessage::IsExpired (uint64_t ts) const - { - 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 ()); + 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 } std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) @@ -110,17 +103,6 @@ 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 (); @@ -142,10 +124,9 @@ namespace i2p } std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, - uint32_t replyTunnelID, bool exploratory, std::unordered_set * excludedPeers) + uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - int cnt = excludedPeers ? excludedPeers->size () : 0; - auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); + auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -166,6 +147,7 @@ namespace i2p if (excludedPeers) { + int cnt = excludedPeers->size (); htobe16buf (buf, cnt); buf += 2; for (auto& it: *excludedPeers) @@ -187,7 +169,7 @@ namespace i2p } std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::unordered_set& excludedFloodfills, + const std::set& excludedFloodfills, std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES) { @@ -371,6 +353,305 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } + static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) + { + if (maxNumTransitTunnels > 0 && g_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + g_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } + + uint16_t GetMaxNumTransitTunnels () + { + return g_MaxNumTransitTunnels; + } + + static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + { + for (int i = 0; i < num; i++) + { + uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; + if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); + if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; + uint8_t retCode = 0; + // replace record to reply + if (i2p::context.AcceptsTunnels () && + i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && + !i2p::transport::transports.IsBandwidthExceeded () && + !i2p::transport::transports.IsTransitBandwidthExceeded ()) + { + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + } + else + retCode = 30; // always reject with bandwidth reason (30) + + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; + // encrypt reply + i2p::crypto::CBCEncryption encryption; + for (int j = 0; j < num; j++) + { + uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + } + } + return true; + } + } + return false; + } + + static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); + if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); + return; + } + + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + } + else + { + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) + { + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } + } + + static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) + { + LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); + } + + static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + if (len < num*recordSize + 1) + { + LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); + return; + } + + auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); + if (tunnel) + { + // reply for outbound tunnel + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddOutboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + } + else + LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); + } + + static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); + if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); + return; + } + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + return; + } + const uint8_t * record = buf + 1; + for (int i = 0; i < num; i++) + { + if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); + return; + } + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); + return; + } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + uint8_t replyKey[32], layerKey[32], ivKey[32]; + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); + memcpy (replyKey, noiseState.m_CK + 32, 32); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + memcpy (ivKey, noiseState.m_CK , 32); + + // check if we accept this tunnel + uint8_t retCode = 0; + if (!i2p::context.AcceptsTunnels () || + i2p::tunnel::tunnels.GetTransitTunnels ().size () > g_MaxNumTransitTunnels || + i2p::transport::transports.IsBandwidthExceeded () || + i2p::transport::transports.IsTransitBandwidthExceeded ()) + retCode = 30; + if (!retCode) + { + // create new transit tunnel + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + } + + // encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // send reply + if (isEndpoint) + { + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + tunnel->SendTunnelDataMsg (replyMsg); + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } + } + else + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + return; + } + record += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + } + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPTunnelMessage (false); @@ -424,11 +705,7 @@ namespace i2p return msg; } else - { - auto newMsg = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); - if (msg->onDrop) newMsg->onDrop = msg->onDrop; - return newMsg; - } + return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); } std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, @@ -466,6 +743,49 @@ 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) @@ -475,34 +795,29 @@ namespace i2p switch (typeID) { case eI2NPTunnelData: - if (!msg->from) - i2p::tunnel::tunnels.PostTunnelData (msg); + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPTunnelGateway: - if (!msg->from) - i2p::tunnel::tunnels.PostTunnelData (msg); + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: { - if (msg->from && msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); + if (msg->from) + { + if (msg->from->GetTunnelPool ()) + msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); + else + LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore"); + } else i2p::context.ProcessGarlicMessage (msg); 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 if floodfill and came directly - if (!msg->from && i2p::context.IsFloodfill ()) - i2p::data::netdb.PostI2NPMsg (msg); + // forward to netDb + i2p::data::netdb.PostI2NPMsg (msg); break; case eI2NPDeliveryStatus: { @@ -512,25 +827,17 @@ namespace i2p i2p::context.ProcessDeliveryStatusMessage (msg); break; } - case eI2NPTunnelTest: - if (msg->from && msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessTunnelTest (msg); - break; case eI2NPVariableTunnelBuild: - case eI2NPTunnelBuild: - case eI2NPShortTunnelBuild: - // forward to tunnel thread - if (!msg->from) - i2p::tunnel::tunnels.PostTunnelData (msg); - break; case eI2NPVariableTunnelBuildReply: + case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: + case eI2NPShortTunnelBuild: case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; default: - LogPrint(eLogError, "I2NP: Unexpected I2NP message with type ", int(typeID), " during handling; skipping"); + HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); } } } @@ -561,8 +868,14 @@ 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 39aed10f..e60f6a9c 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,10 +11,8 @@ #include #include -#include +#include #include -#include -#include #include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" @@ -49,11 +47,6 @@ 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; @@ -108,6 +101,7 @@ namespace i2p enum I2NPMessageType { + eI2NPDummyMsg = 0, eI2NPDatabaseStore = 1, eI2NPDatabaseLookup = 2, eI2NPDatabaseSearchReply = 3, @@ -121,8 +115,7 @@ namespace i2p eI2NPVariableTunnelBuild = 23, eI2NPVariableTunnelBuildReply = 24, eI2NPShortTunnelBuild = 25, - eI2NPShortTunnelBuildReply = 26, - eI2NPTunnelTest = 231 + eI2NPShortTunnelBuildReply = 26 }; const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; @@ -145,17 +138,8 @@ 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 @@ -164,11 +148,9 @@ 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), enqueueTime (0) {}; // reserve 2 bytes for NTCP header + I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2), + offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header // header accessors uint8_t * GetHeader () { return GetBuffer (); }; @@ -178,9 +160,7 @@ 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 ()); }; @@ -260,6 +240,7 @@ namespace tunnel SetSize (len - offset - I2NP_HEADER_SIZE); SetChks (0); } + void ToNTCP2 () { uint8_t * ntcp2 = GetNTCP2Header (); @@ -270,9 +251,6 @@ 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 @@ -284,7 +262,6 @@ 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); @@ -292,12 +269,11 @@ 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::unordered_set * excludedPeers = nullptr); + uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::unordered_set& excludedFloodfills, + const std::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); @@ -317,6 +293,7 @@ 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 @@ -329,8 +306,12 @@ namespace tunnel private: - std::list > m_TunnelMsgs, m_TunnelGatewayMsgs; + std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; + + const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); + uint16_t GetMaxNumTransitTunnels (); } #endif diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index 681a4999..d97bd055 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__) || defined(__HAIKU__) +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__) #include #elif defined(__APPLE__) && defined(__MACH__) @@ -36,23 +36,6 @@ #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) @@ -67,7 +50,6 @@ #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 240862ae..cff0c37d 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,6 @@ #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" -#include "CryptoKey.h" #include "Identity.h" namespace i2p @@ -28,15 +27,18 @@ namespace data size_t Identity::FromBuffer (const uint8_t * buf, size_t len) { - if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow - memcpy (this, buf, DEFAULT_IDENTITY_SIZE); + if ( len < DEFAULT_IDENTITY_SIZE ) { + // buffer too small, don't overflow + return 0; + } + memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE); return DEFAULT_IDENTITY_SIZE; } IdentHash Identity::Hash () const { IdentHash hash; - SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash); + SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash); return hash; } @@ -47,22 +49,13 @@ namespace data IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { - uint8_t randomPaddingBlock[32]; - RAND_bytes (randomPaddingBlock, 32); if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { - memcpy (m_StandardIdentity.publicKey, publicKey ? publicKey : randomPaddingBlock, 32); - for (int i = 0; i < 7; i++) // 224 bytes - memcpy (m_StandardIdentity.publicKey + 32*(i + 1), randomPaddingBlock, 32); + memcpy (m_StandardIdentity.publicKey, publicKey, 32); + RAND_bytes (m_StandardIdentity.publicKey + 32, 224); } else - { - if (publicKey) - memcpy (m_StandardIdentity.publicKey, publicKey, 256); - else - for (int i = 0; i < 8; i++) // 256 bytes - memcpy (m_StandardIdentity.publicKey + 32*i, randomPaddingBlock, 32); - } + memcpy (m_StandardIdentity.publicKey, publicKey, 256); if (type != SIGNING_KEY_TYPE_DSA_SHA1) { size_t excessLen = 0; @@ -100,8 +93,7 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - for (int i = 0; i < 3; i++) // 96 bytes - memcpy (m_StandardIdentity.signingKey + 32*i, randomPaddingBlock, 32); + RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } @@ -120,17 +112,6 @@ 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"); } @@ -143,15 +124,12 @@ namespace data htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) + if (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; + LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen); + excessLen = MAX_EXTENDED_BUFFER_SIZE - 4; } - else - memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); + memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } // calculate ident hash @@ -199,8 +177,7 @@ namespace data IdentityEx::~IdentityEx () { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) - delete[] m_ExtendedBufferPtr; + delete m_Verifier; } IdentityEx& IdentityEx::operator=(const IdentityEx& other) @@ -208,32 +185,15 @@ 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) - { - 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); - } + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; + memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); } + + delete m_Verifier; m_Verifier = nullptr; - CreateVerifier (); return *this; } @@ -242,10 +202,11 @@ namespace data { m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); + m_ExtendedLen = 0; + delete m_Verifier; m_Verifier = nullptr; - CreateVerifier (); return *this; } @@ -259,28 +220,13 @@ 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) - { - 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); + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; + memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else { @@ -293,8 +239,8 @@ namespace data m_ExtendedLen = 0; SHA256(buf, GetFullLen (), m_IdentHash); + delete m_Verifier; m_Verifier = nullptr; - CreateVerifier (); return GetFullLen (); } @@ -305,32 +251,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) - { - 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); - } + memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); return fullLen; } - size_t IdentityEx::FromBase64(std::string_view s) + size_t IdentityEx::FromBase64(const std::string& s) { - std::vector buf(s.length ()); // binary data can't exceed base64 - auto len = Base64ToByteStream (s, buf.data(), buf.size ()); + 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); 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); - return i2p::data::ByteStreamToBase64 (buf.data(), l); + size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen); + return std::string (str.data(), l1); } size_t IdentityEx::GetSigningPublicKeyLen () const { + if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPublicKeyLen (); return 128; @@ -339,12 +285,13 @@ namespace data const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const { auto keyLen = GetSigningPublicKeyLen (); - if (keyLen > 128) return nullptr; // P521 or PQ + if (keyLen > 128) return nullptr; // P521 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; @@ -352,12 +299,14 @@ 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; @@ -366,7 +315,7 @@ namespace data SigningKeyType IdentityEx::GetSigningKeyType () const { if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 2) - return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer : m_ExtendedBufferPtr); // signing key + return bufbe16toh (m_ExtendedBuffer); // signing key return SIGNING_KEY_TYPE_DSA_SHA1; } @@ -379,7 +328,7 @@ namespace data CryptoKeyType IdentityEx::GetCryptoKeyType () const { if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4) - return bufbe16toh (m_ExtendedLen <= MAX_EXTENDED_BUFFER_SIZE ? m_ExtendedBuffer + 2 : m_ExtendedBufferPtr + 2); // crypto key + return bufbe16toh (m_ExtendedBuffer + 2); // crypto key return CRYPTO_KEY_TYPE_ELGAMAL; } @@ -403,10 +352,6 @@ 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: @@ -418,41 +363,52 @@ namespace data return nullptr; } - void IdentityEx::CreateVerifier () + void IdentityEx::CreateVerifier () const { - if (!m_Verifier) + if (m_Verifier) return; // don't create again + auto verifier = CreateVerifier (GetSigningKeyType ()); + if (verifier) { - auto verifier = CreateVerifier (GetSigningKeyType ()); - if (verifier) + auto keyLen = verifier->GetPublicKeyLen (); + if (keyLen <= 128) + verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); + else { - 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; - } + // 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) @@ -463,14 +419,15 @@ 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); }; @@ -483,21 +440,11 @@ 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 - size_t keyLen = m_Public->GetSigningPrivateKeyLen (); - m_SigningPrivateKey.resize (keyLen); - memcpy (m_SigningPrivateKey.data (), keys.signingPrivateKey, keyLen); + memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ()); m_OfflineSignature.resize (0); m_TransientSignatureLen = 0; m_TransientSigningPrivateKeyLen = 0; @@ -513,7 +460,7 @@ namespace data m_OfflineSignature = other.m_OfflineSignature; m_TransientSignatureLen = other.m_TransientSignatureLen; m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen; - m_SigningPrivateKey = other.m_SigningPrivateKey; + memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_TransientSigningPrivateKeyLen > 0 ? m_TransientSigningPrivateKeyLen : m_Public->GetSigningPrivateKeyLen ()); m_Signer = nullptr; CreateSigner (); return *this; @@ -536,9 +483,8 @@ namespace data memcpy (m_PrivateKey, buf + ret, cryptoKeyLen); ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); - if (signingPrivateKeySize + ret > len) return 0; // overflow - m_SigningPrivateKey.resize (signingPrivateKeySize); - memcpy (m_SigningPrivateKey.data (), buf + ret, signingPrivateKeySize); + if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow + memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); ret += signingPrivateKeySize; m_Signer = nullptr; // check if signing private key is all zeros @@ -553,12 +499,7 @@ namespace data { // offline information const uint8_t * offlineInfo = buf + ret; - uint32_t expires = bufbe32toh (buf + ret); ret += 4; // expires timestamp - if (expires < i2p::util::GetSecondsSinceEpoch ()) - { - LogPrint (eLogError, "Identity: Offline signature expired"); - return 0; - } + ret += 4; // expires timestamp SigningKeyType keyType = bufbe16toh (buf + ret); ret += 2; // key type std::unique_ptr transientVerifier (IdentityEx::CreateVerifier (keyType)); if (!transientVerifier) return 0; @@ -579,9 +520,8 @@ namespace data memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen); // override signing private key m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen (); - if (m_TransientSigningPrivateKeyLen + ret > len) return 0; - if (m_TransientSigningPrivateKeyLen > 128) m_SigningPrivateKey.resize (m_TransientSigningPrivateKeyLen); - memcpy (m_SigningPrivateKey.data (), buf + ret, m_TransientSigningPrivateKeyLen); + if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0; + memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen); ret += m_TransientSigningPrivateKeyLen; CreateSigner (keyType); } @@ -601,7 +541,7 @@ namespace data if (IsOfflineSignature ()) memset (buf + ret, 0, signingPrivateKeySize); else - memcpy (buf + ret, m_SigningPrivateKey.data (), signingPrivateKeySize); + memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize); ret += signingPrivateKeySize; if (IsOfflineSignature ()) { @@ -612,24 +552,32 @@ namespace data ret += offlineSignatureLen; // transient private key if (ret + m_TransientSigningPrivateKeyLen > len) return 0; - memcpy (buf + ret, m_SigningPrivateKey.data (), m_TransientSigningPrivateKeyLen); + memcpy (buf + ret, m_SigningPrivateKey, m_TransientSigningPrivateKeyLen); ret += m_TransientSigningPrivateKeyLen; } return ret; } - size_t PrivateKeys::FromBase64(std::string_view s) + size_t PrivateKeys::FromBase64(const std::string& s) { - std::vector buf(s.length ()); - size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ()); - return FromBuffer (buf.data (), l); + 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::string PrivateKeys::ToBase64 () const { - std::vector buf(GetFullLen ()); - size_t l = ToBuffer (buf.data (), buf.size ()); - return i2p::data::ByteStreamToBase64 (buf.data (), l); + 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; } void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const @@ -651,13 +599,13 @@ namespace data { if (m_Signer) return; if (keyType == SIGNING_KEY_TYPE_DSA_SHA1) - m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey.data (), m_Public->GetStandardIdentity ().signingKey)); + m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey)); else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ()) - 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 + m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH)); // TODO: remove public key check else { // public key is not required - auto signer = CreateSigner (keyType, m_SigningPrivateKey.data ()); + auto signer = CreateSigner (keyType, m_SigningPrivateKey); if (signer) m_Signer.reset (signer); } } @@ -692,11 +640,6 @@ 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"); } @@ -710,7 +653,8 @@ namespace data size_t PrivateKeys::GetPrivateKeyLen () const { - return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); + // private key length always 256, but type 4 + return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256; } uint8_t * PrivateKeys::GetPadding() @@ -736,38 +680,34 @@ namespace data return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: - case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: - case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: - case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: + case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); break; + case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: + return std::make_shared(key); + break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); }; return nullptr; } - PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType, bool isDestination) + PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType) { if (type != SIGNING_KEY_TYPE_DSA_SHA1) { PrivateKeys keys; // signature - 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 ()); + uint8_t signingPublicKey[512]; // signing public key is 512 bytes max + GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey); // encryption uint8_t publicKey[256]; - if (isDestination) - RAND_bytes (keys.m_PrivateKey, 256); - else - GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); + GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); // identity - keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey.data (), type, cryptoType); + keys.m_Public = std::make_shared (publicKey, signingPublicKey, type, cryptoType); keys.CreateSigner (); return keys; @@ -792,7 +732,9 @@ 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); @@ -806,11 +748,6 @@ 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 @@ -825,12 +762,13 @@ 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: @@ -848,10 +786,9 @@ 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.data (), keys.m_OfflineSignature.data () + 6); // public key + GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, 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; @@ -870,14 +807,11 @@ namespace data return keys; } - IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay) + IdentHash CreateRoutingKey (const IdentHash& ident) { uint8_t buf[41]; // ident + yyyymmdd memcpy (buf, (const uint8_t *)ident, 32); - if (nextDay) - i2p::util::GetNextDayDate ((char *)(buf + 32)); - else - i2p::util::GetCurrentDate ((char *)(buf + 32)); + i2p::util::GetCurrentDate ((char *)(buf + 32)); IdentHash key; SHA256(buf, 40, key); return key; @@ -886,12 +820,29 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; - - 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]; +#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]; + } return m; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index c95ce000..10f1d5ed 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,19 +12,16 @@ #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; @@ -59,8 +56,6 @@ 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 (); @@ -70,10 +65,9 @@ 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_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 CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later + const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES + const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2; @@ -82,12 +76,11 @@ 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; // since openssl 3.0.0 + const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented 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; @@ -108,7 +101,7 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; - size_t FromBase64(std::string_view s); + size_t FromBase64(const std::string& s); std::string ToBase64 () const; const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; @@ -125,6 +118,7 @@ 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); @@ -134,23 +128,19 @@ namespace data private: - void CreateVerifier (); - + void CreateVerifier () const; + void UpdateVerifier (i2p::crypto::Verifier * verifier) const; + private: Identity m_StandardIdentity; IdentHash m_IdentHash; - std::unique_ptr m_Verifier; + mutable i2p::crypto::Verifier * m_Verifier = nullptr; + mutable std::mutex m_VerifierMutex; size_t m_ExtendedLen; - union - { - uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; - uint8_t * m_ExtendedBufferPtr; - }; + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; }; - size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer - class PrivateKeys // for eepsites { public: @@ -164,7 +154,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.data (); }; + const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; }; size_t GetSignatureLen () const; // might not match identity bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; }; uint8_t * GetPadding(); @@ -175,13 +165,13 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; - size_t FromBase64(std::string_view s); + size_t FromBase64(const std::string& s); std::string ToBase64 () const; std::shared_ptr CreateDecryptor (const uint8_t * key) const; static std::shared_ptr CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key); - static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL, bool isDestination = false); + static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL); static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long static i2p::crypto::Signer * CreateSigner (SigningKeyType keyType, const uint8_t * priv); @@ -200,7 +190,7 @@ namespace data std::shared_ptr m_Public; uint8_t m_PrivateKey[256]; - std::vector m_SigningPrivateKey; + uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes mutable std::unique_ptr m_Signer; std::vector m_OfflineSignature; // non zero length, if applicable size_t m_TransientSignatureLen = 0; @@ -221,7 +211,7 @@ namespace data bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; }; }; - IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay = false); + IdentHash CreateRoutingKey (const IdentHash& ident); XORMetric operator^(const IdentHash& key1, const IdentHash& key2); // destination for delivery instructions diff --git a/libi2pd/KadDHT.cpp b/libi2pd/KadDHT.cpp deleted file mode 100644 index 0f9df8e4..00000000 --- a/libi2pd/KadDHT.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* -* 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 deleted file mode 100644 index 3bc31780..00000000 --- a/libi2pd/KadDHT.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -* 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 3001bdfb..c844ab60 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,7 +14,6 @@ #include "Timestamp.h" #include "NetDb.hpp" #include "Tunnel.h" -#include "CryptoKey.h" #include "LeaseSet.h" namespace i2p @@ -36,9 +35,16 @@ namespace data ReadFromBuffer (); } - void LeaseSet::Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature) + void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature) { - SetBuffer (buf, len); + if (len > m_BufferLen) + { + auto oldBuffer = m_Buffer; + m_Buffer = new uint8_t[len]; + delete[] oldBuffer; + } + memcpy (m_Buffer, buf, len); + m_BufferLen = len; ReadFromBuffer (false, verifySignature); } @@ -51,11 +57,11 @@ namespace data void LeaseSet::ReadFromBuffer (bool readIdentity, bool verifySignature) { if (readIdentity || !m_Identity) - m_Identity = netdb.NewIdentity (m_Buffer, m_BufferLen); + m_Identity = std::make_shared(m_Buffer, m_BufferLen); size_t size = m_Identity->GetFullLen (); - if (size + 256 > m_BufferLen) + if (size > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: Identity length ", int(size), " exceeds buffer size ", int(m_BufferLen)); + LogPrint (eLogError, "LeaseSet: Identity length ", size, " exceeds buffer size ", m_BufferLen); m_IsValid = false; return; } @@ -68,7 +74,7 @@ namespace data size += m_Identity->GetSigningPublicKeyLen (); // unused signing key if (size + 1 > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); + LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen); m_IsValid = false; return; } @@ -77,13 +83,13 @@ namespace data LogPrint (eLogDebug, "LeaseSet: Read num=", (int)num); if (!num || num > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: Incorrect number of leases", (int)num); + LogPrint (eLogError, "LeaseSet: Rncorrect number of leases", (int)num); m_IsValid = false; return; } if (size + num*LEASE_SIZE > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); + LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen); m_IsValid = false; return; } @@ -119,7 +125,7 @@ namespace data auto signedSize = leases - m_Buffer; if (signedSize + m_Identity->GetSignatureLen () > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", int(m_BufferLen)); + LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", m_BufferLen); m_IsValid = false; } else if (!m_Identity->Verify (m_Buffer, signedSize, leases)) @@ -166,7 +172,7 @@ namespace data m_ExpirationTime = lease.endDate; if (m_StoreLeases) { - auto ret = m_Leases.insert (i2p::data::netdb.NewLease (lease)); + auto ret = m_Leases.insert (std::make_shared(lease)); if (!ret.second) (*ret.first)->endDate = lease.endDate; // update existing (*ret.first)->isUpdated = true; } @@ -258,18 +264,8 @@ namespace data void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) { - if (len > MAX_LS_BUFFER_SIZE) - { - LogPrint (eLogError, "LeaseSet: Buffer is too long ", len); - len = MAX_LS_BUFFER_SIZE; - } - if (m_Buffer && len > m_BufferLen) - { - delete[] m_Buffer; - m_Buffer = nullptr; - } - if (!m_Buffer) - m_Buffer = new uint8_t[len]; + if (m_Buffer) delete[] m_Buffer; + m_Buffer = new uint8_t[len]; m_BufferLen = len; memcpy (m_Buffer, buf, len); } @@ -278,32 +274,31 @@ namespace data { if (len <= m_BufferLen) m_BufferLen = len; else - LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen)); + LogPrint (eLogError, "LeaseSet2: Actual buffer size ", len , " exceeds full buffer size ", m_BufferLen); } - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, - bool storeLeases, std::shared_ptr dest, CryptoKeyType preferredCrypto): + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) { SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBufferEncrypted (buf, len, nullptr, dest, nullptr); + ReadFromBufferEncrypted (buf, len, nullptr, nullptr); else - ReadFromBuffer (buf, len, dest); + ReadFromBuffer (buf, len); } LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, - std::shared_ptr dest, const uint8_t * secret, CryptoKeyType preferredCrypto): + const uint8_t * secret, CryptoKeyType preferredCrypto): LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto) { - ReadFromBufferEncrypted (buf, len, key, dest, secret); + ReadFromBufferEncrypted (buf, len, key, secret); } - void LeaseSet2::Update (const uint8_t * buf, size_t len, std::shared_ptr dest, bool verifySignature) + void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature) { SetBuffer (buf, len); if (GetStoreType () != NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBuffer (buf, len, dest, false, verifySignature); + ReadFromBuffer (buf, len, false, verifySignature); // TODO: implement encrypted } @@ -313,20 +308,19 @@ namespace data return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp; } - void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, std::shared_ptr dest, - bool readIdentity, bool verifySignature) + void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature) { // standard LS2 header std::shared_ptr identity; - if (readIdentity || !GetIdentity ()) + if (readIdentity) { - identity = netdb.NewIdentity (buf, len); + identity = std::make_shared(buf, len); SetIdentity (identity); } else identity = GetIdentity (); size_t offset = identity->GetFullLen (); - if (offset + 8 > len) return; + if (offset + 8 >= len) return; m_PublishedTimestamp = bufbe32toh (buf + offset); offset += 4; // published timestamp (seconds) uint16_t expires = bufbe16toh (buf + offset); offset += 2; // expires (seconds) SetExpirationTime ((m_PublishedTimestamp + expires)*1000LL); // in milliseconds @@ -352,7 +346,7 @@ namespace data switch (m_StoreType) { case NETDB_STORE_TYPE_STANDARD_LEASESET2: - s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset, dest); + s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset); break; case NETDB_STORE_TYPE_META_LEASESET2: s = ReadMetaLS2TypeSpecificPart (buf + offset, len - offset); @@ -369,13 +363,7 @@ 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)); - return; - } SetBufferLen (offset); } @@ -394,61 +382,40 @@ namespace data return verified; } - size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len, - std::shared_ptr dest) + size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len) { size_t offset = 0; // properties uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties + if (offset + 1 >= len) return 0; // key sections CryptoKeyType preferredKeyType = m_EncryptionType; - m_EncryptionType = 0; bool preferredKeyFound = false; - if (offset + 1 > len) return 0; int numKeySections = buf[offset]; offset++; for (int i = 0; i < numKeySections; i++) { - if (offset + 4 > len) return 0; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption key type + if (offset + 2 >= len) return 0; uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; - if (offset + encryptionKeyLen > len) return 0; + if (offset + encryptionKeyLen >= len) return 0; if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only { - // 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; - } - } - } + // 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; + } } offset += encryptionKeyLen; } // leases - if (offset + 1 > len) return 0; + 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 (); @@ -459,19 +426,13 @@ 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 (); } else offset += numLeases*LEASE2_SIZE; // 40 bytes per lease - - return (offset > len ? 0 : offset); + return offset; } size_t LeaseSet2::ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len) @@ -481,18 +442,18 @@ namespace data uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties // entries - if (offset + 1 > len) return 0; + if (offset + 1 >= len) return 0; int numEntries = buf[offset]; offset++; for (int i = 0; i < numEntries; i++) { - if (offset + LEASE2_SIZE > len) return 0; + if (offset + 40 >= len) return 0; offset += 32; // hash offset += 3; // flags offset += 1; // cost offset += 4; // expires } // revocations - if (offset + 1 > len) return 0; + if (offset + 1 >= len) return 0; int numRevocations = buf[offset]; offset++; for (int i = 0; i < numRevocations; i++) { @@ -502,8 +463,7 @@ namespace data return offset; } - void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, - std::shared_ptr key, std::shared_ptr dest, const uint8_t * secret) + void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret) { size_t offset = 0; // blinded key @@ -606,7 +566,7 @@ namespace data m_StoreType = innerPlainText[0]; SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); // parse and verify Layer 2 - ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1, dest); + ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); } else LogPrint (eLogError, "LeaseSet2: Unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); @@ -758,41 +718,25 @@ 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 } @@ -853,8 +797,8 @@ namespace data } LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const EncryptionKeys& encryptionKeys, const std::vector >& tunnels, - bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted): + const KeySections& encryptionKeys, const std::vector >& tunnels, + bool isPublic, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { auto identity = keys.GetPublic (); @@ -863,7 +807,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->pub.size()/*key*/; + keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*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; @@ -883,7 +827,8 @@ namespace data m_Buffer[0] = storeType; // LS2 header auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1; - htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds) + auto timestamp = i2p::util::GetSecondsSinceEpoch (); + htobe32buf (m_Buffer + offset, timestamp); 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 ()) @@ -898,50 +843,35 @@ 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->pub.size()); offset += 2; // key len - memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key + 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 } // 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 - publishedTimestamp; + auto expires = (int)expirationTime - timestamp; htobe16buf (expiresBuf, expires > 0 ? expires : 0); } else { // no tunnels or withdraw - SetExpirationTime (publishedTimestamp*1000LL); + SetExpirationTime (timestamp*1000LL); memset (expiresBuf, 0, 2); // expires immeditely } // sign diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 46f40cb5..a79a5870 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,14 +12,12 @@ #include #include #include -#include #include #include #include "Identity.h" #include "Timestamp.h" #include "I2PEndian.h" #include "Blinding.h" -#include "CryptoKey.h" namespace i2p { @@ -59,16 +57,12 @@ namespace data }; typedef std::function LeaseInspectFunc; -#if OPENSSL_PQ - const size_t MAX_LS_BUFFER_SIZE = 8192; -#else - const size_t MAX_LS_BUFFER_SIZE = 4096; -#endif + + const size_t MAX_LS_BUFFER_SIZE = 3072; 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 { @@ -76,7 +70,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, std::shared_ptr dest, bool verifySignature); + virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true); virtual bool IsNewer (const uint8_t * buf, size_t len) const; void PopulateLeases (); // from buffer @@ -102,9 +96,6 @@ 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 (); @@ -154,43 +145,38 @@ namespace data { public: - LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update - LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, - 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; }; + 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; }; bool IsPublic () const { return m_IsPublic; }; - 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; + 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; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted) const override; - CryptoKeyType GetEncryptionType () const override { return m_EncryptionType; }; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; + CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; private: - 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); + 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); 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 override; + uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const; 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; // seconds + uint32_t m_PublishedTimestamp = 0; bool m_IsPublic = true, m_IsPublishedEncrypted = false; std::shared_ptr m_TransientVerifier; CryptoKeyType m_EncryptionType; @@ -256,13 +242,17 @@ namespace data { public: - typedef std::list > EncryptionKeys; + struct KeySection + { + uint16_t keyType, keyLen; + const uint8_t * encryptionPublicKey; + }; + typedef std::vector KeySections; LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const EncryptionKeys& encryptionKeys, + const KeySections& encryptionKeys, const std::vector >& tunnels, - bool isPublic, uint64_t publishedTimestamp, - bool isPublishedEncrypted = false); + bool isPublic, 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 76e85d4a..e90b5e2b 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,14 +18,13 @@ namespace log { /** * @brief Maps our loglevel to their symbolic name */ - static const char *g_LogLevelStr[eNumLogLevels] = + static const char * g_LogLevelStr[eNumLogLevels] = { - "none", // eLogNone - "critical", // eLogCritical - "error", // eLogError - "warn", // eLogWarning - "info", // eLogInfo - "debug" // eLogDebug + "none", // eLogNone + "error", // eLogError + "warn", // eLogWarn + "info", // eLogInfo + "debug" // eLogDebug }; /** @@ -33,16 +32,15 @@ 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 */ - "\033[1;34m", /* debug: blue */ - "\033[0m" /* reset */ + [eLogNone] = "\033[0m", /* reset */ + [eLogError] = "\033[1;31m", /* red */ + [eLogWarning] = "\033[1;33m", /* yellow */ + [eLogInfo] = "\033[1;36m", /* cyan */ + [eLogDebug] = "\033[1;34m", /* blue */ + [eNumLogLevels] = "\033[0m", /* reset */ }; #endif @@ -55,7 +53,6 @@ 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; @@ -126,14 +123,13 @@ namespace log { void Log::SetLogLevel (const std::string& level_) { std::string level=str_tolower(level_); - 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; } + 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; } else { - LogPrint(eLogCritical, "Log: Unknown loglevel: ", level); + LogPrint(eLogError, "Log: Unknown loglevel: ", level); return; } LogPrint(eLogInfo, "Log: Logging level set to ", level); @@ -216,7 +212,7 @@ namespace log { m_LogStream = os; return; } - LogPrint(eLogCritical, "Log: Can't open file ", path); + LogPrint(eLogError, "Log: Can't open file ", path); } void Log::SendTo (std::shared_ptr os) { diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 18592c9e..465e10bc 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,7 +27,6 @@ enum LogLevel { eLogNone = 0, - eLogCritical, eLogError, eLogWarning, eLogInfo, @@ -87,8 +86,8 @@ namespace log { Log (); ~Log (); - LogType GetLogType () const { return m_Destination; }; - LogLevel GetLogLevel () const { return m_MinLevel; }; + LogType GetLogType () { return m_Destination; }; + LogLevel GetLogLevel () { return m_MinLevel; }; void Start (); void Stop (); @@ -160,11 +159,6 @@ 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 @@ -172,6 +166,16 @@ 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, ...) @@ -180,14 +184,22 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept template void LogPrint (LogLevel level, TArgs&&... args) noexcept { - if (!CheckLogLevel (level)) return; + i2p::log::Log &log = i2p::log::Logger(); + if (level > log.GetLogLevel ()) + 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(); - i2p::log::Logger().Append(msg); + log.Append(msg); } /** @@ -201,7 +213,11 @@ 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 bc18f5a6..0b20c964 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,10 +19,9 @@ #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 @@ -42,29 +41,28 @@ namespace transport delete[] m_SessionConfirmedBuffer; } - bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) + void 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]; - if (!priv.Agree (pub, inputKeyMaterial)) return false; + priv.Agree (pub, inputKeyMaterial); MixKey (inputKeyMaterial); - return true; } - bool NTCP2Establisher::KDF1Alice () + void NTCP2Establisher::KDF1Alice () { - return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); + KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); } - bool NTCP2Establisher::KDF1Bob () + void NTCP2Establisher::KDF1Bob () { - return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); + KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); } - bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) + void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) { MixHash (sessionRequest + 32, 32); // encrypted payload @@ -75,35 +73,33 @@ namespace transport // x25519 between remote pub and ephemaral priv uint8_t inputKeyMaterial[32]; - if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false; + m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial); + MixKey (inputKeyMaterial); - return true; } - bool NTCP2Establisher::KDF2Alice () + void NTCP2Establisher::KDF2Alice () { - return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ()); + KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ()); } - bool NTCP2Establisher::KDF2Bob () + void NTCP2Establisher::KDF2Bob () { - return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); + KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); } - bool NTCP2Establisher::KDF3Alice () + void NTCP2Establisher::KDF3Alice () { uint8_t inputKeyMaterial[32]; - if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false; + i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial); MixKey (inputKeyMaterial); - return true; } - bool NTCP2Establisher::KDF3Bob () + void NTCP2Establisher::KDF3Bob () { uint8_t inputKeyMaterial[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false; + m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial); MixKey (inputKeyMaterial); - return true; } void NTCP2Establisher::CreateEphemeralKey () @@ -111,19 +107,20 @@ namespace transport m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); } - bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng) + void NTCP2Establisher::CreateSessionRequestMessage () { // create buffer and fill padding - auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes + auto paddingLength = rand () % (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.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X - memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated + encryption.SetIV (m_IV); + encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X + encryption.GetIV (m_IV); // save IV for SessionCreated // encryption key for next block - if (!KDF1Alice ()) return false; + KDF1Alice (); // fill options uint8_t options[32]; // actual options size is 16 bytes memset (options, 0, 16); @@ -131,8 +128,7 @@ namespace transport options[1] = 2; // ver htobe16buf (options + 2, paddingLength); // padLen // m3p2Len - auto riBuffer = i2p::context.CopyRouterInfoBuffer (); - auto bufLen = riBuffer->GetBufferLen (); + auto bufLen = i2p::context.GetRouterInfo ().GetBufferLen (); m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options htobe16buf (options + 4, m3p2Len); // fill m3p2 payload (RouterInfo block) @@ -141,44 +137,40 @@ namespace transport m3p2[0] = eNTCP2BlkRouterInfo; // block htobe16buf (m3p2 + 1, bufLen + 1); // flag + RI m3p2[3] = 0; // flag - memcpy (m3p2 + 4, riBuffer->data (), bufLen); // TODO: eliminate extra copy + memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex // 2 bytes reserved - htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds + htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsA // 4 bytes reserved - // encrypt options - if (!Encrypt (options, m_SessionRequestBuffer + 32, 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options"); - return false; - } - return true; + // 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 } - bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng) + void NTCP2Establisher::CreateSessionCreatedMessage () { - auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); + auto paddingLen = rand () % (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.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y + encryption.SetIV (m_IV); + encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y // encryption key for next block (m_K) - if (!KDF2Bob ()) return false; + KDF2Bob (); 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 - // encrypt options - if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options"); - return false; - } - return true; + htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsB + // sign and encrypt options, use m_H as AD + uint8_t nonce[12]; + memset (nonce, 0, 12); // set nonce to zero + i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt + } - bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 () + void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce) { // update AD MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload @@ -186,31 +178,21 @@ namespace transport if (paddingLength > 0) MixHash (m_SessionCreatedBuffer + 64, paddingLength); - // 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; + // part1 48 bytes + i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt } - bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 () + void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce) { // part 2 // update AD again MixHash (m_SessionConfirmedBuffer, 48); // encrypt m3p2, it must be filled in SessionRequest - if (!KDF3Alice ()) return false; // MixKey, n = 0 + KDF3Alice (); uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; - if (!Encrypt (m3p2, m3p2, m3p2Len - 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2"); - return false; - } + i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt // update h again MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) - return true; } bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew) @@ -219,17 +201,15 @@ namespace transport // decrypt X i2p::crypto::CBCDecryption decryption; decryption.SetKey (i2p::context.GetIdentHash ()); - decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ()); - memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated + decryption.SetIV (i2p::context.GetNTCP2IV ()); + decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ()); + decryption.GetIV (m_IV); // save IV for SessionCreated // decryption key for next block - 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)) + 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 { // options if (options[0] && options[0] != i2p::context.GetNetID ()) @@ -277,16 +257,15 @@ namespace transport // decrypt Y i2p::crypto::CBCDecryption decryption; decryption.SetKey (m_RemoteIdentHash); - decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ()); + decryption.SetIV (m_IV); + decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ()); // decryption key for next block (m_K) - if (!KDF2Alice ()) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed"); - return false; - } + KDF2Alice (); // decrypt and verify MAC uint8_t payload[16]; - if (Decrypt (m_SessionCreatedBuffer + 32, 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 { // options paddingLen = bufbe16toh(payload + 2); @@ -307,7 +286,7 @@ namespace transport return true; } - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () + bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce) { // update AD MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload @@ -315,8 +294,7 @@ namespace transport if (paddingLength > 0) MixHash (m_SessionCreatedBuffer + 64, paddingLength); - // decrypt S, n = 1 - if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32)) + if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); return false; @@ -324,18 +302,14 @@ namespace transport return true; } - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf) + bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf) { // update AD again MixHash (m_SessionConfirmedBuffer, 48); - 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 + KDF3Bob (); + if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt + // caclulate new h again for KDF data MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) else { @@ -351,7 +325,6 @@ 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 @@ -374,7 +347,7 @@ namespace transport LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); } m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + - m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } NTCP2Session::~NTCP2Session () @@ -400,30 +373,11 @@ 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 (); - 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"); - } + LogPrint (eLogDebug, "NTCP2: Session terminated"); } } - void NTCP2Session::Close () - { - m_Socket.close (); - } - void NTCP2Session::TerminateByTimeout () { SendTerminationAndTerminate (eNTCP2IdleTimeout); @@ -431,15 +385,14 @@ namespace transport void NTCP2Session::Done () { - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); + m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); } void NTCP2Session::Established () { m_IsEstablished = true; m_Establisher.reset (nullptr); - SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE); - SendQueue (); + SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT); transports.PeerConnected (shared_from_this ()); } @@ -465,7 +418,7 @@ namespace transport void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) { if (m_NextReceivedBuffer && !m_IsReceiving && - ts > GetLastActivityTimestamp () + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) + ts > m_LastActivityTimestamp + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) { delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; @@ -491,14 +444,8 @@ namespace transport void NTCP2Session::SendSessionRequest () { - 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; - } + m_Establisher->CreateSessionRequestMessage (); // 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)); } @@ -521,6 +468,7 @@ 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 ()); @@ -528,48 +476,38 @@ namespace transport } else { - m_Establisher->CreateEphemeralKey (); - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this (), bytes_transferred] () + LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred); + uint16_t paddingLen = 0; + bool clockSkew = false; + if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew)) + { + if (clockSkew) { - s->ProcessSessionRequest (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 (); } } - 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) @@ -578,25 +516,13 @@ namespace transport Terminate (); } else - { - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->SendSessionCreated (); - }); - } + SendSessionCreated (); } void NTCP2Session::SendSessionCreated () { - 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; - } + m_Establisher->CreateSessionCreatedMessage (); // 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)); } @@ -610,45 +536,31 @@ namespace transport } else { - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this (), bytes_transferred] () + LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred); + uint16_t paddingLen = 0; + if (m_Establisher->ProcessSessionCreatedMessage (paddingLen)) + { + if (paddingLen > 0) { - s->ProcessSessionCreated (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 (); } } - 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) @@ -659,27 +571,17 @@ namespace transport else { m_Establisher->m_SessionCreatedBufferLen += bytes_transferred; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->SendSessionConfirmed (); - }); + SendSessionConfirmed (); } } void NTCP2Session::SendSessionConfirmed () { - 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; - } + 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); // 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)); @@ -731,7 +633,6 @@ 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 ()); @@ -739,150 +640,82 @@ namespace transport } else { - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () + 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 ())) { - s->ProcessSessionConfirmed ();; - }); + KeyDerivationFunctionDataPhase (); + // Bob data phase keys + m_SendKey = m_Kba; + m_ReceiveKey = m_Kab; + SetSipKeys (m_Sipkeysba, m_Sipkeysab); + memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8); + memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8); + // payload + // process RI + if (buf[0] != eNTCP2BlkRouterInfo) + { + LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed"); + Terminate (); + return; + } + auto size = bufbe16toh (buf.data () + 1); + if (size > buf.size () - 3) + { + LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); + Terminate (); + return; + } + // TODO: check flag + i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag + if (ri.IsUnreachable ()) + { + LogPrint (eLogError, "NTCP2: Signature verification failed in SessionConfirmed"); + SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail); + return; + } + if (i2p::util::GetMillisecondsSinceEpoch () > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes + { + LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed"); + SendTerminationAndTerminate (eNTCP2Message3Error); + return; + } + auto addr = ri.GetNTCP2AddressWithStaticKey (m_Establisher->m_RemoteStaticKey); + if (!addr) + { + LogPrint (eLogError, "NTCP2: No NTCP2 address with static key found in SessionConfirmed"); + Terminate (); + return; + } + i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice + // TODO: process options + + // ready to communicate + auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already + SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ()); + if (m_Server.AddNTCP2Session (shared_from_this (), true)) + { + Established (); + ReceiveLength (); + } + else + Terminate (); + } + else + Terminate (); + } + else + Terminate (); } } - 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 @@ -908,17 +741,12 @@ namespace transport void NTCP2Session::ClientLogin () { m_Establisher->CreateEphemeralKey (); - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->SendSessionRequest (); - }); + SendSessionRequest (); } void NTCP2Session::ServerLogin () { - SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT); - SetLastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()); + m_Establisher->CreateEphemeralKey (); boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -961,19 +789,14 @@ namespace transport CreateNextReceivedBuffer (m_NextReceivedLen); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); - 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 (); - } + 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); + } else - LogPrint (eLogWarning, "NTCP2: Socket error: ", ec.message ()); + Receive (); } else { @@ -999,17 +822,18 @@ 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 { - UpdateNumReceivedBytes (bytes_transferred + 2); - i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumReceivedBytes += bytes_transferred + 2; // + length + i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); uint8_t nonce[12]; CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++; - if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen)) + if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false)) { LogPrint (eLogDebug, "NTCP2: Received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); @@ -1034,7 +858,7 @@ namespace transport auto size = bufbe16toh (frame + offset); offset += 2; LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size); - if (offset + size > len) + if (size > len) { LogPrint (eLogError, "NTCP2: Unexpected block length ", size); break; @@ -1042,39 +866,15 @@ namespace transport switch (blk) { case eNTCP2BlkDateTime: - { LogPrint (eLogDebug, "NTCP2: Datetime"); - if (m_IsEstablished) - { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - uint64_t tsA = bufbe32toh (frame + offset); - if (tsA < ts - NTCP2_CLOCK_SKEW || tsA > ts + NTCP2_CLOCK_SKEW) - { - LogPrint (eLogWarning, "NTCP2: Established session time difference ", (int)(ts - tsA), " exceeds clock skew"); - SendTerminationAndTerminate (eNTCP2ClockSkew); - } - } - break; - } + break; case eNTCP2BlkOptions: LogPrint (eLogDebug, "NTCP2: Options"); break; case eNTCP2BlkRouterInfo: { - 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); + LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); + i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size)); break; } case eNTCP2BlkI2NPMessage: @@ -1191,14 +991,9 @@ 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++; - m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers + i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block // send buffers @@ -1220,16 +1015,10 @@ 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++; - m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); + i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer); // send m_IsSending = true; @@ -1250,53 +1039,40 @@ namespace transport } else { - UpdateNumSentBytes (bytes_transferred); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumSentBytes += bytes_transferred; i2p::transport::transports.UpdateSentBytes (bytes_transferred); LogPrint (eLogDebug, "NTCP2: Next frame sent ", bytes_transferred); - if (GetLastActivityTimestamp () > m_NextRouterInfoResendTime) + if (m_LastActivityTimestamp > m_NextRouterInfoResendTime) { m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + - m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; SendRouterInfo (); } else - { SendQueue (); - SetSendQueueSize (m_SendQueue.size ()); - } } } void NTCP2Session::SendQueue () { - if (!m_SendQueue.empty () && m_IsEstablished) + if (!m_SendQueue.empty ()) { 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 @@ -1306,33 +1082,13 @@ 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) - { - int l = (int)NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; - if (l <= 0) return 0; - paddingSize = l; - } + if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; if (paddingSize > len) paddingSize = len; if (paddingSize) { @@ -1341,7 +1097,7 @@ namespace transport RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); m_NextPaddingSize = 0; } - paddingSize = m_PaddingSizes[m_NextPaddingSize++] % (paddingSize + 1); + paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; } buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size @@ -1352,19 +1108,13 @@ namespace transport void NTCP2Session::SendRouterInfo () { if (!IsEstablished ()) return; - 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 + auto riLen = i2p::context.GetRouterInfo ().GetBufferLen (); + size_t payloadLen = riLen + 4; // 3 bytes block header + 1 byte RI flag m_NextSendBuffer = new uint8_t[payloadLen + 16 + 2 + 64]; // up to 64 bytes padding - // DateTime block - m_NextSendBuffer[2] = eNTCP2BlkDateTime; - htobe16buf (m_NextSendBuffer + 3, 4); - htobe32buf (m_NextSendBuffer + 5, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - // RouterInfo block - m_NextSendBuffer[9] = eNTCP2BlkRouterInfo; - htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size - m_NextSendBuffer[12] = 0; // flag - memcpy (m_NextSendBuffer + 13, riBuffer->data (), riLen); // TODO: eliminate extra copy + m_NextSendBuffer[2] = eNTCP2BlkRouterInfo; + htobe16buf (m_NextSendBuffer + 3, riLen + 1); // size + m_NextSendBuffer[5] = 0; // flag + memcpy (m_NextSendBuffer + 6, i2p::context.GetRouterInfo ().GetBuffer (), riLen); // padding block auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); payloadLen += paddingSize; @@ -1396,60 +1146,20 @@ namespace transport void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason) { SendTermination (reason); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go + m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go } - void NTCP2Session::ReadSomethingAndTerminate () + void NTCP2Session::SendI2NPMessages (const std::vector >& 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 ())); + m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs)); } - void NTCP2Session::PostI2NPMessages () + void NTCP2Session::PostI2NPMessages (std::vector > msgs) { if (m_IsTerminated) return; - 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) + for (auto it: msgs) + m_SendQueue.push_back (it); + if (!m_IsSending) SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { @@ -1457,25 +1167,17 @@ namespace transport GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } - SetSendQueueSize (m_SendQueue.size ()); } - void NTCP2Session::SendLocalRouterInfo (bool update) + void NTCP2Session::SendLocalRouterInfo () { - if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); + if (!IsOutgoing ()) // we send it in SessionConfirmed + m_Server.GetService ().post (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_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) + m_ProxyType(eNoProxy), m_Resolver(GetService ()) { } @@ -1486,7 +1188,6 @@ namespace transport void NTCP2Server::Start () { - m_EstablisherService.Start (); if (!IsRunning ()) { StartIOService (); @@ -1494,13 +1195,14 @@ 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(m_ProxyAddress, std::to_string(m_ProxyPort), e); + auto itr = m_Resolver.resolve(q, e); if(e) - LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message()); + LogPrint(eLogError, "NTCP2: Failed to resolve proxy ", e.message()); else { - m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ())); + m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr)); if (m_ProxyEndpoint) LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint); } @@ -1508,9 +1210,8 @@ namespace transport else LogPrint(eLogInfo, "NTCP2: Proxy is not used"); // start acceptors - auto addresses = context.GetRouterInfo ().GetAddresses (); - if (!addresses) return; - for (const auto& address: *addresses) + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) { if (!address) continue; if (address->IsPublishedNTCP2 () && address->port) @@ -1525,7 +1226,7 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogCritical, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); + LogPrint(eLogError, "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; } @@ -1568,7 +1269,7 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogCritical, "NTCP2: Failed to bind to v6 port ", address->port, ": ", ex.what()); + LogPrint(eLogError, "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; } @@ -1581,14 +1282,13 @@ namespace transport void NTCP2Server::Stop () { - m_EstablisherService.Stop (); { // we have to copy it because Terminate changes m_NTCP2Sessions auto ntcpSessions = m_NTCP2Sessions; for (auto& it: ntcpSessions) it.second->Terminate (); for (auto& it: m_PendingIncomingSessions) - it.second->Terminate (); + it->Terminate (); } m_NTCP2Sessions.clear (); @@ -1604,44 +1304,27 @@ namespace transport { if (!session) return false; if (incoming) - m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ()); - if (!session->GetRemoteIdentity ()) - { - LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ()); - session->Terminate (); - return false; - } + m_PendingIncomingSessions.remove (session); + if (!session->GetRemoteIdentity ()) return false; auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); auto it = m_NTCP2Sessions.find (ident); if (it != m_NTCP2Sessions.end ()) { - LogPrint (eLogWarning, "NTCP2: Session with ", ident.ToBase64 (), " already exists. ", incoming ? "Replaced" : "Dropped"); + LogPrint (eLogWarning, "NTCP2: Session to ", ident.ToBase64 (), " already exists"); if (incoming) - { // replace by new session - auto s = it->second; - s->MoveSendQueue (session); - m_NTCP2Sessions.erase (it); - s->Terminate (); - } + it->second->Terminate (); else - { - session->Terminate (); return false; - } } - m_NTCP2Sessions.emplace (ident, session); + m_NTCP2Sessions.insert (std::make_pair (ident, session)); return true; } void NTCP2Server::RemoveNTCP2Session (std::shared_ptr session) { if (session && session->GetRemoteIdentity ()) - { - auto it = m_NTCP2Sessions.find (session->GetRemoteIdentity ()->GetIdentHash ()); - if (it != m_NTCP2Sessions.end () && it->second == session) - m_NTCP2Sessions.erase (it); - } + m_NTCP2Sessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); } std::shared_ptr NTCP2Server::FindNTCP2Session (const i2p::data::IdentHash& ident) @@ -1659,9 +1342,8 @@ namespace transport LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; } - LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); - boost::asio::post (GetService (), [this, conn]() + LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint ()); + GetService ().post([this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1716,47 +1398,33 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); + LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint ()); conn->ClientLogin (); } } void NTCP2Server::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error && conn) + if (!error) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (!i2p::transport::transports.IsInReservedRange(ep.address ())) + if (conn) { - if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) - { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - conn = nullptr; - } - else - LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + m_PendingIncomingSessions.push_back (conn); + conn = nullptr; } - else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } else - { LogPrint (eLogError, "NTCP2: Accept error ", error.message ()); - if (error == boost::asio::error::no_descriptors) - { - i2p::context.SetError (eRouterErrorNoDescriptors); - return; - } - } if (error != boost::asio::error::operation_aborted) { @@ -1771,47 +1439,27 @@ namespace transport void NTCP2Server::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error && conn) + if (!error) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (!i2p::transport::transports.IsInReservedRange(ep.address ()) || - i2p::util::net::IsYggdrasilAddress (ep.address ())) + if (conn) { - if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) - { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - conn = nullptr; - } - else - LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + m_PendingIncomingSessions.push_back (conn); } - else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } - else - { - LogPrint (eLogError, "NTCP2: Accept ipv6 error ", error.message ()); - if (error == boost::asio::error::no_descriptors) - { - i2p::context.SetErrorV6 (eRouterErrorNoDescriptors); - return; - } - } if (error != boost::asio::error::operation_aborted) { - if (!conn) // connection is used, create new one - conn = std::make_shared (*this); - else // reuse failed - conn->Close (); + conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); } @@ -1819,8 +1467,7 @@ namespace transport void NTCP2Server::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds( - NTCP2_TERMINATION_CHECK_TIMEOUT + m_Rng () % NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP2_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimer.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -1843,34 +1490,18 @@ namespace transport // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { - if (it->second->IsEstablished () || it->second->IsTerminationTimeoutExpired (ts)) + if ((*it)->IsEstablished () || (*it)->IsTerminationTimeoutExpired (ts)) { - it->second->Terminate (); + (*it)->Terminate (); it = m_PendingIncomingSessions.erase (it); // established of expired } - else if (it->second->IsTerminated ()) + else if ((*it)->IsTerminated ()) it = m_PendingIncomingSessions.erase (it); // already terminated else it++; } - ScheduleTermination (); - // try to restart acceptors if no description - // we do it after timer to let timer take descriptor first - if (i2p::context.GetError () == eRouterErrorNoDescriptors) - { - i2p::context.SetError (eRouterErrorNone); - auto conn = std::make_shared (*this); - m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, - conn, std::placeholders::_1)); - } - if (i2p::context.GetErrorV6 () == eRouterErrorNoDescriptors) - { - i2p::context.SetErrorV6 (eRouterErrorNone); - auto conn = std::make_shared (*this); - m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, - conn, std::placeholders::_1)); - } + ScheduleTermination (); } } @@ -1882,7 +1513,7 @@ namespace transport LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); return; } - boost::asio::post (GetService(), [this, conn]() + GetService().post([this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1927,18 +1558,47 @@ namespace transport case eSocksProxy: { // TODO: support username/password auth etc - Socks5Handshake (conn->GetSocket(), conn->GetRemoteEndpoint (), - [conn, timer](const boost::system::error_code& ec) - { - timer->cancel(); - if (!ec) - conn->ClientLogin(); - else + static const uint8_t buff[3] = {0x05, 0x01, 0x00}; + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), + [] (const boost::system::error_code & ec, std::size_t transferred) + { + (void) transferred; + if(ec) { - LogPrint(eLogError, "NTCP2: SOCKS proxy handshake error ", ec.message()); - conn->Terminate(); - } - }); + 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"); + timer->cancel(); + conn->Terminate(); + }); break; } case eHTTPProxy: @@ -1966,7 +1626,7 @@ namespace transport LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message()); }); - auto readbuff = std::make_shared(); + boost::asio::streambuf * readbuff = new boost::asio::streambuf; 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) { @@ -1980,12 +1640,13 @@ namespace transport { readbuff->commit(transferred); i2p::http::HTTPRes res; - if(res.parse(std::string {boost::asio::buffers_begin(readbuff->data ()), boost::asio::buffers_begin(readbuff->data ()) + readbuff->size ()}) > 0) + if(res.parse(boost::asio::buffer_cast(readbuff->data()), readbuff->size()) > 0) { if(res.code == 200) { timer->cancel(); conn->ClientLogin(); + delete readbuff; return; } else @@ -1995,6 +1656,7 @@ namespace transport LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response"); timer->cancel(); conn->Terminate(); + delete readbuff; } }); break; @@ -2004,6 +1666,69 @@ namespace transport } } + void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer) + { + // build request + size_t sz = 6; // header + port + auto buff = std::make_shared >(256); + auto readbuff = std::make_shared >(256); + (*buff)[0] = 0x05; + (*buff)[1] = 0x01; + (*buff)[2] = 0x00; + + auto& ep = conn->GetRemoteEndpoint (); + if(ep.address ().is_v4 ()) + { + (*buff)[3] = 0x01; + auto addrbytes = ep.address ().to_v4().to_bytes(); + sz += 4; + memcpy(buff->data () + 4, addrbytes.data(), 4); + } + else if (ep.address ().is_v6 ()) + { + (*buff)[3] = 0x04; + auto addrbytes = ep.address ().to_v6().to_bytes(); + sz += 16; + memcpy(buff->data () + 4, addrbytes.data(), 16); + } + else + { + // We mustn't really fall here because all connections are made to IP addresses + LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy"); + return; + } + htobe16buf(buff->data () + sz - 2, ep.port ()); + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), + [buff](const boost::system::error_code & ec, std::size_t written) + { + if(ec) + { + LogPrint(eLogError, "NTCP2: Failed to write handshake to socks proxy ", ec.message()); + return; + } + }); + + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), + [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) + { + if(e) + { + LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message()); + } + else if(transferred == sz) + { + if((*readbuff)[1] == 0x00) + { + timer->cancel(); + conn->ClientLogin(); + return; + } + } + timer->cancel(); + conn->Terminate(); + }); + } + void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) { auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); @@ -2017,17 +1742,5 @@ 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 b50d9087..46c47756 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -29,17 +28,14 @@ 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 = 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_TERMINATION_TIMEOUT = 120; // 2 minutes + const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 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 @@ -91,29 +87,30 @@ 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; }; - bool KDF1Alice (); - bool KDF1Bob (); - bool KDF2Alice (); - bool KDF2Bob (); - bool KDF3Alice (); // for SessionConfirmed part 2 - bool KDF3Bob (); + void KDF1Alice (); + void KDF1Bob (); + void KDF2Alice (); + void KDF2Bob (); + void KDF3Alice (); // for SessionConfirmed part 2 + void KDF3Bob (); - 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 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 void CreateEphemeralKey (); - bool CreateSessionRequestMessage (std::mt19937& rng); - bool CreateSessionCreatedMessage (std::mt19937& rng); - bool CreateSessionConfirmedMessagePart1 (); - bool CreateSessionConfirmedMessagePart2 (); + void CreateSessionRequestMessage (); + void CreateSessionCreatedMessage (); + void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce); + void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce); bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); bool ProcessSessionCreatedMessage (uint16_t& paddingLen); - bool ProcessSessionConfirmedMessagePart1 (); - bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf); + bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce); + bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf); std::shared_ptr m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 @@ -137,25 +134,23 @@ namespace transport ~NTCP2Session (); void Terminate (); void TerminateByTimeout (); - void Done () override; - void Close (); // for accept + void Done (); + void Close () { m_Socket.close (); }; // for accept void DeleteNextReceiveBuffer (uint64_t ts); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; - bool IsEstablished () const override { return m_IsEstablished; }; - i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; + bool IsEstablished () const { return m_IsEstablished; }; bool IsTerminated () const { return m_IsTerminated; }; void ClientLogin (); // Alice void ServerLogin (); // Bob - void SendLocalRouterInfo (bool update) override; // after handshake or by update - void SendI2NPMessages (std::list >& msgs) override; - void MoveSendQueue (std::shared_ptr other); - + void SendLocalRouterInfo (); // after handshake + void SendI2NPMessages (const std::vector >& msgs); + private: void Established (); @@ -172,17 +167,13 @@ 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); @@ -200,8 +191,7 @@ namespace transport void SendRouterInfo (); void SendTermination (NTCP2TerminationReason reason); void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void ReadSomethingAndTerminate (); - void PostI2NPMessages (); + void PostI2NPMessages (std::vector > msgs); private: @@ -234,28 +224,13 @@ 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 @@ -264,20 +239,13 @@ namespace transport eSocksProxy, eHTTPProxy }; - + NTCP2Server (); ~NTCP2Server (); void Start (); void Stop (); - 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); - + boost::asio::io_service& GetService () { return GetIOService (); }; bool AddNTCP2Session (std::shared_ptr session, bool incoming = false); void RemoveNTCP2Session (std::shared_ptr session); @@ -298,7 +266,8 @@ 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); @@ -308,20 +277,15 @@ namespace transport boost::asio::deadline_timer m_TerminationTimer; std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; std::map > m_NTCP2Sessions; - std::map > m_PendingIncomingSessions; + std::list > m_PendingIncomingSessions; ProxyType m_ProxyType; std::string m_ProxyAddress, m_ProxyAuthorization; uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; - std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; - std::mt19937 m_Rng; - EstablisherService m_EstablisherService; - i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; - i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; - + public: // for HTTP/I2PControl diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index cab40e43..ef36895a 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -37,9 +36,7 @@ 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_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), m_HiddenMode(false) { } @@ -57,49 +54,42 @@ 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.GetSize () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils + if (m_RouterInfos.size () < threshold || m_Floodfills.size () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils { Reseed (); } - else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false)) + else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), 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->GetIdentHash ()); + m_Floodfills.remove (it->second); m_RouterInfos.erase (it); } // insert own router m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); if (i2p::context.IsFloodfill ()) - m_Floodfills.Insert (i2p::context.GetSharedRouterInfo ()); + m_Floodfills.push_back (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 (); + for (auto& it: m_RouterInfos) + it.second->SaveProfile (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); - m_Floodfills.Clear (); + m_Floodfills.clear (); if (m_Thread) { m_IsRunning = false; @@ -109,109 +99,133 @@ namespace data m_Thread = 0; } m_LeaseSets.clear(); + m_Requests.Stop (); } - m_Requests = nullptr; } void NetDb::Run () { i2p::util::SetThreadName("NetDB"); - uint64_t lastManage = 0; - uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), - lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup; - int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0; + uint64_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; + uint64_t lastProfilesCleanup = i2p::util::GetSecondsSinceEpoch (); + int16_t profilesCleanupVariance = 0; - std::list > msgs; while (m_IsRunning) { try { - if (m_Queue.Wait (1,0)) // 1 sec + auto msg = m_Queue.GetNextWithTimeout (15000); // 15 sec + if (msg) { - m_Queue.GetWholeQueue (msgs); - while (!msgs.empty ()) + int numMsgs = 0; + while (msg) { - 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 () || !i2p::transport::transports.IsRunning ()) - continue; // don't manage netdb when offline or transports are not running + if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline - uint64_t mts = i2p::util::GetMonotonicMilliseconds (); - if (mts >= lastManage + 60000) // manage routers and leasesets every minute + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts - lastManageRequest >= 15) // manage requests every 15 seconds { - if (lastManage) + m_Requests.ManageRequests (); + lastManageRequest = ts; + } + + if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute + { + if (lastSave) { - ManageRouterInfos (); + SaveUpdated (); ManageLeaseSets (); } - lastManage = mts; + lastSave = ts; } - if (mts >= lastProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)*1000) + if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) { - 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; + i2p::context.CleanupDestination (); + lastDestinationCleanup = ts; } - if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) + if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)) { - 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) + 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 isApplying = m_ApplyingProfileUpdates.valid (); - if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + bool publish = false; + if (m_PublishReplyToken) { - 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; - } + // next publishing attempt + if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; + } + else if (i2p::context.GetLastUpdateTime () > lastPublish || + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) + { + // new publish + m_PublishExcluded.clear (); + if (i2p::context.IsFloodfill ()) + m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // do publish to ourselves + publish = true; + } + if (publish) // update timestamp and publish + { + i2p::context.UpdateTimestamp (ts); + Publish (); + lastPublish = ts; + } + } + + if (ts - lastExploratory >= 30) // exploratory every 30 seconds + { + auto numRouters = m_RouterInfos.size (); + if (!numRouters) + throw std::runtime_error("No known routers, reseed seems to be totally failed"); + else // we have peers now + m_FloodfillBootstrap = nullptr; + if (numRouters < 2500 || ts - lastExploratory >= 90) + { + numRouters = 800/numRouters; + if (numRouters < 1) numRouters = 1; + if (numRouters > 9) numRouters = 9; + m_Requests.ManageRequests (); + if(!m_HiddenMode) + Explore (numRouters); + lastExploratory = ts; + } + } } catch (std::exception& ex) { @@ -220,10 +234,17 @@ namespace data } } - std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len) + void NetDb::SetHidden(bool hide) + { + // TODO: remove reachable addresses from router info + m_HiddenMode = hide; + } + + bool NetDb::AddRouterInfo (const uint8_t * buf, int len) { bool updated; - return AddRouterInfo (buf, len, updated); + AddRouterInfo (buf, len, updated); + return updated; } std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len, bool& updated) @@ -238,8 +259,7 @@ namespace data bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) { bool updated; - if (!AddRouterInfo (ident, buf, len, updated)) - updated = false; + AddRouterInfo (ident, buf, len, updated); return updated; } @@ -252,86 +272,41 @@ namespace data if (r->IsNewer (buf, len)) { bool wasFloodfill = r->IsFloodfill (); - { - std::lock_guard l(m_RouterInfosMutex); - if (!r->Update (buf, len)) - { - updated = false; - m_Requests->RequestComplete (ident, r); - return r; - } - if (r->IsUnreachable () || - i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ()) - { - // delete router as invalid or from future after update - m_RouterInfos.erase (ident); - if (wasFloodfill) - { - std::lock_guard l(m_FloodfillsMutex); - m_Floodfills.Remove (r->GetIdentHash ()); - } - m_Requests->RequestComplete (ident, nullptr); - return nullptr; - } - } - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + r->Update (buf, len); + LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); - std::lock_guard l(m_FloodfillsMutex); + LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); + std::unique_lock l(m_FloodfillsMutex); if (wasFloodfill) - m_Floodfills.Remove (r->GetIdentHash ()); + m_Floodfills.remove (r); else if (r->IsEligibleFloodfill ()) - { - if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || r->GetProfile ()->IsReal ()) - m_Floodfills.Insert (r); - else - r->ResetFloodfill (); - } + m_Floodfills.push_back (r); } } else { - r->CancelBufferToDelete (); // since an update received - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); + LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); updated = false; } } else { r = std::make_shared (buf, len); - 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) + if (!r->IsUnreachable () && r->HasValidAddresses ()) { bool inserted = false; { - std::lock_guard l(m_RouterInfosMutex); + std::unique_lock l(m_RouterInfosMutex); inserted = m_RouterInfos.insert ({r->GetIdentHash (), r}).second; } if (inserted) { - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); if (r->IsFloodfill () && r->IsEligibleFloodfill ()) { - 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 (); + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.push_back (r); } } else @@ -344,13 +319,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::lock_guard lock(m_LeaseSetsMutex); + std::unique_lock 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) @@ -361,12 +336,11 @@ namespace data { if(it->second->GetExpirationTime() < expires) { - it->second->Update (buf, len, nullptr, false); // signature is verified already - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + it->second->Update (buf, len, false); // signature is verified already + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); updated = true; } - else if (CheckLogLevel (eLogDebug)) + else LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); } else @@ -377,8 +351,7 @@ namespace data auto leaseSet = std::make_shared (buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; updated = true; } @@ -390,10 +363,10 @@ namespace data bool NetDb::AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType) { + std::unique_lock lock(m_LeaseSetsMutex); auto leaseSet = std::make_shared (storeType, buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { - std::lock_guard lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find(ident); if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) @@ -401,14 +374,13 @@ namespace data if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) { // TODO: implement actual update - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; return true; } else { - LogPrint (eLogWarning, "NetDb: Unpublished or expired or future LeaseSet2 received: ", ident.ToBase32()); + LogPrint (eLogWarning, "NetDb: Unpublished or expired LeaseSet2 received: ", ident.ToBase32()); m_LeaseSets.erase (ident); } } @@ -420,7 +392,7 @@ namespace data std::shared_ptr NetDb::FindRouter (const IdentHash& ident) const { - std::lock_guard l(m_RouterInfosMutex); + std::unique_lock l(m_RouterInfosMutex); auto it = m_RouterInfos.find (ident); if (it != m_RouterInfos.end ()) return it->second; @@ -430,7 +402,7 @@ namespace data std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { - std::lock_guard lock(m_LeaseSetsMutex); + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) return it->second; @@ -449,34 +421,9 @@ namespace data void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable) { - auto r = FindRouter (ident); - if (r) - { - r->SetUnreachable (unreachable); - auto profile = r->GetProfile (); - if (profile) - { - profile->Unreachable (unreachable); - if (!unreachable && r->IsDeclaredFloodfill () && !r->IsFloodfill () && - r->IsEligibleFloodfill () && profile->IsReal ()) - { - // enable previously disabled floodfill - r->SetFloodfill (); - std::lock_guard l(m_FloodfillsMutex); - m_Floodfills.Insert (r); - } - } - } - } - - void NetDb::ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports) - { - auto r = FindRouter (ident); - if (r) - { - std::lock_guard l(m_RouterInfosMutex); - r->ExcludeReachableTransports (transports); - } + auto it = m_RouterInfos.find (ident); + if (it != m_RouterInfos.end ()) + return it->second->SetUnreachable (unreachable); } void NetDb::Reseed () @@ -487,13 +434,32 @@ 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::list > requests; + std::vector > requests; i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); i2p::data::IdentHash ih = ri.GetIdentHash(); @@ -516,20 +482,20 @@ namespace data } // send them off - i2p::transport::transports.SendMessages(ih, std::move (requests)); + i2p::transport::transports.SendMessages(ih, requests); } bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts) { auto r = std::make_shared(path); if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses () && - ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) // too old + ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) { r->DeleteBuffer (); if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) { if (r->IsFloodfill () && r->IsEligibleFloodfill ()) - m_Floodfills.Insert (r); + m_Floodfills.push_back (r); } } else @@ -542,7 +508,7 @@ namespace data void NetDb::VisitLeaseSets(LeaseSetVisitor v) { - std::lock_guard lock(m_LeaseSetsMutex); + std::unique_lock lock(m_LeaseSetsMutex); for ( auto & entry : m_LeaseSets) v(entry.first, entry.second); } @@ -558,7 +524,7 @@ namespace data void NetDb::VisitRouterInfos(RouterInfoVisitor v) { - std::lock_guard lock(m_RouterInfosMutex); + std::unique_lock lock(m_RouterInfosMutex); for ( const auto & item : m_RouterInfos ) v(item.second); } @@ -570,8 +536,8 @@ namespace data size_t iters = max_iters_per_cyle; while(n > 0) { - std::lock_guard lock(m_RouterInfosMutex); - uint32_t idx = m_Rng () % m_RouterInfos.size (); + std::unique_lock lock(m_RouterInfosMutex); + uint32_t idx = rand () % m_RouterInfos.size (); uint32_t i = 0; for (const auto & it : m_RouterInfos) { if(i >= idx) // are we at the random start point? @@ -613,7 +579,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; @@ -621,125 +587,62 @@ namespace data for (const auto& path : files) LoadRouterInfo (path, ts); - LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.GetSize (), " floodfils)"); + LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " 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.GetSize (); + auto totalFloodfills = m_Floodfills.size (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); auto uptime = i2p::context.GetUptime (); - double minTunnelCreationSuccessRate; - i2p::config::GetOption("limits.zombies", minTunnelCreationSuccessRate); - bool isLowRate = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < minTunnelCreationSuccessRate; // routers don't expire if less than 90 or uptime is less than 1 hour - bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // 10 minutes - if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour + bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes + if (checkForExpiration && uptime > 3600) // 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 [ident, r]: m_RouterInfos) + for (auto& it: m_RouterInfos) { - if (!r || r == own) continue; // skip own - if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete + if (it.second == own) continue; // skip own + std::string ident = it.second->GetIdentHashBase64(); + if (it.second->IsUpdated ()) { - std::lock_guard l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update - r->DeleteBuffer (); - } - if (r->IsUpdated ()) - { - if (r->GetBuffer () && !r->IsUnreachable ()) - { - // we have something to save - std::shared_ptr buffer; - { - std::lock_guard l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update - buffer = r->CopyBuffer (); - } - if (!i2p::transport::transports.IsConnected (ident)) - r->ScheduleBufferToDelete (); - if (buffer) - saveToDisk.emplace_back(ident.ToBase64 (), buffer); - } - r->SetUpdated (false); + it.second->SaveToFile (m_Storage.Path(ident)); + it.second->SetUpdated (false); + it.second->SetUnreachable (false); + it.second->DeleteBuffer (); updatedCount++; continue; } - else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) - // since update was long time ago we assume that router is not connected anymore - r->ScheduleBufferToDelete (); - - if (r->HasProfile () && r->GetProfile ()->IsUnreachable ()) - r->SetUnreachable (true); // make router reachable back if too few routers or floodfills - if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline || - (r->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) - r->SetUnreachable (false); - if (!r->IsUnreachable ()) + if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || + (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) + it.second->SetUnreachable (false); + // find & mark expired routers + if (!it.second->IsReachable () && it.second->IsSSU (false)) { - // 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); - } + if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) + // RouterInfo expires after 1 hour if uses introducer + it.second->SetUnreachable (true); } - // 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 ()) + else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) + it.second->SetUnreachable (true); + + if (it.second->IsUnreachable ()) { - if (r->IsFloodfill ()) deletedFloodfillsCount++; + if (it.second->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file - removeFromDisk.emplace_back (ident.ToBase64()); + m_Storage.Remove(ident); 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"); @@ -748,46 +651,80 @@ namespace data LogPrint (eLogInfo, "NetDb: Deleting ", deletedCount, " unreachable routers"); // clean up RouterInfos table { - std::lock_guard l(m_RouterInfosMutex); + std::unique_lock l(m_RouterInfosMutex); for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { - if (!it->second || it->second->IsUnreachable ()) - it = m_RouterInfos.erase (it); - else + if (it->second->IsUnreachable ()) { - it->second->DropProfile (); - it++; + if (m_PersistProfiles) it->second->SaveProfile (); + it = m_RouterInfos.erase (it); + continue; } + ++it; } } // clean up expired floodfills or not floodfills anymore { - std::lock_guard l(m_FloodfillsMutex); - m_Floodfills.Cleanup ([](const std::shared_ptr& r)->bool - { - return r && r->IsFloodfill () && !r->IsUnreachable (); - }); + 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; } } } - 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) { - 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); + 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); + } + } + } else - LogPrint (eLogError, "NetDb: Requests is null"); + { + 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)); } void NetDb::HandleNTCP2RouterInfoMsg (std::shared_ptr m) @@ -806,11 +743,6 @@ namespace data { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); - if (len < DATABASE_STORE_HEADER_SIZE) - { - LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped"); - return; - } IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); if (ident.IsZero ()) { @@ -821,41 +753,19 @@ namespace data size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { - if (len < offset + 36) // 32 + 4 - { - 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 (replyToken != 0xFFFFFFFFU) // if not caught on OBEP or IBGW + if (!tunnelID) // send response directly + transports.SendMessage (buf + offset, deliveryStatus); + else { - IdentHash replyIdent(buf + offset); - auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); - if (!tunnelID) // send response directly - transports.SendMessage (replyIdent, deliveryStatus); + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); 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) // 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"); - } - } + LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found"); } offset += 32; } @@ -871,36 +781,23 @@ namespace data uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET]; if (storeType) // LeaseSet or LeaseSet2 { - if (len > MAX_LS_BUFFER_SIZE + offset) - { - LogPrint (eLogError, "NetDb: Database store message is too long ", len); - return; - } - if (!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 (!m->from) // unsolicited LS must be received directly { if (storeType == NETDB_STORE_TYPE_LEASESET) // 1 { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); updated = AddLeaseSet (ident, buf + offset, len - offset); } else // all others are considered as LeaseSet2 { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32()); + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", storeType, " for ", ident.ToBase32()); updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType); } } } else // RouterInfo { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: Store request: RouterInfo ", ident.ToBase64()); + LogPrint (eLogDebug, "NetDb: Store request: RouterInfo"); size_t size = bufbe16toh (buf + offset); offset += 2; if (size > MAX_RI_BUFFER_SIZE || size > len - offset) @@ -932,16 +829,89 @@ namespace data { memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + payloadOffset, msgLen); floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); - 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); + Flood (ident, floodMsg); } 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 (); @@ -951,13 +921,14 @@ namespace data LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored"); return; } - std::string key; - if (CheckLogLevel (eLogInfo)) - key = i2p::data::ByteStreamToBase64 (buf, 32); + char key[48]; + int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); + key[l] = 0; 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; @@ -969,42 +940,46 @@ namespace data } uint16_t numExcluded = bufbe16toh (excluded); excluded += 2; - if (numExcluded > 512 || (excluded - buf) + numExcluded*32 > (int)msg->GetPayloadLength ()) + if (numExcluded > 512) { - LogPrint (eLogWarning, "NetDb: Number of excluded peers", numExcluded, " is too much"); + LogPrint (eLogWarning, "NetDb: Number of excluded peers", numExcluded, " exceeds 512"); return; } 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::unordered_set excludedRouters; - const uint8_t * excluded_ident = excluded; + std::set excludedRouters; for (int i = 0; i < numExcluded; i++) { - excludedRouters.insert (excluded_ident); - excluded_ident += 32; + excludedRouters.insert (excluded); + excluded += 32; } - replyMsg = CreateDatabaseSearchReply (ident, GetExploratoryNonFloodfill (ident, - NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES, excludedRouters)); + 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); } 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 ()) + if (router) { LogPrint (eLogDebug, "NetDb: Requested RouterInfo ", key, " found"); - if (PopulateRouterInfoBuffer (router)) + if (!router->GetBuffer ()) + router->LoadBuffer (m_Storage.Path (router->GetIdentHashBase64 ())); + if (router->GetBuffer ()) replyMsg = CreateDatabaseStoreMsg (router); } } @@ -1012,42 +987,33 @@ namespace data if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { - // 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) + auto leaseSet = FindLeaseSet (ident); + if (!leaseSet) { - LogPrint (eLogWarning, "NetDb: Explicit LeaseSet lookup to non-floodfill dropped"); - return; - } + // 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); + } } if (!replyMsg) { - std::unordered_set excludedRouters; + std::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, false); + auto closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); if (closestFloodfills.empty ()) - LogPrint (eLogWarning, "NetDb: No more floodfills for ", key, " found. ", numExcluded, " peers excluded"); + LogPrint (eLogWarning, "NetDb: Requested ", key, " not found, ", numExcluded, " peers excluded"); replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); - } + } } excluded += numExcluded * 32; if (replyMsg) @@ -1079,67 +1045,131 @@ namespace data else LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); } - 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)); + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsg (replyIdent, 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"); - } + transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); } else transports.SendMessage (replyIdent, replyMsg); } } - void NetDb::Flood (const IdentHash& ident, std::shared_ptr floodMsg, bool andNextDay) + void NetDb::HandleDeliveryStatusMsg (std::shared_ptr msg) { - std::unordered_set excluded; + 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; 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, false); // current day + auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) { - const auto& h = floodfill->GetIdentHash(); + auto h = floodfill->GetIdentHash(); + LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64()); transports.SendMessage (h, CopyI2NPMessage(floodMsg)); excluded.insert (h); } else - return; // no more floodfills + break; } - 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 @@ -1151,60 +1181,58 @@ namespace data }); } - std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, - bool reverse, bool endpoint, bool clientTunnel) const + std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, bool reverse) const { - bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate - context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime return GetRandomRouter ( - [compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr router)->bool + [compatibleWith, reverse](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)): - router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && - router->IsECIES () && !router->IsHighCongestion (clientTunnel) && - (!checkIsReal || router->GetProfile ()->IsReal ()) && - (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) + (reverse ? compatibleWith->IsReachableFrom (*router) : + router->IsReachableFrom (*compatibleWith)) && + router->IsECIES (); }); } - std::shared_ptr NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set& excluded) const + std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4, const std::set& excluded) const { return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { return !router->IsHidden () && router->IsECIES () && - router->IsSSU2PeerTesting (v4) && !excluded.count (router->GetIdentHash ()); + router->IsPeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } - std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::unordered_set& excluded) const + std::shared_ptr NetDb::GetRandomSSUV6Router () const + { + return GetRandomRouter ( + [](std::shared_ptr router)->bool + { + return !router->IsHidden () && router->IsECIES () && router->IsSSUV6 (); + }); + } + + std::shared_ptr NetDb::GetRandomIntroducer (bool v4, const std::set& excluded) const { return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsSSU2Introducer (v4) && - !excluded.count (router->GetIdentHash ()); + return !router->IsHidden () && router->IsECIES () && !router->IsFloodfill () && // floodfills don't send relay tag + router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()); }); } - std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, - bool reverse, bool endpoint) const + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) 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, endpoint, checkIsReal](std::shared_ptr router)->bool + [compatibleWith, reverse](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)) : - router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && + (reverse ? compatibleWith->IsReachableFrom (*router) : + router->IsReachableFrom (*compatibleWith)) && (router->GetCaps () & RouterInfo::eHighBandwidth) && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && - 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) - + router->IsECIES (); }); } @@ -1212,13 +1240,11 @@ namespace data std::shared_ptr NetDb::GetRandomRouter (Filter filter) const { if (m_RouterInfos.empty()) - return nullptr; + return 0; uint16_t inds[3]; RAND_bytes ((uint8_t *)inds, sizeof (inds)); - std::lock_guard l(m_RouterInfosMutex); - auto count = m_RouterInfos.size (); - if(count == 0) return nullptr; - inds[0] %= count; + std::unique_lock l(m_RouterInfosMutex); + inds[0] %= m_RouterInfos.size (); auto it = m_RouterInfos.begin (); std::advance (it, inds[0]); // try random router @@ -1273,46 +1299,80 @@ namespace data if (msg) m_Queue.Put (msg); } - void NetDb::PostDatabaseSearchReplyMsg (std::shared_ptr msg) - { - if (msg && m_Requests) - m_Requests->PostDatabaseSearchReplyMsg (msg); - } - std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, - const std::unordered_set& excluded, bool nextDay) const + const std::set& excluded, bool closeThanUsOnly) const { - IdentHash destKey = CreateRoutingKey (destination, nextDay); - std::lock_guard l(m_FloodfillsMutex); - return m_Floodfills.FindClosest (destKey, [&excluded](const std::shared_ptr& r)->bool + 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 ()) { - return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () && - !excluded.count (r->GetIdentHash ()); - }); + XORMetric m = destKey ^ it->GetIdentHash (); + if (m < minMetric && !excluded.count (it->GetIdentHash ())) + { + minMetric = m; + r = it; + } + } + } + return r; } std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num, - std::unordered_set& excluded, bool closeThanUsOnly) const + std::set& excluded, bool closeThanUsOnly) const { - std::vector res; - IdentHash destKey = CreateRoutingKey (destination); - std::vector > v; + struct Sorted { - 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; + std::shared_ptr r; + XORMetric metric; + bool operator< (const Sorted& other) const { return metric < other.metric; }; + }; + std::set sorted; + IdentHash destKey = CreateRoutingKey (destination); XORMetric ourMetric; if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); - for (auto& it: v) { - if (closeThanUsOnly && ourMetric < (destKey ^ it->GetIdentHash ())) break; - res.push_back (it->GetIdentHash ()); + 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; } return res; } @@ -1326,61 +1386,27 @@ namespace data }); } - std::vector NetDb::GetExploratoryNonFloodfill (const IdentHash& destination, - size_t num, const std::unordered_set& excluded) + std::shared_ptr NetDb::GetClosestNonFloodfill (const IdentHash& destination, + const std::set& excluded) const { - 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) - { - // update selection - m_ExploratorySelection.clear (); - std::vector > eligible; - eligible.reserve (m_RouterInfos.size ()); - { - // 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 + std::shared_ptr r; + XORMetric minMetric; 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) + minMetric.SetMax (); + // must be called from NetDb thread only + for (const auto& it: m_RouterInfos) { - 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); + if (!it.second->IsFloodfill ()) + { + XORMetric m = destKey ^ it.first; + if (m < minMetric && !excluded.count (it.first)) + { + minMetric = m; + r = it.second; + } + } } - SaveUpdated (); + return r; } void NetDb::ManageLeaseSets () @@ -1396,14 +1422,6 @@ namespace data else ++it; } - m_LeasesPool.CleanUpMt (); - } - - bool NetDb::PopulateRouterInfoBuffer (std::shared_ptr r) - { - if (!r) return false; - if (r->GetBuffer ()) return true; - return r->LoadBuffer (m_Storage.Path (r->GetIdentHashBase64 ())); } } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index df831be8..ad88274a 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,13 +10,12 @@ #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" @@ -32,7 +31,6 @@ #include "Family.h" #include "version.h" #include "util.h" -#include "KadDHT.h" namespace i2p { @@ -40,25 +38,17 @@ namespace data { const int NETDB_MIN_ROUTERS = 90; const int NETDB_MIN_FLOODFILLS = 5; - const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline - const int NETDB_NUM_FLOODFILLS_THRESHOLD = 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_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_PUBLISH_INTERVAL = 60 * 40; + const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds + const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 const int NETDB_MIN_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; @@ -79,7 +69,7 @@ namespace data void Start (); void Stop (); - std::shared_ptr AddRouterInfo (const uint8_t * buf, int len); + bool AddRouterInfo (const uint8_t * buf, int len); bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len); bool AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType); @@ -88,29 +78,38 @@ 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, 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::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; + std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const; + std::shared_ptr GetRandomPeerTestRouter (bool v4, const std::set& excluded) const; + std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later + std::shared_ptr GetRandomIntroducer (bool v4, const std::set& excluded) const; + std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, - std::unordered_set& excluded, bool closeThanUsOnly = false) const; - std::vector GetExploratoryNonFloodfill (const IdentHash& destination, size_t num, const std::unordered_set& excluded); + std::set& excluded, bool closeThanUsOnly = false) const; + std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; 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); - void PostDatabaseSearchReplyMsg (std::shared_ptr msg); // to NetdbReq thread + + /** set hidden mode, aka don't publish our RI to netdb and don't explore */ + void SetHidden(bool hide); void Reseed (); Families& GetFamilies () { return m_Families; }; // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; - int GetNumFloodfills () const { return m_Floodfills.GetSize (); }; + int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; /** visit all lease sets we currently store */ @@ -123,34 +122,19 @@ namespace data size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n); void ClearRouterInfos () { m_RouterInfos.clear (); }; - template - std::shared_ptr NewRouterInfoBuffer (TArgs&&... args) - { - return m_RouterInfoBuffersPool.AcquireSharedMt (std::forward(args)...); - } - bool PopulateRouterInfoBuffer (std::shared_ptr r); - std::shared_ptr NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); }; - RouterInfo::AddressesPtr NewRouterInfoAddresses () - { - return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (), - std::bind ::*)(RouterInfo::Addresses *)> - (&i2p::util::MemoryPoolMt::ReleaseMt, - &m_RouterInfoAddressVectorsPool, std::placeholders::_1)}; - }; - std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; - std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); }; - std::shared_ptr NewRouterProfile () { return m_RouterProfilesPool.AcquireSharedMt (); }; + std::shared_ptr NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); }; + + uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; private: void Load (); bool LoadRouterInfo (const std::string& path, uint64_t ts); void SaveUpdated (); - 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 Run (); // exploratory thread + void Explore (int numDestinations); + void Publish (); + void Flood (const IdentHash& ident, std::shared_ptr floodMsg); void ManageLeaseSets (); void ManageRequests (); @@ -162,10 +146,6 @@ 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; @@ -173,7 +153,7 @@ namespace data mutable std::mutex m_RouterInfosMutex; std::unordered_map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; - DHTTable m_Floodfills; + std::list > m_Floodfills; bool m_IsRunning; std::thread * m_Thread; @@ -184,21 +164,21 @@ namespace data Families m_Families; i2p::fs::HashedStorage m_Storage; - std::shared_ptr m_Requests; + friend class NetDbRequests; + NetDbRequests m_Requests; bool m_PersistProfiles; - std::future m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters; - std::vector > m_ExploratorySelection; - uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds - std::mt19937 m_Rng; + /** 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; 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 94633e10..e7aab34c 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,30 +10,12 @@ #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) { @@ -46,9 +28,7 @@ namespace data msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); if(router) m_ExcludedPeers.insert (router->GetIdentHash ()); - m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); - m_NumAttempts++; - m_IsSentDirectly = false; + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); return msg; } @@ -57,155 +37,80 @@ namespace data auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); - m_NumAttempts++; - m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); - m_IsSentDirectly = true; + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); 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_IsActive) - { - m_IsActive = false; - InvokeRequestComplete (r); - } + if (m_RequestComplete) + { + m_RequestComplete (r); + m_RequestComplete = nullptr; + } } void RequestedDestination::Fail () { - if (m_IsActive) - { - m_IsActive = false; - InvokeRequestComplete (nullptr); - } + if (m_RequestComplete) + { + m_RequestComplete (nullptr); + m_RequestComplete = 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 () { - if (IsRunning ()) - { - m_ManageRequestsTimer.cancel (); - m_ExploratoryTimer.cancel (); - m_CleanupTimer.cancel (); - StopIOService (); - - m_RequestedDestinations.clear (); - m_RequestedDestinationsPool.CleanUpMt (); - } + m_RequestedDestinations.clear (); } - 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) + + std::shared_ptr NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete) { // request RouterInfo directly - 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; - } + 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; + } return dest; } void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr r) { - 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 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 (); + } } 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; @@ -214,349 +119,50 @@ namespace data void NetDbRequests::ManageRequests () { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + std::unique_lock l(m_RequestedDestinationsMutex); for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) { auto& dest = it->second; - if (dest->IsActive () || ts < dest->GetCreationTime () + REQUEST_CACHE_TIME) - { - if (!dest->IsExploratory ()) - { - // regular request - bool done = false; - if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME) - { - if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) - // try next floodfill if no response after min interval - done = !SendNextRequest (dest); - } - else // request is expired - done = true; - 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 = 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) + bool done = false; + if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute + { + if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds { - 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 count = dest->GetExcludedPeers ().size (); + if (!dest->IsExploratory () && count < 7) + { + 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) - { - 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 ())); - } + outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, + dest->CreateRequestMessage (nextFloodfill, inbound)); else { - ret = false; + 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"); } - } + } 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 - }); + if (!dest->IsExploratory ()) + LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); + done = true; + } } - else - i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); } + else // delete obsolete request + done = true; + + if (done) + it = m_RequestedDestinations.erase (it); else - RequestComplete (randomHash, nullptr); + ++it; } - 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 53af2c6a..cf2f0915 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,121 +9,66 @@ #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, bool direct = true); - ~RequestedDestination (); + RequestedDestination (const IdentHash& destination, bool isExploratory = false): + m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {}; + ~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); }; const IdentHash& GetDestination () const { return m_Destination; }; - const std::unordered_set& GetExcludedPeers () const { return m_ExcludedPeers; }; - int GetNumAttempts () const { return m_NumAttempts; }; + int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; + const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; void ClearExcludedPeers (); bool IsExploratory () const { return m_IsExploratory; }; - bool IsDirect () const { return m_IsDirect; }; - bool IsActive () const { return m_IsActive; }; - bool IsSentDirectly () const { return m_IsSentDirectly; }; - bool IsExcluded (const IdentHash& ident) const; + bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; 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 AddRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete.push_back (requestComplete); }; - void ResetRequestComplete () { m_RequestComplete.clear (); }; + void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; + bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; void Success (std::shared_ptr r); void Fail (); - private: - - void InvokeRequestComplete (std::shared_ptr r); - private: IdentHash m_Destination; - 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; + bool m_IsExploratory; + std::set m_ExcludedPeers; + uint64_t m_CreationTime; + RequestComplete m_RequestComplete; }; - class NetDbRequests: public std::enable_shared_from_this, - private i2p::util::RunnableServiceWithWork + class NetDbRequests { 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); - 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: - 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; + mutable std::mutex m_RequestedDestinationsMutex; + std::map > m_RequestedDestinations; }; } } diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp new file mode 100644 index 00000000..20b3ab2a --- /dev/null +++ b/libi2pd/Poly1305.cpp @@ -0,0 +1,25 @@ +/** + * 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 new file mode 100644 index 00000000..db659b84 --- /dev/null +++ b/libi2pd/Poly1305.h @@ -0,0 +1,261 @@ +/** + * 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 deleted file mode 100644 index fa268828..00000000 --- a/libi2pd/PostQuantum.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* -* 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 deleted file mode 100644 index f426d661..00000000 --- a/libi2pd/PostQuantum.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -* 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 fe7f9905..55b95831 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,42 +7,34 @@ */ #include -#include -#include -#include -#include #include #include #include "Base.h" #include "FS.h" #include "Log.h" -#include "Timestamp.h" -#include "NetDb.hpp" #include "Profiling.h" namespace i2p { namespace data { - static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); - static std::unordered_map > g_Profiles; - static std::mutex g_ProfilesMutex; - static std::list)> > > g_PostponedUpdates; - static std::mutex g_PostponedUpdatesMutex; - + i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + RouterProfile::RouterProfile (): - 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) + m_LastUpdateTime (boost::posix_time::second_clock::local_time()), + m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), + m_NumTimesTaken (0), m_NumTimesRejected (0) { } + boost::posix_time::ptime RouterProfile::GetTime () const + { + return boost::posix_time::second_clock::local_time(); + } + void RouterProfile::UpdateTime () { - m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); - m_IsUpdated = true; + m_LastUpdateTime = GetTime (); } void RouterProfile::Save (const IdentHash& identHash) @@ -55,20 +47,15 @@ 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_TIMESTAMP, m_LastUpdateTime); - if (m_LastUnreachableTime) - pt.put (PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime); + pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); // save to file std::string ident = identHash.ToBase64 (); - std::string path = g_ProfilesStorage.Path(ident); + std::string path = m_ProfilesStorage.Path(ident); try { boost::property_tree::write_ini (path, pt); @@ -80,9 +67,8 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { - m_IsUpdated = false; std::string ident = identHash.ToBase64 (); - std::string path = g_ProfilesStorage.Path(ident); + std::string path = m_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; if (!i2p::fs::Exists(path)) @@ -103,24 +89,11 @@ namespace data try { - 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) + 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) { - m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0); try { // read participations @@ -139,8 +112,6 @@ 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) { @@ -160,44 +131,17 @@ namespace data { UpdateTime (); if (ret > 0) - { m_NumTunnelsDeclined++; - m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); - } else - { - m_NumTunnelsAgreed++; - m_LastDeclineTime = 0; - } + m_NumTunnelsAgreed++; } void RouterProfile::TunnelNonReplied () { - m_NumTunnelsNonReplied++; - UpdateTime (); - if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3) - { - m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); - } - } - - void RouterProfile::Unreachable (bool unreachable) - { - m_LastUnreachableTime = unreachable ? i2p::util::GetSecondsSinceEpoch () : 0; - UpdateTime (); - } - - void RouterProfile::Connected () - { - m_HasConnected = true; + m_NumTunnelsNonReplied++; UpdateTime (); } - void RouterProfile::Duplicated () - { - m_IsDuplicated = true; - } - bool RouterProfile::IsLowPartcipationRate () const { return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate @@ -209,21 +153,8 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } - bool RouterProfile::IsDeclinedRecently (uint64_t ts) - { - if (!m_LastDeclineTime) return false; - if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL || - ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime) - m_LastDeclineTime = 0; - return (bool)m_LastDeclineTime; - } - bool RouterProfile::IsBad () { - if (IsUnreachable () || m_IsDuplicated) return true; - auto ts = i2p::util::GetSecondsSinceEpoch (); - if (ts > PEER_PROFILE_MAX_DECLINED_INTERVAL + m_LastDeclineTime) return false; - if (IsDeclinedRecently (ts)) return true; auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { @@ -237,186 +168,36 @@ namespace data return isBad; } - bool RouterProfile::IsUnreachable () - { - if (!m_LastUnreachableTime) return false; - auto ts = i2p::util::GetSecondsSinceEpoch (); - if (ts > m_LastUnreachableTime + PEER_PROFILE_UNREACHABLE_INTERVAL || - ts + PEER_PROFILE_UNREACHABLE_INTERVAL < m_LastUnreachableTime) - m_LastUnreachableTime = 0; - return (bool)m_LastUnreachableTime; - } - - bool RouterProfile::IsUseful() const - { - return IsReal () || m_NumTunnelsNonReplied >= PEER_PROFILE_USEFUL_THRESHOLD; - } - std::shared_ptr GetRouterProfile (const IdentHash& identHash) { - { - std::unique_lock l(g_ProfilesMutex); - auto it = g_Profiles.find (identHash); - if (it != g_Profiles.end ()) - { - it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ()); - return it->second; - } - } - auto profile = netdb.NewRouterProfile (); + auto profile = std::make_shared (); profile->Load (identHash); // if possible - std::lock_guard l(g_ProfilesMutex); - g_Profiles.emplace (identHash, profile); return profile; } - bool IsRouterBanned (const IdentHash& identHash) - { - std::lock_guard l(g_ProfilesMutex); - auto it = g_Profiles.find (identHash); - if (it != g_Profiles.end ()) - return it->second->IsUnreachable (); - return false; - } - - bool IsRouterDuplicated (const IdentHash& identHash) - { - std::lock_guard l(g_ProfilesMutex); - auto it = g_Profiles.find (identHash); - if (it != g_Profiles.end ()) - return it->second->IsDuplicated (); - return false; - } - void InitProfilesStorage () { - g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); - g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); - } - - static void SaveProfilesToDisk (std::list > >&& profiles) - { - for (auto& it: profiles) - if (it.second) it.second->Save (it.first); - } - - std::future PersistProfiles () - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::list > > tmp; - { - std::lock_guard l(g_ProfilesMutex); - for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) - { - if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL) - { - tmp.push_back (*it); - it->second->SetLastPersistTime (ts); - it->second->SetUpdated (false); - } - if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL) - it = g_Profiles.erase (it); - else - it++; - } - } - if (!tmp.empty ()) - return std::async (std::launch::async, SaveProfilesToDisk, std::move (tmp)); - return std::future(); + m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); + m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); } - void SaveProfiles () + void DeleteObsoleteProfiles () { - std::unordered_map > tmp; - { - std::lock_guard l(g_ProfilesMutex); - std::swap (tmp, g_Profiles); - } - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: tmp) - if (it.second->IsUseful() && (it.second->IsUpdated () || ts - it.second->GetLastUpdateTime () < PEER_PROFILE_EXPIRATION_TIMEOUT)) - it.second->Save (it.first); - } - - static void DeleteFilesFromDisk () - { - std::vector files; - g_ProfilesStorage.Traverse(files); - struct stat st; std::time_t now = std::time(nullptr); - for (const auto& path: files) - { - if (stat(path.c_str(), &st) != 0) - { + + std::vector files; + m_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) - { + if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) { LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); i2p::fs::Remove(path); } } - } - - std::future DeleteObsoleteProfiles () - { - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::lock_guard l(g_ProfilesMutex); - for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) - { - if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) - it = g_Profiles.erase (it); - else - it++; - } - } - - return std::async (std::launch::async, DeleteFilesFromDisk); } - - bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update) - { - if (!update) return true; - std::shared_ptr profile; - { - std::lock_guard l(g_ProfilesMutex); - auto it = g_Profiles.find (identHash); - if (it != g_Profiles.end ()) - profile = it->second; - } - if (profile) - { - update (profile); - return true; - } - // postpone - std::lock_guard l(g_PostponedUpdatesMutex); - g_PostponedUpdates.emplace_back (identHash, update); - return false; - } - - static void ApplyPostponedUpdates (std::list)> > >&& updates) - { - for (const auto& [ident, update] : updates) - { - auto profile = GetRouterProfile (ident); - update (profile); - } - } - - std::future FlushPostponedRouterProfileUpdates () - { - if (g_PostponedUpdates.empty ()) return std::future(); - - std::list)> > > updates; - { - std::lock_guard l(g_PostponedUpdatesMutex); - g_PostponedUpdates.swap (updates); - } - return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates)); - } } } diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 59995b3f..49e362ca 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,9 +10,7 @@ #define PROFILING_H__ #include -#include -#include -#include +#include #include "Identity.h" namespace i2p @@ -23,81 +21,44 @@ 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"; // deprecated - const char PEER_PROFILE_LAST_UPDATE_TIMESTAMP[] = "lastupdatetimestamp"; - const char PEER_PROFILE_LAST_UNREACHABLE_TIME[] = "lastunreachabletime"; + const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; 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 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 - + + const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days) + const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 24 * 3600; // in seconds (1 day) + const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3 * 3600; // in seconds (3 hours) + class RouterProfile { public: RouterProfile (); + RouterProfile& operator= (const RouterProfile& ) = default; void Save (const IdentHash& identHash); void Load (const IdentHash& identHash); bool IsBad (); - bool IsUnreachable (); - bool IsReal () const { return m_HasConnected || m_NumTunnelsAgreed > 0 || m_NumTunnelsDeclined > 0; } void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); - void Unreachable (bool unreachable); - void Connected (); - void Duplicated (); - - uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; - bool IsUpdated () const { return m_IsUpdated; }; - void SetUpdated (bool updated) { m_IsUpdated = updated; } - uint64_t GetLastAccessTime () const { return m_LastAccessTime; }; - void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; }; - uint64_t GetLastPersistTime () const { return m_LastPersistTime; }; - void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; }; - - bool IsUseful() const; - bool IsDuplicated () const { return m_IsDuplicated; }; - - const boost::asio::ip::udp::endpoint& GetLastEndpoint () const { return m_LastEndpoint; } - void SetLastEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_LastEndpoint = ep; } - bool HasLastEndpoint (bool v4) const { return !m_LastEndpoint.address ().is_unspecified () && m_LastEndpoint.port () && - ((v4 && m_LastEndpoint.address ().is_v4 ()) || (!v4 && m_LastEndpoint.address ().is_v6 ())); } - private: + boost::posix_time::ptime GetTime () const; void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; bool IsLowPartcipationRate () const; bool IsLowReplyRate () const; - bool IsDeclinedRecently (uint64_t ts); private: - bool m_IsUpdated; - uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, - m_LastAccessTime, m_LastPersistTime; // in seconds + boost::posix_time::ptime m_LastUpdateTime; // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -105,21 +66,11 @@ 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 (); - std::future DeleteObsoleteProfiles (); - void SaveProfiles (); - std::future PersistProfiles (); - bool UpdateRouterProfile (const IdentHash& identHash, std::function)> update); // return true if updated immediately, and false if postponed - std::future FlushPostponedRouterProfileUpdates (); + void DeleteObsoleteProfiles (); } } diff --git a/libi2pd/Queue.h b/libi2pd/Queue.h index 0e3e4fde..441f8c3a 100644 --- a/libi2pd/Queue.h +++ b/libi2pd/Queue.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,7 +9,8 @@ #ifndef QUEUE_H__ #define QUEUE_H__ -#include +#include +#include #include #include #include @@ -28,20 +29,22 @@ namespace util void Put (Element e) { std::unique_lock l(m_QueueMutex); - m_Queue.push_back (std::move(e)); + m_Queue.push (std::move(e)); m_NonEmpty.notify_one (); } - void Put (std::list& list) + templateclass Container, typename... R> + void Put (const Container& vec) { - if (!list.empty ()) + if (!vec.empty ()) { std::unique_lock l(m_QueueMutex); - m_Queue.splice (m_Queue.end (), list); + for (const auto& it: vec) + m_Queue.push (std::move(it)); m_NonEmpty.notify_one (); - } - } - + } + } + Element GetNext () { std::unique_lock l(m_QueueMutex); @@ -84,7 +87,7 @@ namespace util return m_Queue.empty (); } - int GetSize () const + int GetSize () { std::unique_lock l(m_QueueMutex); return m_Queue.size (); @@ -104,28 +107,15 @@ namespace util return GetNonThreadSafe (true); } - 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_front (); + m_Queue.pop (); return el; } return nullptr; @@ -133,8 +123,8 @@ namespace util private: - std::list m_Queue; - mutable std::mutex m_QueueMutex; + std::queue m_Queue; + std::mutex m_QueueMutex; std::condition_variable m_NonEmpty; }; } diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 23dae8ff..4c23b4cc 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,9 +14,6 @@ #include #include #include -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#endif #include #include "Crypto.h" @@ -29,7 +26,6 @@ #include "HTTP.h" #include "util.h" #include "Config.h" -#include "Socks5.h" namespace i2p { @@ -157,7 +153,7 @@ namespace data return ProcessSU3Stream (s); else { - LogPrint (eLogCritical, "Reseed: Can't open file ", filename); + LogPrint (eLogError, "Reseed: Can't open file ", filename); return 0; } } @@ -174,7 +170,7 @@ namespace data } else { - LogPrint (eLogCritical, "Reseed: Can't open file ", filename); + LogPrint (eLogError, "Reseed: Can't open file ", filename); return 0; } } @@ -282,7 +278,7 @@ namespace data if (verify) // not verified { - LogPrint (eLogCritical, "Reseed: SU3 verification failed"); + LogPrint (eLogError, "Reseed: SU3 verification failed"); return 0; } @@ -324,7 +320,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; @@ -483,36 +479,20 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - 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; + const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); + const BIGNUM * n, * e, * d; RSA_get0_key(key, &n, &e, &d); -#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); - } + PublicKey value; + i2p::crypto::bn2buf (n, value, 512); + if (cn) + m_SigningKeys[cn] = value; else - LogPrint (eLogError, "Reseed: Can't extract RSA key from ", filename); -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - BN_free (n1); -#endif + LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); } SSL_free (ssl); } else - LogPrint (eLogCritical, "Reseed: Can't open certificate file ", filename); + LogPrint (eLogError, "Reseed: Can't open certificate file ", filename); SSL_CTX_free (ctx); } @@ -554,24 +534,24 @@ namespace data } // check for valid proxy url schema if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { - LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); + LogPrint(eLogError, "Reseed: Bad proxy url: ", proxy); return ""; } } else { - LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); + LogPrint(eLogError, "Reseed: Bad proxy url: ", proxy); return ""; } } i2p::http::URL url; if (!url.parse(address)) { - LogPrint(eLogCritical, "Reseed: Failed to parse url: ", address); + LogPrint(eLogError, "Reseed: Failed to parse url: ", address); return ""; } url.schema = "https"; if (!url.port) url.port = 443; - boost::asio::io_context service; + boost::asio::io_service service; boost::system::error_code ecode; boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); @@ -581,10 +561,11 @@ namespace data if(proxyUrl.schema.size()) { // proxy connection - auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode); + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode); if(!ecode) { - s.lowest_layer().connect(*it.begin (), ecode); + s.lowest_layer().connect(*it, ecode); if(!ecode) { auto & sock = s.next_layer(); @@ -617,7 +598,7 @@ namespace data LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message()); return ""; } - if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0) + if(proxyRes.parse(boost::asio::buffer_cast(readbuf.data()), readbuf.size()) <= 0) { sock.close(); LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply"); @@ -634,21 +615,62 @@ namespace data { // assume socks if not http, is checked before this for other types // TODO: support username/password auth etc - 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) + 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) { 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 ""; + } } } } @@ -656,32 +678,26 @@ namespace data else { // direct connection - auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); if (!ecode) { bool connected = false; - for (const auto& it: endpoints) + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) { - 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) + boost::asio::ip::tcp::endpoint ep = *it; + if ((ep.address ().is_v4 () && i2p::context.SupportsV4 ()) || + (ep.address ().is_v6 () && i2p::context.SupportsV6 ())) { s.lowest_layer().connect (ep, ecode); if (!ecode) { - LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); connected = true; break; } } + it++; } if (!connected) { @@ -761,44 +777,20 @@ namespace data if (!url.port) url.port = 80; boost::system::error_code ecode; - boost::asio::io_context service; + boost::asio::io_service service; boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); - auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); + 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); if (!ecode) { - 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); + LogPrint (eLogDebug, "Reseed: Connected to Yggdrasil ", url.host, ":", url.port); return ReseedRequest (s, url.to_string()); } else - LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't connect to Yggdrasil ", url.host, ": ", ecode.message ()); return ""; } diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index cfb92f00..9f8b6626 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,9 +20,6 @@ #include "Log.h" #include "Family.h" #include "ECIESX25519AEADRatchetSession.h" -#include "Transports.h" -#include "Tunnel.h" -#include "CryptoKey.h" #include "RouterContext.h" namespace i2p @@ -32,17 +29,14 @@ 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_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) + m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) { } void RouterContext::Init () { - srand (m_Rng () % 1000); - m_StartupTime = i2p::util::GetMonotonicSeconds (); + srand (i2p::util::GetMillisecondsSinceEpoch () % 1000); + m_StartupTime = std::chrono::steady_clock::now(); if (!Load ()) CreateNewRouter (); @@ -53,40 +47,6 @@ 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, @@ -100,9 +60,14 @@ namespace i2p i2p::data::LocalRouterInfo routerInfo; routerInfo.SetRouterIdentity (GetIdentity ()); uint16_t port; i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); + if (!port) + { + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser + } bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ssu; i2p::config::GetOption("ssu", ssu); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); @@ -125,103 +90,88 @@ namespace i2p bool ssu2Published = false; if (ssu2) i2p::config::GetOption("ssu2.published", ssu2Published); - uint8_t caps = 0; + uint8_t caps = 0, addressCaps = 0; if (ipv4) { - std::string host; - if (!nat) + std::string host = "127.0.0.1"; + if (!i2p::config::IsDefault("host")) + i2p::config::GetOption("host", host); + else if (!nat) + { // we have no NAT so set external address from local address - i2p::config::GetOption("address4", host); - if (host.empty ()) i2p::config::GetOption("host", host); + std::string address4; i2p::config::GetOption("address4", address4); + if (!address4.empty ()) host = address4; + } if (ntcp2) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - if (ntcp2Published && ntcp2Port) + if (ntcp2Published) + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v4::from_string (host), port); + else // add non-published NTCP2 address { - boost::asio::ip::address addr; - if (!host.empty ()) - addr = boost::asio::ip::make_address (host); - if (!addr.is_v4()) - addr = boost::asio::ip::address_v4 (); - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - } - else - { - // add non-published NTCP2 address - uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; - if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, addressCaps); + addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); } } + if (ssu) + { + routerInfo.AddSSUAddress (host.c_str(), port, nullptr); + caps |= i2p::data::RouterInfo::eReachable; // R + } if (ssu2) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - if (ssu2Published && ssu2Port) - { - boost::asio::ip::address addr; - if (!host.empty ()) - addr = boost::asio::ip::make_address (host); - if (!addr.is_v4()) - addr = boost::asio::ip::address_v4 (); - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - } + if (ssu2Published) + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v4::from_string (host), port); else { - uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; - if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, addressCaps); + addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro); } } } if (ipv6) { - std::string host; i2p::config::GetOption("address6", host); - if (host.empty () && !ipv4) i2p::config::GetOption("host", host); // use host for ipv6 only if ipv4 is not presented + std::string host = "::1"; + if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only + i2p::config::GetOption("host", host); + else + { + std::string address6; i2p::config::GetOption("address6", address6); + if (!address6.empty ()) host = address6; + } if (ntcp2) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - if (ntcp2Published && ntcp2Port) + if (ntcp2Published) { std::string ntcp2Host; if (!i2p::config::IsDefault ("ntcp2.addressv6")) i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); else ntcp2Host = host; - boost::asio::ip::address addr; - if (!ntcp2Host.empty ()) - addr = boost::asio::ip::make_address (ntcp2Host); - if (!addr.is_v6()) - addr = boost::asio::ip::address_v6 (); - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port); } else { if (!ipv4) // no other ntcp2 addresses yet - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::AddressCaps::eV6); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; } } + if (ssu) + { + routerInfo.AddSSUAddress (host.c_str(), port, nullptr); + caps |= i2p::data::RouterInfo::eReachable; // R + } if (ssu2) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - if (ssu2Published && ssu2Port) - { - boost::asio::ip::address addr; - if (!host.empty ()) - addr = boost::asio::ip::make_address (host); - if (!addr.is_v6()) - addr = boost::asio::ip::address_v6 (); - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - } + if (ssu2Published) + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), port); else { if (!ipv4) // no other ssu2 addresses yet - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::AddressCaps::eV6); + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro); + addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; } } } @@ -232,59 +182,20 @@ namespace i2p routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); } + if (addressCaps) + routerInfo.SetUnreachableAddressesTransportCaps (addressCaps); routerInfo.UpdateCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - m_RouterInfo.SetUnreachable (false); - } - - uint16_t RouterContext::SelectRandomPort () const - { - uint16_t port; - do - { - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - } - while(i2p::util::net::IsPortInReservedRange(port)); - - return port; } void RouterContext::UpdateRouterInfo () { - 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_RouterInfo.CreateBuffer (m_Keys); + m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO)); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } @@ -314,34 +225,12 @@ 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; + m_Error = eRouterErrorNone; switch (m_Status) { case eRouterStatusOK: @@ -350,12 +239,6 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; - case eRouterStatusMesh: - m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable); - break; - case eRouterStatusProxy: - m_RouterInfo.UpdateCaps ((m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable) & ~i2p::data::RouterInfo::eReachable); - break; default: ; } @@ -364,11 +247,8 @@ 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) { @@ -386,12 +266,10 @@ namespace i2p void RouterContext::UpdatePort (int port) { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; bool updated = false; - for (auto& address : *addresses) + for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address && address->port != port) + if (!address->IsNTCP2 () && !address->IsSSU2 () && address->port != port) { address->port = port; updated = true; @@ -401,98 +279,82 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::PublishNTCP2Address (std::shared_ptr address, - int port, bool publish) const - { - if (!address) return; - if (!port && !address->port) port = SelectRandomPort (); - if (port) address->port = port; - address->published = publish; - memcpy (address->i, m_NTCP2Keys->iv, 16); - } - void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg) { if (!m_NTCP2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; bool updated = false; - if (v4) + for (auto& address : m_RouterInfo.GetAddresses ()) { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; - if (addr && (addr->port != port || addr->published != publish)) + if (address->IsNTCP2 () && (address->port != port || address->published != publish)) { - PublishNTCP2Address (addr, port, publish); - updated = true; + bool isAddr = v4 && address->IsV4 (); + if (!isAddr && (v6 || ygg)) + { + if (i2p::util::net::IsYggdrasilAddress (address->host)) + isAddr = ygg; + else + isAddr = v6 && address->IsV6 (); + } + if (isAddr) + { + if (!port && !address->port) + { + // select random port only if address's port is not set + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser + } + if (port) address->port = port; + address->published = publish; + memcpy (address->i, m_NTCP2Keys->iv, 16); + updated = true; + } } } - if (v6) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && (addr->port != port || addr->published != publish)) - { - PublishNTCP2Address (addr, port, publish); - updated = true; - } - } - if (ygg) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; - if (addr && (addr->port != port || addr->published != publish)) - { - PublishNTCP2Address (addr, port, publish); - updated = true; - } - } - if (updated) UpdateRouterInfo (); } - void RouterContext::UpdateNTCP2Keys () + void RouterContext::UpdateNTCP2Address (bool enable) { - if (!m_NTCP2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& it: *addresses) + auto& addresses = m_RouterInfo.GetAddresses (); + bool found = false, updated = false; + for (auto it = addresses.begin (); it != addresses.end (); ++it) { - if (it && it->IsNTCP2 ()) + if ((*it)->IsNTCP2 ()) { - it->s = m_NTCP2Keys->staticPublicKey; - memcpy (it->i, m_NTCP2Keys->iv, 16); + found = true; + if (!enable) + { + addresses.erase (it); + updated= true; + } + break; } } + if (enable && !found) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + updated = true; + } + if (updated) + UpdateRouterInfo (); } void RouterContext::PublishSSU2Address (int port, bool publish, bool v4, bool v6) { - if (!m_SSU2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - int newPort = 0; - if (!port) - { - for (const auto& address : *addresses) - if (address && address->port) - { - newPort = address->port; - break; - } - if (!newPort) newPort = SelectRandomPort (); - } + if (!m_SSU2Keys || (publish && !port)) return; bool updated = false; - for (auto& address : *addresses) + for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address && address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && + if (address->IsSSU2 () && (address->port != port || address->published != publish) && ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) { - if (port) address->port = port; - else if (!address->port) address->port = newPort; + address->port = port; address->published = publish; if (publish) - address->caps |= (i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); + address->caps |= i2p::data::RouterInfo::eSSUIntroducer; else - address->caps &= ~(i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); + address->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; updated = true; } } @@ -500,116 +362,90 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::UpdateSSU2Keys () + void RouterContext::UpdateSSU2Address (bool enable) { - if (!m_SSU2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& it: *addresses) + auto& addresses = m_RouterInfo.GetAddresses (); + bool found = false, updated = false; + for (auto it = addresses.begin (); it != addresses.end (); ++it) { - if (it && it->IsSSU2 ()) + if ((*it)->IsSSU2 ()) { - it->s = m_SSU2Keys->staticPublicKey; - it->i = m_SSU2Keys->intro; + found = true; + if (!enable) + { + addresses.erase (it); + updated= true; + } + break; } } + if (enable && !found) + { + uint8_t addressCaps = 0; + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + if (ipv4) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addressCaps); + updated = true; + } + if (updated) + UpdateRouterInfo (); } void RouterContext::UpdateAddress (const boost::asio::ip::address& host) { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; bool updated = false; - if (host.is_v4 ()) + for (auto& address : m_RouterInfo.GetAddresses ()) { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; - if (addr && addr->host != host) + if (address->host != host && address->IsCompatible (host) && + !i2p::util::net::IsYggdrasilAddress (address->host)) { - addr->host = host; - updated = true; - } - addr = (*addresses)[i2p::data::RouterInfo::eSSU2V4Idx]; - if (addr && addr->host != host) - { - addr->host = host; - updated = true; - } - } - else if (host.is_v6 ()) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && addr->host != host) - { - addr->host = host; - updated = true; - } - addr = (*addresses)[i2p::data::RouterInfo::eSSU2V6Idx]; - if (addr && (addr->host != host || !addr->ssu->mtu)) - { - addr->host = host; - if (m_StatusV6 != eRouterStatusProxy) + address->host = host; + if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU) { // update MTU auto mtu = i2p::util::net::GetMTU (host); if (mtu) { LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); - if (mtu > maxMTU) - { - mtu = maxMTU; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); + if (mtu > 1472) { // TODO: magic constant + mtu = 1472; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); } - addr->ssu->mtu = mtu; + if (address->ssu) address->ssu->mtu = mtu; } } updated = true; } } - auto ts = i2p::util::GetSecondsSinceEpoch (); if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) UpdateRouterInfo (); } - bool RouterContext::AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4) + bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer) { - bool ret = m_RouterInfo.AddSSU2Introducer (introducer, v4); + bool ret = m_RouterInfo.AddIntroducer (introducer); if (ret) UpdateRouterInfo (); return ret; } - void RouterContext::RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4) + void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { - if (m_RouterInfo.RemoveSSU2Introducer (h, v4)) + if (m_RouterInfo.RemoveIntroducer (e)) 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); - if (addr && !addr->ssu->introducers.empty ()) - { - addr->ssu->introducers.clear (); - UpdateRouterInfo (); - } - } - void RouterContext::SetFloodfill (bool floodfill) { m_IsFloodfill = floodfill; if (floodfill) - m_RouterInfo.UpdateFloodfillProperty (true); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); else { - m_RouterInfo.UpdateFloodfillProperty (false); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); // 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); @@ -646,15 +482,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 = 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_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_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s default: - limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low; // 48 + limit = 48; type = low; } /* update caps & flags in RI */ auto caps = m_RouterInfo.GetCaps (); @@ -665,7 +501,9 @@ 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; } @@ -676,16 +514,13 @@ namespace i2p void RouterContext::SetBandwidth (int limit) { - 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'); } + 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'); } else { SetBandwidth('K'); } - - - LogPrint(eLogInfo, "RouterContext: Set bandwidth ", limit, ". kb/s"); m_BandwidthLimit = limit; // set precise limit } @@ -701,6 +536,22 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } + void RouterContext::RemoveNTCPAddress (bool v4only) + { + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto it = addresses.begin (); it != addresses.end ();) + { + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () && + (!v4only || (*it)->host.is_v4 ())) + { + it = addresses.erase (it); + if (v4only) break; // otherwise might be more than one address + } + else + ++it; + } + } + void RouterContext::SetUnreachable (bool v4, bool v6) { if (v4 || (v6 && !SupportsV4 ())) @@ -715,18 +566,15 @@ namespace i2p } uint16_t port = 0; // delete previous introducers - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { - for (auto& addr : *addresses) - if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) - { - addr->published = false; - addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer - addr->ssu->introducers.clear (); - port = addr->port; - } - } + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr : addresses) + if (addr->ssu && !addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + { + addr->published = false; + addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer + addr->ssu->introducers.clear (); + port = addr->port; + } // unpublish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) @@ -750,19 +598,15 @@ namespace i2p } uint16_t port = 0; // delete previous introducers - bool isSSU2Published; i2p::config::GetOption ("ssu2.published", isSSU2Published); - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { - for (auto& addr : *addresses) - if (addr && addr->ssu && isSSU2Published && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) - { - addr->published = true; - addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; - addr->ssu->introducers.clear (); - if (addr->port) port = addr->port; - } - } + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr : addresses) + if (addr->ssu && !addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + { + addr->published = true; + addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; + addr->ssu->introducers.clear (); + port = addr->port; + } // publish NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) @@ -785,98 +629,79 @@ namespace i2p if (supportsV6) { // insert v6 addresses if necessary - bool foundNTCP2 = false, foundSSU2 = false; + bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false; uint16_t port = 0; - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - for (auto& addr: *addresses) + if (addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) { - if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) + switch (addr->transportStyle) { - switch (addr->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; - } + case i2p::data::RouterInfo::eTransportSSU: + foundSSU = true; + break; + case i2p::data::RouterInfo::eTransportNTCP: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; } - if (addr) port = addr->port; + } + port = addr->port; + } + if (!port) i2p::config::GetOption("port", port); + // SSU + if (!foundSSU) + { + bool ssu; i2p::config::GetOption("ssu", ssu); + if (ssu) + { + std::string host = "::1"; // TODO: read host + m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); } } - if (!port) - { - i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); - } // NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + if (!foundNTCP2) { - if (!foundNTCP2) + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); + if (ntcp2) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - bool added = false; - bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); if (ntcp2Published) { std::string ntcp2Host; if (!i2p::config::IsDefault ("ntcp2.addressv6")) i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); else - i2p::config::GetOption("host", ntcp2Host); - if (!ntcp2Host.empty () && ntcp2Port) - { - auto addr = boost::asio::ip::make_address (ntcp2Host); - if (addr.is_v6 ()) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - added = true; - } - } + ntcp2Host = "::1"; + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); } - if (!added) - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV6); + else + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV6); } } - else - m_RouterInfo.RemoveNTCP2Address (false); // SSU2 - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) + if (!foundSSU2) { - if (!foundSSU2) + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - bool added = false; bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - if (ssu2Published && ssu2Port) + if (ssu2Published) { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v6 ()) - { - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - added = true; - } - } + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("::1"), ssu2Port); } - if (!added) - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV6); + else + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV6); } } - else - m_RouterInfo.RemoveSSU2Address (false); - if (ntcp2 || ssu2) - m_RouterInfo.EnableV6 (); + m_RouterInfo.EnableV6 (); } else m_RouterInfo.DisableV6 (); @@ -885,97 +710,78 @@ namespace i2p void RouterContext::SetSupportsV4 (bool supportsV4) { + // check if updates + if (supportsV4 && SupportsV4 ()) return; + if (!supportsV4 && !SupportsV4 ()) return; + // update if (supportsV4) { - bool foundNTCP2 = false, foundSSU2 = false; + bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false; + std::string host = "127.0.0.1"; uint16_t port = 0; - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - for (auto& addr: *addresses) + if (addr->IsV4 ()) { - if (addr && addr->IsV4 ()) + switch (addr->transportStyle) { - switch (addr->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; - } + case i2p::data::RouterInfo::eTransportSSU: + foundSSU = true; + break; + case i2p::data::RouterInfo::eTransportNTCP: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; } - if (addr && addr->port) port = addr->port; } + if (addr->port) port = addr->port; } - if (!port) + if (!port) i2p::config::GetOption("port", port); + // SSU + if (!foundSSU) { - i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); + bool ssu; i2p::config::GetOption("ssu", ssu); + if (ssu) + m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); } // NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + if (!foundNTCP2) { - if (!foundNTCP2) + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - bool added = false; bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published && ntcp2Port) + if (ntcp2Published) { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v4 ()) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - added = true; - } - } + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); } - if (!added) - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV4); + else + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV4); } } - else - m_RouterInfo.RemoveNTCP2Address (true); // SSU2 - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) + if (!foundSSU2) { - if (!foundSSU2) + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - bool added = false; bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - std::string host; i2p::config::GetOption("host", host); - if (ssu2Published && ssu2Port) + if (ssu2Published) { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v4 ()) - { - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - added = true; - } - } + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("127.0.0.1"), ssu2Port); } - if (!added) - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV4); + else + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV6); } } - else - m_RouterInfo.RemoveSSU2Address (true); - if (ntcp2 || ssu2) - m_RouterInfo.EnableV4 (); + m_RouterInfo.EnableV4 (); } else m_RouterInfo.DisableV4 (); @@ -986,61 +792,53 @@ namespace i2p { if (supportsmesh) { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; m_RouterInfo.EnableMesh (); - if ((*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]) return; // we have mesh address already uint16_t port = 0; i2p::config::GetOption ("ntcp2.port", port); if (!port) i2p::config::GetOption("port", port); - if (!port) + bool foundMesh = false; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - for (auto& addr: *addresses) + if (!port) port = addr->port; + if (i2p::util::net::IsYggdrasilAddress (addr->host)) { - if (addr && addr->port) - { - port = addr->port; - break; - } + foundMesh = true; + break; } } - if (!port) port = SelectRandomPort (); - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); + if (!foundMesh) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); } else m_RouterInfo.DisableMesh (); UpdateRouterInfo (); } - void RouterContext::SetMTU (int mtu, bool v4) - { - if (mtu < 1280 || mtu > 1500) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& addr: *addresses) - { - if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) - { - addr->ssu->mtu = mtu; - LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); - } - } - } - void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - std::shared_ptr addr; - if (i2p::util::net::IsYggdrasilAddress (host)) // yggdrasil - addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; - else if (host.is_v6 ()) - addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && addr->IsPublishedNTCP2 () && addr->host != host) + bool isYgg = i2p::util::net::IsYggdrasilAddress (host); + bool updated = false; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - addr->host = host; - UpdateRouterInfo (); + if (addr->IsPublishedNTCP2 ()) + { + bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); + if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) + { + if (addr->host != host) + { + addr->host = host; + updated = true; + } + break; + } + } } + + if (updated) + UpdateRouterInfo (); } void RouterContext::UpdateStats () @@ -1108,20 +906,6 @@ namespace i2p } n2k.close (); } - // read SSU2 keys if available - std::ifstream s2k (i2p::fs::DataDirPath (SSU2_KEYS), std::ifstream::in | std::ifstream::binary); - if (s2k) - { - s2k.seekg (0, std::ios::end); - size_t len = s2k.tellg(); - s2k.seekg (0, std::ios::beg); - if (len == sizeof (SSU2PrivateKeys)) - { - m_SSU2Keys.reset (new SSU2PrivateKeys ()); - s2k.read ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); - } - s2k.close (); - } // read RouterInfo m_RouterInfo.SetRouterIdentity (oldIdentity ? oldIdentity : GetIdentity ()); i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); @@ -1142,28 +926,40 @@ namespace i2p if (IsUnreachable ()) SetReachable (true, true); // we assume reachable until we discover firewall through peer tests - bool updated = false; - // create new NTCP2 keys if required + // read NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - if ((ntcp2 || ygg) && !m_NTCP2Keys) + if (ntcp2 || ygg) { - NewNTCP2Keys (); - UpdateNTCP2Keys (); - updated = true; + if (!m_NTCP2Keys) NewNTCP2Keys (); + UpdateNTCP2Address (true); // enable NTCP2 } - // create new SSU2 keys if required + else + UpdateNTCP2Address (false); // disable NTCP2 + + // read SSU2 bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2 && !m_SSU2Keys) + if (ssu2) { - NewSSU2Keys (); - UpdateSSU2Keys (); - updated = true; + // read SSU2 keys if available + std::ifstream s2k (i2p::fs::DataDirPath (SSU2_KEYS), std::ifstream::in | std::ifstream::binary); + if (s2k) + { + s2k.seekg (0, std::ios::end); + size_t len = s2k.tellg(); + s2k.seekg (0, std::ios::beg); + if (len == sizeof (SSU2PrivateKeys)) + { + m_SSU2Keys.reset (new SSU2PrivateKeys ()); + s2k.read ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); + } + s2k.close (); + } + if (!m_SSU2Keys) NewSSU2Keys (); + UpdateSSU2Address (true); // enable SSU2 } - if (m_RouterInfo.UpdateCongestion (i2p::data::RouterInfo::eLowCongestion)) - updated = true; - if (updated) - UpdateRouterInfo (); + else + UpdateSSU2Address (false); // disable SSU2 return true; } @@ -1184,45 +980,23 @@ 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, i2p::garlic::ECIESX25519AEADRatchetSession * from) + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { - if (typeID == eI2NPTunnelTest) - { - // try tunnel test - auto pool = GetTunnelPool (); - if (pool && pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET))) - return true; - } auto msg = CreateI2NPMessage (typeID, payload, len, msgID); if (!msg) return false; i2p::HandleI2NPMessage (msg); return true; } + void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { - 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) - { + std::unique_lock l(m_GarlicMutex); uint8_t * buf = msg->GetPayload (); uint32_t len = bufbe32toh (buf); if (len > msg->GetLength ()) @@ -1239,52 +1013,28 @@ namespace i2p else LogPrint (eLogError, "Router: Session is not set for ECIES router"); } - } - + } + void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - if (m_Service) - boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg)); + if (i2p::data::netdb.GetPublishReplyToken () == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) + i2p::data::netdb.PostI2NPMsg (msg); else - LogPrint (eLogError, "Router: service is NULL"); - } - - void RouterContext::PostDeliveryStatusMessage (std::shared_ptr msg) - { - 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 + std::unique_lock l(m_GarlicMutex); i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + } } - void RouterContext::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) + void RouterContext::CleanupDestination () { - 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"); - } + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::CleanupExpiredTags (); + } uint32_t RouterContext::GetUptime () const { - return i2p::util::GetMonotonicSeconds () - m_StartupTime; + return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); } bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const @@ -1354,208 +1104,4 @@ 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 754c49da..eb5db38f 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,13 +12,12 @@ #include #include #include -#include -#include +#include +#include #include #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" -#include "util.h" namespace i2p { @@ -31,32 +30,17 @@ 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 = 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 + const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes enum RouterStatus { eRouterStatusOK = 0, - eRouterStatusFirewalled = 1, - eRouterStatusUnknown = 2, - eRouterStatusProxy = 3, - eRouterStatusMesh = 4 - }; - - const char* const ROUTER_STATUS_NAMES[] = - { - "OK", // 0 - "Firewalled", // 1 - "Unknown", // 2 - "Proxy", // 3 - "Mesh" // 4 + eRouterStatusTesting = 1, + eRouterStatusFirewalled = 2, + eRouterStatusError = 3, + eRouterStatusUnknown = 4, + eRouterStatusProxy = 5, + eRouterStatusMesh = 6 }; enum RouterError @@ -64,9 +48,7 @@ namespace garlic eRouterErrorNone = 0, eRouterErrorClockSkew = 1, eRouterErrorOffline = 2, - eRouterErrorSymmetricNAT = 3, - eRouterErrorFullConeNAT = 4, - eRouterErrorNoDescriptors = 5 + eRouterErrorSymmetricNAT = 3 }; class RouterContext: public i2p::garlic::GarlicDestination @@ -87,23 +69,11 @@ 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 () @@ -116,8 +86,7 @@ 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; }; @@ -132,31 +101,26 @@ 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); + void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; RouterStatus GetStatusV6 () const { return m_StatusV6; }; void SetStatusV6 (RouterStatus status); - RouterError GetErrorV6 () const { return m_ErrorV6; }; - void SetErrorV6 (RouterError error) { m_ErrorV6 = error; }; int GetNetID () const { return m_NetID; }; void SetNetID (int netID) { m_NetID = netID; }; bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data); void UpdatePort (int port); // called from Daemon - void UpdateAddress (const boost::asio::ip::address& host); // called from SSU2 or Daemon + void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); + void UpdateNTCP2Address (bool enable); void PublishSSU2Address (int port, bool publish, bool v4, bool v6); - 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); + void UpdateSSU2Address (bool enable); + void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later + bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); + void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool IsUnreachable () const; void SetUnreachable (bool v4, bool v6); void SetReachable (bool v4, bool v6); @@ -169,43 +133,38 @@ 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 (); }; void SetSupportsV6 (bool supportsV6); 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 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 {}; + 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 () {}; // implements GarlicDestination - std::shared_ptr GetLeaseSet () override { return nullptr; }; - std::shared_ptr GetTunnelPool () const override; + std::shared_ptr GetLeaseSet () { return nullptr; }; + std::shared_ptr GetTunnelPool () const; // override GarlicDestination - void ProcessGarlicMessage (std::shared_ptr msg) override; - void ProcessDeliveryStatusMessage (std::shared_ptr msg) override; - void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override; + void ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatusMessage (std::shared_ptr msg); protected: // implements GarlicDestination - 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 HandleI2NPMessage (const uint8_t * buf, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); private: @@ -214,31 +173,11 @@ namespace garlic void UpdateRouterInfo (); void NewNTCP2Keys (); void NewSSU2Keys (); - void UpdateNTCP2Keys (); - void UpdateSSU2Keys (); bool Load (); void SaveKeys (); - void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; - uint16_t SelectRandomPort () const; - void PublishNTCP2Address (std::shared_ptr address, int port, bool publish) const; bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); - void PostGarlicMessage (std::shared_ptr msg); - void PostDeliveryStatusMessage (std::shared_ptr msg); - void ScheduleInitialPublish (); - void HandleInitialPublishTimer (const boost::system::error_code& ecode); - void SchedulePublish (); - void HandlePublishTimer (const boost::system::error_code& ecode); - void Publish (); - void SchedulePublishResend (); - void HandlePublishResendTimer (const boost::system::error_code& ecode); - void ScheduleCongestionUpdate (); - void HandleCongestionUpdateTimer (const boost::system::error_code& ecode); - void UpdateCongestion (); - void ScheduleCleanupTimer (); - void HandleCleanupTimer (const boost::system::error_code& ecode); - private: i2p::data::LocalRouterInfo m_RouterInfo; @@ -247,29 +186,18 @@ namespace garlic std::shared_ptr m_ECIESSession; uint64_t m_LastUpdateTime; // in seconds bool m_AcceptsTunnels, m_IsFloodfill; - uint64_t m_StartupTime; // monotonic seconds + std::chrono::time_point m_StartupTime; uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; RouterStatus m_Status, m_StatusV6; - RouterError m_Error, m_ErrorV6; - bool m_Testing, m_TestingV6; + RouterError m_Error; 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 4af32f57..34f38919 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,10 +10,9 @@ #include #include "I2PEndian.h" #include -#include -#include -#include // for boost::to_lower -#ifndef __cpp_lib_atomic_shared_ptr +#include +#include +#if (BOOST_VERSION >= 105300) #include #endif #include "version.h" @@ -22,10 +21,8 @@ #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 @@ -36,35 +33,33 @@ namespace data { if (len > size ()) len = size (); memcpy (data (), buf, len); - m_BufferLen = len; } RouterInfo::RouterInfo (): m_Buffer (nullptr) { - m_Addresses = AddressesPtr(new Addresses ()); // create empty list + m_Addresses = boost::make_shared(); // create empty list } RouterInfo::RouterInfo (const std::string& fullPath): - 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_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), + m_SupportedTransports (0),m_ReachableTransports (0), + m_Caps (0), m_Version (0) { - m_Addresses = AddressesPtr(new Addresses ()); // create empty list - m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's + m_Addresses = boost::make_shared(); // create empty list + m_Buffer = 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_IsFloodfill (false), - m_IsBufferScheduledToDelete (false), m_SupportedTransports (0), m_ReachableTransports (0), m_PublishedTransports (0), - m_Caps (0), m_Version (0), m_Congestion (eLowCongestion) + m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), + m_SupportedTransports (0), m_ReachableTransports (0), + m_Caps (0), m_Version (0) { if (len <= MAX_RI_BUFFER_SIZE) { - m_Addresses = AddressesPtr(new Addresses ()); // create empty list + m_Addresses = boost::make_shared(); // create empty list m_Buffer = buf; - if (m_Buffer) m_Buffer->SetBufferLen (len); + m_BufferLen = len; ReadFromBuffer (true); } else @@ -76,7 +71,7 @@ namespace data } RouterInfo::RouterInfo (const uint8_t * buf, size_t len): - RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len) + RouterInfo (std::make_shared (buf, len), len) { } @@ -84,12 +79,13 @@ namespace data { } - bool RouterInfo::Update (const uint8_t * buf, size_t len) + void RouterInfo::Update (const uint8_t * buf, size_t len) { if (len > MAX_RI_BUFFER_SIZE) { - LogPrint (eLogWarning, "RouterInfo: Updated buffer is too long ", len, ". Not changed"); - return false; + LogPrint (eLogError, "RouterInfo: Buffer is too long ", len); + m_IsUnreachable = true; + return; } // verify signature since we have identity already int l = len - m_RouterIdentity->GetSignatureLen (); @@ -100,24 +96,23 @@ namespace data m_IsUnreachable = false; m_SupportedTransports = 0; m_ReachableTransports = 0; - m_PublishedTransports = 0; - m_Caps = 0; m_IsFloodfill = false; + m_Caps = 0; // don't clean up m_Addresses, it will be replaced in ReadFromStream ClearProperties (); + // copy buffer + UpdateBuffer (buf, len); // skip identity size_t identityLen = m_RouterIdentity->GetFullLen (); // read new RI - ReadFromBuffer (buf + identityLen, len - identityLen); - if (!m_IsUnreachable) - UpdateBuffer (buf, len); // save buffer + std::stringstream str (std::string ((char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen)); + ReadFromStream (str); // don't delete buffer until saved to the file } else - { - LogPrint (eLogWarning, "RouterInfo: Updated signature verification failed. Not changed"); - return false; - } - return true; + { + LogPrint (eLogError, "RouterInfo: Signature verification failed"); + m_IsUnreachable = true; + } } void RouterInfo::SetRouterIdentity (std::shared_ptr identity) @@ -132,17 +127,16 @@ namespace data if (s.is_open ()) { s.seekg (0,std::ios::end); - size_t bufferLen = s.tellg (); - if (bufferLen < 40 || bufferLen > MAX_RI_BUFFER_SIZE) + m_BufferLen = s.tellg (); + if (m_BufferLen < 40 || m_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 (), bufferLen); - m_Buffer->SetBufferLen (bufferLen); + s.read((char *)m_Buffer->data (), m_BufferLen); } else { @@ -167,12 +161,11 @@ namespace data m_IsUnreachable = true; return; } - size_t bufferLen = m_Buffer->GetBufferLen (); - m_RouterIdentity = NewIdentity (m_Buffer->data (), bufferLen); + m_RouterIdentity = std::make_shared(m_Buffer->data (), m_BufferLen); size_t identityLen = m_RouterIdentity->GetFullLen (); - if (identityLen >= bufferLen) + if (identityLen >= m_BufferLen) { - LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", bufferLen); + LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", m_BufferLen); m_IsUnreachable = true; return; } @@ -186,45 +179,53 @@ namespace data return; } // verify signature - int l = bufferLen - m_RouterIdentity->GetSignatureLen (); + int l = m_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 - if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen)) + std::stringstream str; + str.write ((const char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen); + ReadFromStream (str); + if (!str) { LogPrint (eLogError, "RouterInfo: Malformed message"); m_IsUnreachable = true; - } + } } - bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len) + void RouterInfo::ReadFromStream (std::istream& s) { - if (len < 9) return false; - m_Caps = 0; m_Congestion = eLowCongestion; - m_Timestamp = bufbe64toh (buf); - size_t offset = 8; // timestamp + if (!s) return; + m_Caps = 0; + s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); + m_Timestamp = be64toh (m_Timestamp); // read addresses - auto addresses = NewAddresses (); - uint8_t numAddresses = buf[offset]; offset++; + auto addresses = boost::make_shared(); + uint8_t numAddresses; + s.read ((char *)&numAddresses, sizeof (numAddresses)); + addresses->reserve (numAddresses); 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 (); - offset++; // cost, ignore - address->date = bufbe64toh (buf + offset); offset += 8; // date - bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false; - auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1; - if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2 - address->transportStyle = eTransportNTCP2; - else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 + auto address = std::make_shared

(); + uint8_t cost; // ignore + s.read ((char *)&cost, sizeof (cost)); + s.read ((char *)&address->date, sizeof (address->date)); + bool isHost = false, isIntroKey = false, isStaticKey = false, isV2 = false; + Tag<32> iV2; // for 'i' field in SSU, TODO: remove later + char transportStyle[6]; + ReadString (transportStyle, 6, s); + if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 + address->transportStyle = eTransportNTCP; + else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2 { - address->transportStyle = eTransportSSU2; + address->transportStyle = (transportStyle[3] == '2') ? eTransportSSU2 : eTransportSSU; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } @@ -232,88 +233,70 @@ namespace data address->transportStyle = eTransportUnknown; address->caps = 0; address->port = 0; - if (offset + 2 > len) return false; - uint16_t size = bufbe16toh (buf + offset); offset += 2; // size - if (offset + size >= len) return false; + uint16_t size, r = 0; + s.read ((char *)&size, sizeof (size)); if (!s) return; + size = be16toh (size); if (address->transportStyle == eTransportUnknown) { // skip unknown address - offset += size; - continue; + s.seekg (size, std::ios_base::cur); + if (s) continue; else return; } - size_t r = 0; while (r < size) { - auto [key, value, sz] = ExtractParam (buf + offset, len - offset); - r += sz; offset += sz; - if (key.empty ()) continue; - if (key == "host") + 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")) { boost::system::error_code ecode; - address->host = boost::asio::ip::make_address (value, ecode); - if (!ecode && !address->host.is_unspecified ()) - { - if (!i2p::transport::transports.IsInReservedRange (address->host) || - i2p::util::net::IsYggdrasilAddress (address->host)) - isHost = true; - else - // we consider such address as invalid - address->transportStyle = eTransportUnknown; - } + address->host = boost::asio::ip::address::from_string (value, ecode); + if (!ecode && !address->host.is_unspecified ()) isHost = true; } - else if (key == "port") - { - auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); - if (res.ec != std::errc()) - LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); - } - else if (key == "mtu") + else if (!strcmp (key, "port")) + address->port = boost::lexical_cast(value); + else if (!strcmp (key, "mtu")) { if (address->ssu) - { - 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 ()); - } + address->ssu->mtu = boost::lexical_cast(value); else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP"); } - else if (key == "caps") - address->caps = ExtractAddressCaps (value); - else if (key == "s") // ntcp2 or ssu2 static key + else if (!strcmp (key, "key")) { - if (Base64ToByteStream (value, address->s, 32) == 32 && - !(address->s[31] & 0x80)) // check if x25519 public key - isStaticKey = true; + if (address->ssu) + isIntroKey = (Base64ToByteStream (value, strlen (value), address->i, 32) == 32); else - address->transportStyle = eTransportUnknown; // invalid address + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } - else if (key == "i") // ntcp2 iv or ssu2 intro + else if (!strcmp (key, "caps")) + address->caps = ExtractAddressCaps (value); + else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key + { + Base64ToByteStream (value, strlen (value), address->s, 32); + isStaticKey = true; + } + else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro { if (address->IsNTCP2 ()) { - if (Base64ToByteStream (value, address->i, 16) == 16) - address->published = true; // presence of "i" means "published" NTCP2 - else - address->transportStyle = eTransportUnknown; // invalid address + Base64ToByteStream (value, strlen (value), address->i, 16); + address->published = true; // presence of "i" means "published" NTCP2 } else if (address->IsSSU2 ()) - { - if (Base64ToByteStream (value, address->i, 32) == 32) - isIntroKey = true; - else - address->transportStyle = eTransportUnknown; // invalid address - } + Base64ToByteStream (value, strlen (value), address->i, 32); + else + Base64ToByteStream (value, strlen (value), iV2, 32); } - else if (key == "v") + else if (!strcmp (key, "v")) { - if (value == "2") + if (!strcmp (value, "2")) isV2 = true; else - { LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v"); - address->transportStyle = eTransportUnknown; // invalid address - } } else if (key[0] == 'i') { @@ -323,11 +306,13 @@ namespace data LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; } - unsigned char index = key[key.length () - 1] - '0'; // TODO: + size_t l = strlen(key); + unsigned char index = key[l-1] - '0'; // TODO: + key[l-1] = 0; if (index > 9) { LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); - continue; + if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) { @@ -336,38 +321,36 @@ namespace data address->ssu->introducers.resize (index + 1); } Introducer& introducer = address->ssu->introducers.at (index); - auto key1 = key.substr(0, key.length () - 1); - if (key1 == "itag") + if (!strcmp (key, "ihost")) { - auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); - if (res.ec != std::errc()) - LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ()); - } - else if (key1 == "ih") - Base64ToByteStream (value, introducer.iH, 32); - else if (key1 == "iexp") - { - auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); - if (res.ec != std::errc()) - LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); + boost::system::error_code ecode; + introducer.iHost = boost::asio::ip::address::from_string (value, ecode); } + else if (!strcmp (key, "iport")) + introducer.iPort = boost::lexical_cast(value); + else if (!strcmp (key, "itag")) + introducer.iTag = boost::lexical_cast(value); + else if (!strcmp (key, "ikey") || !strcmp (key, "ih")) + Base64ToByteStream (value, strlen (value), introducer.iKey, 32); + else if (!strcmp (key, "iexp")) + introducer.iExp = boost::lexical_cast(value); } - } - if (address->transportStyle == eTransportNTCP2) + if (!s) return; + } + if (address->transportStyle == eTransportNTCP) { if (isStaticKey) { - if (isHost && address->port) + if (isHost) { if (address->host.is_v6 ()) supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); else supportedTransports |= eNTCP2V4; - m_PublishedTransports |= supportedTransports; + m_ReachableTransports |= supportedTransports; } - else + else if (!address->published) { - address->published = false; if (address->caps) { if (address->caps & AddressCaps::eV4) supportedTransports |= eNTCP2V4; @@ -378,154 +361,177 @@ namespace data } } } - else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey) + else if (address->transportStyle == eTransportSSU) + { + if (isIntroKey) + { + if (isHost) + supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6; + else if (address->caps & AddressCaps::eV6) + { + supportedTransports |= eSSUV6; + if (address->caps & AddressCaps::eV4) supportedTransports |= eSSUV4; // in additional to v6 + } + else + supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4 + if (address->ssu && !address->ssu->introducers.empty ()) + { + // exclude invalid introducers + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + int numValid = 0; + for (auto& it: address->ssu->introducers) + { + if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; + if (ts <= it.iExp && it.iPort > 0 && + ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) + numValid++; + else + it.iPort = 0; + } + if (numValid) + m_ReachableTransports |= supportedTransports; + else + address->ssu->introducers.resize (0); + } + else if (isHost && address->port) + { + address->published = true; + m_ReachableTransports |= supportedTransports; + } + } + } + if (address->transportStyle == eTransportSSU2 || (isV2 && address->transportStyle == eTransportSSU)) { if (address->IsV4 ()) supportedTransports |= eSSU2V4; if (address->IsV6 ()) supportedTransports |= eSSU2V6; - if (isHost && address->port) + if (address->port) { - if (address->host.is_v4 ()) m_PublishedTransports |= eSSU2V4; - if (address->host.is_v6 ()) m_PublishedTransports |= eSSU2V6; - address->published = true; - } - else if (address->ssu && !address->ssu->introducers.empty ()) - { - // exclude invalid introducers - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - UpdateIntroducers (address, ts); - if (!address->ssu->introducers.empty ()) // still has something - m_ReachableTransports |= supportedTransports; + if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4; + if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6; } } if (supportedTransports) { if (!(m_SupportedTransports & supportedTransports)) // avoid duplicates { - for (uint8_t i = 0; i < eNumTransports; i++) - if ((1 << i) & supportedTransports) - (*addresses)[i] = address; + addresses->push_back(address); + if (address->transportStyle == eTransportSSU && isV2) + { + // create additional SSU2 address. TODO: remove later + auto ssu2addr = std::make_shared
(); + ssu2addr->transportStyle = eTransportSSU2; + ssu2addr->host = address->host; ssu2addr->port = address->port; + ssu2addr->s = address->s; ssu2addr->i = iV2; + ssu2addr->date = address->date; ssu2addr->caps = address->caps; + ssu2addr->published = address->published; + ssu2addr->ssu.reset (new SSUExt ()); ssu2addr->ssu->mtu = address->ssu->mtu; + for (const auto& introducer: address->ssu->introducers) + if (!introducer.iPort) // SSU2 + ssu2addr->ssu->introducers.push_back (introducer); + addresses->push_back(ssu2addr); + } } m_SupportedTransports |= supportedTransports; } } - m_ReachableTransports |= m_PublishedTransports; - // update addresses -#ifdef __cpp_lib_atomic_shared_ptr - m_Addresses = addresses; -#else +#if (BOOST_VERSION >= 105300) boost::atomic_store (&m_Addresses, addresses); +#else + m_Addresses = addresses; // race condition #endif // read peers - if (offset + 1 > len) return false; - uint8_t numPeers = buf[offset]; offset++; // num peers - offset += numPeers*32; // TODO: 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 // read properties - if (offset + 2 > len) return false; m_Version = 0; bool isNetId = false; std::string family; - uint16_t size = bufbe16toh (buf + offset); offset += 2; // size - if (offset + size > len) return false; - size_t r = 0; + uint16_t size, r = 0; + s.read ((char *)&size, sizeof (size)); if (!s) return; + size = be16toh (size); while (r < size) { - auto [key, value, sz] = ExtractParam (buf + offset, len - offset); - r += sz; offset += sz; - if (key.empty ()) continue; + 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; SetProperty (key, value); // extract caps - if (key == "caps") - { + if (!strcmp (key, "caps")) ExtractCaps (value); - m_IsFloodfill = IsDeclaredFloodfill (); - } // extract version - else if (key == ROUTER_INFO_PROPERTY_VERSION) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION)) { m_Version = 0; - for (auto ch: value) + char * ch = value; + while (*ch) { - 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 (key == ROUTER_INFO_PROPERTY_NETID) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID)) { isNetId = true; - int netID; - auto res = std::from_chars(value.data(), value.data() + value.size(), netID); - if (res.ec != std::errc() || netID != i2p::context.GetNetID ()) + if (atoi (value) != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } } // family - else if (key == ROUTER_INFO_PROPERTY_FAMILY) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) { family = value; boost::to_lower (family); } - else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) { - if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO + if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) 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 (std::string_view value) + void RouterInfo::ExtractCaps (const char * value) { - for (auto cap: value) + const char * cap = value; + while (*cap) { - switch (cap) + switch (*cap) { case CAPS_FLAG_FLOODFILL: m_Caps |= Caps::eFloodfill; break; - 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: + case CAPS_FLAG_HIGH_BANDWIDTH1: + case CAPS_FLAG_HIGH_BANDWIDTH2: + case CAPS_FLAG_HIGH_BANDWIDTH3: 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; @@ -536,26 +542,19 @@ 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 (std::string_view value) const + } + + uint8_t RouterInfo::ExtractAddressCaps (const char * value) const { uint8_t caps = 0; - for (auto cap: value) + const char * cap = value; + while (*cap) { - switch (cap) + switch (*cap) { case CAPS_FLAG_V4: caps |= AddressCaps::eV4; @@ -563,33 +562,19 @@ namespace data case CAPS_FLAG_V6: caps |= AddressCaps::eV6; break; - case CAPS_FLAG_SSU2_TESTING: + case CAPS_FLAG_SSU_TESTING: caps |= AddressCaps::eSSUTesting; break; - case CAPS_FLAG_SSU2_INTRODUCER: + case CAPS_FLAG_SSU_INTRODUCER: caps |= AddressCaps::eSSUIntroducer; break; default: ; } + cap++; } return caps; - } - - void RouterInfo::UpdateIntroducers (std::shared_ptr
address, uint64_t ts) - { - if (!address || !address->ssu) return; - int numValid = 0; - for (auto& it: address->ssu->introducers) - { - if (it.iTag && ts < it.iExp && !it.iH.IsZero ()) - numValid++; - else - it.iTag = 0; - } - if (!numValid) - address->ssu->introducers.resize (0); - } - + } + bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { if (!m_RouterIdentity) return false; @@ -604,174 +589,106 @@ namespace data { if (LoadFile (fullPath)) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); - else - return nullptr; } return m_Buffer->data (); } - bool RouterInfo::SaveToFile (const std::string& fullPath, std::shared_ptr buf) - { - if (!buf) return false; - std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) - { - LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath); - return false; - } - f.write ((char *)buf->data (), buf->GetBufferLen ()); - return true; - } - bool RouterInfo::SaveToFile (const std::string& fullPath) { - if (m_IsUnreachable) return false; // don't save bad router if (!m_Buffer) { - LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL"); + LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } - return SaveToFile (fullPath, m_Buffer); + 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; } - std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const + size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const { - uint8_t l = buf[0]; - if (l > len) + 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 { LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); - l = len; - } - return { (const char *)(buf + 1), l }; + s.seekg (l, std::ios::cur); // skip + str[0] = 0; + } + return l+1; } - 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) + + void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { auto addr = std::make_shared
(); + addr->host = boost::asio::ip::address::from_string (host); addr->port = port; - addr->transportStyle = eTransportNTCP2; - addr->caps = caps; + addr->transportStyle = eTransportSSU; + addr->published = true; + addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; - addr->published = false; - memcpy (addr->s, staticKey, 32); - memcpy (addr->i, iv, 16); - if (addr->IsV4 ()) - { - m_SupportedTransports |= eNTCP2V4; - (*GetAddresses ())[eNTCP2V4Idx] = addr; - } - if (addr->IsV6 ()) - { - m_SupportedTransports |= eNTCP2V6; - (*GetAddresses ())[eNTCP2V6Idx] = addr; - } + addr->ssu.reset (new SSUExt ()); + addr->ssu->mtu = mtu; + if (key) + memcpy (addr->i, key, 32); + else + RAND_bytes (addr->i, 32); + for (const auto& it: *m_Addresses) // don't insert same address twice + if (*it == *addr) return; + m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; + m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; + m_Addresses->push_back(std::move(addr)); } void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port) + const boost::asio::ip::address& host, int port, uint8_t caps) { auto addr = std::make_shared
(); addr->host = host; addr->port = port; - addr->transportStyle = eTransportNTCP2; + addr->transportStyle = eTransportNTCP; + addr->caps = caps; addr->date = 0; - addr->published = true; + if (port) addr->published = true; memcpy (addr->s, staticKey, 32); memcpy (addr->i, iv, 16); - addr->caps = 0; - if (host.is_unspecified ()) - { - if (host.is_v4 ()) addr->caps |= eV4; - if (host.is_v6 ()) addr->caps |= eV6; - } - auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eNTCP2V4; - m_ReachableTransports |= eNTCP2V4; - (*addresses)[eNTCP2V4Idx] = addr; + if (addr->published) m_ReachableTransports |= eNTCP2V4; } if (addr->IsV6 ()) { - if (i2p::util::net::IsYggdrasilAddress (addr->host)) - { - m_SupportedTransports |= eNTCP2V6Mesh; - m_ReachableTransports |= eNTCP2V6Mesh; - (*addresses)[eNTCP2V6MeshIdx] = addr; - } - else - { - m_SupportedTransports |= eNTCP2V6; - m_ReachableTransports |= eNTCP2V6; - (*addresses)[eNTCP2V6Idx] = addr; - } + m_SupportedTransports |= eNTCP2V6; + if (addr->published) m_ReachableTransports |= eNTCP2V6; } + m_Addresses->push_back(std::move(addr)); } - void RouterInfo::RemoveNTCP2Address (bool v4) - { - auto addresses = GetAddresses (); - if (v4) - { - if ((*addresses)[eNTCP2V6Idx]) - (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eNTCP2V4Idx].reset (); - } - else - { - if ((*addresses)[eNTCP2V4Idx]) - (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eNTCP2V6Idx].reset (); - } - UpdateSupportedTransports (); - } - - void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps) + void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps) { auto addr = std::make_shared
(); addr->transportStyle = eTransportSSU2; - addr->port = port; addr->caps = caps; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); - auto addresses = GetAddresses (); - if (addr->IsV4 ()) - { - m_SupportedTransports |= eSSU2V4; - (*addresses)[eSSU2V4Idx] = addr; - } - if (addr->IsV6 ()) - { - m_SupportedTransports |= eSSU2V6; - (*addresses)[eSSU2V6Idx] = addr; - } + if (addr->IsV4 ()) m_SupportedTransports |= eSSU2V4; + if (addr->IsV6 ()) m_SupportedTransports |= eSSU2V6; + m_Addresses->push_back(std::move(addr)); } void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, @@ -782,50 +699,68 @@ namespace data addr->host = host; addr->port = port; addr->published = true; + addr->caps = 0; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; memcpy (addr->s, staticKey, 32); memcpy (addr->i, introKey, 32); - if (!host.is_unspecified ()) - addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; - else - { - addr->caps = 0; - if (host.is_v4 ()) addr->caps |= eV4; - if (host.is_v6 ()) addr->caps |= eV6; - } - auto addresses = GetAddresses (); if (addr->IsV4 ()) { m_SupportedTransports |= eSSU2V4; m_ReachableTransports |= eSSU2V4; - (*addresses)[eSSU2V4Idx] = addr; } if (addr->IsV6 ()) { m_SupportedTransports |= eSSU2V6; m_ReachableTransports |= eSSU2V6; - (*addresses)[eSSU2V6Idx] = addr; } + m_Addresses->push_back(std::move(addr)); } - void RouterInfo::RemoveSSU2Address (bool v4) + bool RouterInfo::AddIntroducer (const Introducer& introducer) { - auto addresses = GetAddresses (); - if (v4) + for (auto& addr : *m_Addresses) { - if ((*addresses)[eSSU2V6Idx]) - (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eSSU2V4Idx].reset (); + if (addr->transportStyle == eTransportSSU && + ((addr->IsV4 () && introducer.iHost.is_v4 ()) || (addr->IsV6 () && introducer.iHost.is_v6 ()))) + { + for (auto& intro: addr->ssu->introducers) + if (intro.iTag == introducer.iTag) return false; // already presented + addr->ssu->introducers.push_back (introducer); + m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6); + return true; + } } + return false; + } + + bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) + { + for (auto& addr: *m_Addresses) + { + if (addr->transportStyle == eTransportSSU && + ((addr->IsV4 () && e.address ().is_v4 ()) || (addr->IsV6 () && e.address ().is_v6 ()))) + { + for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) + if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) + { + addr->ssu->introducers.erase (it); + if (addr->ssu->introducers.empty ()) + m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6); + return true; + } + } + } + return false; + } + + bool RouterInfo::IsSSU (bool v4only) const + { + if (v4only) + return m_SupportedTransports & eSSUV4; else - { - if ((*addresses)[eSSU2V4Idx]) - (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eSSU2V6Idx].reset (); - } - UpdateSupportedTransports (); + return m_SupportedTransports & (eSSUV4 | eSSUV6); } bool RouterInfo::IsNTCP2 (bool v4only) const @@ -864,18 +799,21 @@ namespace data { if (IsV6 ()) { - auto addresses = GetAddresses (); - if ((*addresses)[eNTCP2V6Idx]) + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx]) - (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eNTCP2V6Idx].reset (); - } - if ((*addresses)[eSSU2V6Idx]) - { - if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx]) - (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eSSU2V6Idx].reset (); + auto addr = *it; + if (addr->IsV6 ()) + { + if (addr->IsV4 ()) + { + addr->caps &= ~AddressCaps::eV6; + ++it; + } + else + it = m_Addresses->erase (it); + } + else + ++it; } UpdateSupportedTransports (); } @@ -885,18 +823,21 @@ namespace data { if (IsV4 ()) { - auto addresses = GetAddresses (); - if ((*addresses)[eNTCP2V4Idx]) + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx]) - (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eNTCP2V4Idx].reset (); - } - if ((*addresses)[eSSU2V4Idx]) - { - if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx]) - (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eSSU2V4Idx].reset (); + auto addr = *it; + if (addr->IsV4 ()) + { + if (addr->IsV6 ()) + { + addr->caps &= ~AddressCaps::eV4; + ++it; + } + else + it = m_Addresses->erase (it); + } + else + ++it; } UpdateSupportedTransports (); } @@ -917,97 +858,121 @@ namespace data { m_SupportedTransports &= ~eNTCP2V6Mesh; m_ReachableTransports &= ~eNTCP2V6Mesh; - (*GetAddresses ())[eNTCP2V6MeshIdx].reset (); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + { + auto addr = *it; + if (i2p::util::net::IsYggdrasilAddress (addr->host)) + it = m_Addresses->erase (it); + else + ++it; + } } } + std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const + { + return GetAddress ( + [v4only](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && (!v4only || address->IsV4 ()); + }); + } + + std::shared_ptr RouterInfo::GetSSUV6Address () const + { + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->IsV6(); + }); + } + std::shared_ptr RouterInfo::GetSSU2V4Address () const { - return (*GetAddresses ())[eSSU2V4Idx]; + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU2) && address->IsV4(); + }); } std::shared_ptr RouterInfo::GetSSU2V6Address () const { - return (*GetAddresses ())[eSSU2V6Idx]; - } - - std::shared_ptr RouterInfo::GetSSU2Address (bool v4) const - { - if (v4) - { - if (m_SupportedTransports & eSSU2V4) - return GetSSU2V4Address (); - } - else - { - if (m_SupportedTransports & eSSU2V6) - return GetSSU2V6Address (); - } - return nullptr; - } - - RouterInfo::AddressesPtr RouterInfo::GetAddresses () const - { -#ifdef __cpp_lib_atomic_shared_ptr - return m_Addresses; -#else - return boost::atomic_load (&m_Addresses); -#endif + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU2) && address->IsV6(); + }); } template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator -#ifdef __cpp_lib_atomic_shared_ptr - AddressesPtr addresses = m_Addresses; -#else +#if (BOOST_VERSION >= 105300) auto addresses = boost::atomic_load (&m_Addresses); +#else + auto addresses = m_Addresses; #endif for (const auto& address : *addresses) - if (address && filter (address)) return address; + if (filter (address)) return address; return nullptr; } - std::shared_ptr RouterInfo::GetNTCP2V4Address () const + std::shared_ptr RouterInfo::GetNTCP2AddressWithStaticKey (const uint8_t * key) const { - return (*GetAddresses ())[eNTCP2V4Idx]; + if (!key) return nullptr; + return GetAddress ( + [key](std::shared_ptr address)->bool + { + return address->IsNTCP2 () && !memcmp (address->s, key, 32); + }); } - std::shared_ptr RouterInfo::GetNTCP2V6Address () const + std::shared_ptr RouterInfo::GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const { - return (*GetAddresses ())[eNTCP2V6Idx]; + if (!key) return nullptr; + return GetAddress ( + [key, isV6](std::shared_ptr address)->bool + { + return address->IsSSU2 () && !memcmp (address->s, key, 32) && address->IsV6 () == isV6; + }); } std::shared_ptr RouterInfo::GetPublishedNTCP2V4Address () const { - auto addr = (*GetAddresses ())[eNTCP2V4Idx]; - if (addr && addr->IsPublishedNTCP2 ()) return addr; - return nullptr; + return GetAddress ( + [](std::shared_ptr address)->bool + { + return address->IsPublishedNTCP2 () && address->host.is_v4 (); + }); } std::shared_ptr RouterInfo::GetPublishedNTCP2V6Address () const { - auto addr = (*GetAddresses ())[eNTCP2V6Idx]; - if (addr && addr->IsPublishedNTCP2 ()) return addr; - return nullptr; + return GetAddress ( + [](std::shared_ptr address)->bool + { + return address->IsPublishedNTCP2 () && address->host.is_v6 () && + !i2p::util::net::IsYggdrasilAddress (address->host); + }); } std::shared_ptr RouterInfo::GetYggdrasilAddress () const { - return (*GetAddresses ())[eNTCP2V6MeshIdx]; + return GetAddress ( + [](std::shared_ptr address)->bool + { + return address->IsPublishedNTCP2 () && i2p::util::net::IsYggdrasilAddress (address->host); + }); } std::shared_ptr RouterInfo::GetProfile () const { - auto profile = m_Profile; - if (!profile) - { - profile = GetRouterProfile (GetIdentHash ()); - m_Profile = profile; - } - return profile; + if (!m_Profile) + m_Profile = GetRouterProfile (GetIdentHash ()); + return m_Profile; } void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted) const @@ -1019,49 +984,39 @@ namespace data bool RouterInfo::IsEligibleFloodfill () const { - // 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))) && + // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA + return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } - bool RouterInfo::IsPublished (bool v4) const + bool RouterInfo::IsPeerTesting (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; - auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - return addr && addr->IsPeerTesting () && addr->IsReachableSSU (); + if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; + return (bool)GetAddress ( + [v4](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->IsPeerTesting () && + ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU (); + }); } - bool RouterInfo::IsSSU2Introducer (bool v4) const + bool RouterInfo::IsIntroducer (bool v4) const { - if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; - auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - return addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port; + if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; + return (bool)GetAddress ( + [v4](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->IsIntroducer () && + ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); + }); } void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { - for (auto& addr: *GetAddresses ()) + for (auto& addr: *m_Addresses) { - if (addr && !addr->published) + // TODO: implement SSU + if (!addr->published && (addr->transportStyle == eTransportNTCP || addr->transportStyle == eTransportSSU2)) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; @@ -1073,131 +1028,47 @@ namespace data { m_SupportedTransports = 0; m_ReachableTransports = 0; - for (const auto& addr: *GetAddresses ()) + for (const auto& addr: *m_Addresses) { - if (!addr) continue; uint8_t transports = 0; - switch (addr->transportStyle) + if (addr->transportStyle == eTransportNTCP) { - case eTransportNTCP2: - if (addr->IsV4 ()) transports |= eNTCP2V4; - if (addr->IsV6 ()) - transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); - if (addr->IsPublishedNTCP2 ()) - m_ReachableTransports |= transports; - break; - case eTransportSSU2: - if (addr->IsV4 ()) transports |= eSSU2V4; - if (addr->IsV6 ()) transports |= eSSU2V6; - if (addr->IsReachableSSU ()) - m_ReachableTransports |= transports; - break; - default: ; + if (addr->IsV4 ()) transports |= eNTCP2V4; + if (addr->IsV6 ()) + transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); + if (addr->IsPublishedNTCP2 ()) + m_ReachableTransports |= transports; + } + else if (addr->transportStyle == eTransportSSU) + { + if (addr->IsV4 ()) transports |= eSSUV4; + if (addr->IsV6 ()) transports |= eSSUV6; + if (addr->IsReachableSSU ()) + m_ReachableTransports |= transports; } m_SupportedTransports |= transports; } } - void RouterInfo::UpdateIntroducers (uint64_t ts) - { - if (ts*1000 < m_Timestamp + INTRODUCER_UPDATE_INTERVAL) return; - if (m_ReachableTransports & eSSU2V4) - { - auto addr = (*GetAddresses ())[eSSU2V4Idx]; - if (addr && addr->UsesIntroducer ()) - { - UpdateIntroducers (addr, ts); - if (!addr->UsesIntroducer ()) // no more valid introducers - m_ReachableTransports &= ~eSSU2V4; - } - } - if (m_ReachableTransports & eSSU2V6) - { - auto addr = (*GetAddresses ())[eSSU2V6Idx]; - if (addr && addr->UsesIntroducer ()) - { - UpdateIntroducers (addr, ts); - if (!addr->UsesIntroducer ()) // no more valid introducers - m_ReachableTransports &= ~eSSU2V6; - } - } - } - void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len) { - m_IsBufferScheduledToDelete = false; if (!m_Buffer) m_Buffer = NewBuffer (); if (len > m_Buffer->size ()) len = m_Buffer->size (); memcpy (m_Buffer->data (), buf, len); - m_Buffer->SetBufferLen (len); + m_BufferLen = len; } - std::shared_ptr RouterInfo::CopyBuffer () const - { - if (!m_Buffer) return nullptr; - return netdb.NewRouterInfoBuffer (*m_Buffer); - } - std::shared_ptr RouterInfo::NewBuffer () const { return netdb.NewRouterInfoBuffer (); } - std::shared_ptr RouterInfo::NewAddress () const - { - return netdb.NewRouterInfoAddress (); - } - - RouterInfo::AddressesPtr RouterInfo::NewAddresses () const - { - return netdb.NewRouterInfoAddresses (); - } - - std::shared_ptr RouterInfo::NewIdentity (const uint8_t * buf, size_t len) const - { - return netdb.NewIdentity (buf, len); - } - void RouterInfo::RefreshTimestamp () { m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } - bool RouterInfo::IsHighCongestion (bool highBandwidth) const - { - switch (m_Congestion) - { - case eLowCongestion: - return false; - break; - case eMediumCongestion: - return highBandwidth; - break; - case eHighCongestion: - return i2p::util::GetMillisecondsSinceEpoch () < m_Timestamp + HIGH_CONGESTION_INTERVAL*1000LL; - break; - case eRejectAll: - return true; - break; - default: - return false; - } - } - - std::string RouterInfo::GetTransportName (SupportedTransports tr) - { - switch (tr) - { - case eNTCP2V4: return "NTCP2V4"; - case eNTCP2V6: return "NTCP2V6"; - case eSSU2V4: return "SSU2V4"; - case eSSU2V6: return "SSU2V6"; - case eNTCP2V6Mesh: return "Mesh"; - default: return ""; - } - } - void LocalRouterInfo::CreateBuffer (const PrivateKeys& privateKeys) { RefreshTimestamp (); @@ -1235,7 +1106,7 @@ namespace data CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else - caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O' + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else @@ -1243,107 +1114,112 @@ 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_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth + caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (c & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable - switch (GetCongestion ()) - { - case eMediumCongestion: - caps += CAPS_FLAG_MEDIUM_CONGESTION; - break; - case eHighCongestion: - caps += CAPS_FLAG_HIGH_CONGESTION; - break; - case eRejectAll: - caps += CAPS_FLAG_REJECT_ALL_CONGESTION; - break; - default: ; - }; - SetProperty ("caps", caps); } - bool LocalRouterInfo::UpdateCongestion (Congestion c) - { - if (c != GetCongestion ()) - { - SetCongestion (c); - UpdateCapsProperty (); - return true; - } - return false; - } - void LocalRouterInfo::WriteToStream (std::ostream& s) const { - auto addresses = GetAddresses (); - if (!addresses) return; - uint64_t ts = htobe64 (GetTimestamp ()); s.write ((const char *)&ts, sizeof (ts)); + // addresses - uint8_t numAddresses = 0; - for (size_t idx = 0; idx < addresses->size(); idx++) - { - auto addr_ptr = (*addresses)[idx]; - if (!addr_ptr) continue; - if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; - if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; - numAddresses++; - } + const Addresses& addresses = GetAddresses (); + uint8_t numAddresses = addresses.size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (size_t idx = 0; idx < addresses->size(); idx++) + for (const auto& addr_ptr : addresses) { - auto addr_ptr = (*addresses)[idx]; - if (!addr_ptr) continue; - if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; - if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; const Address& address = *addr_ptr; // calculate cost uint8_t cost = 0x7f; - if (address.transportStyle == eTransportNTCP2) + if (address.transportStyle == eTransportNTCP) cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; + else if (address.transportStyle == eTransportSSU) + cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS; else if (address.transportStyle == eTransportSSU2) cost = address.published ? COST_SSU2_DIRECT : COST_SSU2_NON_PUBLISHED; - else - continue; // skip unknown address s.write ((const char *)&cost, sizeof (cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; - bool isPublished = address.published && !address.host.is_unspecified () && address.port; - if (address.transportStyle == eTransportNTCP2) + bool isPublished = false; + if (address.transportStyle == eTransportNTCP) { - WriteString ("NTCP2", s); - // caps - if (!isPublished) + if (address.IsNTCP2 ()) { - 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 << ';'; + WriteString ("NTCP2", s); + if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) + isPublished = true; + else + { + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (caps.empty ()) caps += CAPS_FLAG_V4; + WriteString (caps, properties); + properties << ';'; + } } + else + continue; // don't write NTCP address + } + else if (address.transportStyle == eTransportSSU) + { + WriteString ("SSU", s); + // caps + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; + if (address.host.is_v4 ()) + { + if (address.published) + { + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + } + else + caps += CAPS_FLAG_V4; + } + else if (address.host.is_v6 ()) + { + if (address.published) + { + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + } + else + caps += CAPS_FLAG_V6; + } + else + { + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (caps.empty ()) caps += CAPS_FLAG_V4; + } + WriteString (caps, properties); + properties << ';'; } else if (address.transportStyle == eTransportSSU2) { WriteString ("SSU2", s); // caps std::string caps; - if (isPublished) + if (address.published) { - if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU2_TESTING; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU2_INTRODUCER; + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; } else { 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 (address.IsV6 ()) caps += CAPS_FLAG_V6; if (caps.empty ()) caps += CAPS_FLAG_V4; } if (!caps.empty ()) @@ -1357,7 +1233,7 @@ namespace data else WriteString ("", s); - if (isPublished && !address.host.is_unspecified ()) + if (isPublished) { WriteString ("host", properties); properties << '='; @@ -1371,7 +1247,7 @@ namespace data size_t len = address.IsSSU2 () ? 32 : 16; WriteString (address.i.ToBase64 (len), properties); properties << ';'; } - if (address.transportStyle == eTransportSSU2) + if (address.transportStyle == eTransportSSU || address.IsSSU2 ()) { // write introducers if any if (address.ssu && !address.ssu->introducers.empty()) @@ -1379,56 +1255,92 @@ namespace data int i = 0; for (const auto& introducer: address.ssu->introducers) { - if (!introducer.iTag) continue; if (introducer.iExp) // expiration is specified { - WriteString ("iexp" + std::to_string(i), properties); + WriteString ("iexp" + boost::lexical_cast(i), properties); properties << '='; - WriteString (std::to_string(introducer.iExp), properties); + WriteString (boost::lexical_cast(introducer.iExp), properties); properties << ';'; } i++; } - i = 0; - for (const auto& introducer: address.ssu->introducers) + if (address.transportStyle == eTransportSSU) { - if (!introducer.iTag) continue; - WriteString ("ih" + std::to_string(i), properties); - properties << '='; - auto value = ByteStreamToBase64 (introducer.iH, 32); - WriteString (value, properties); - properties << ';'; - i++; + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("ihost" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (introducer.iHost.to_string (), properties); + properties << ';'; + i++; + } } i = 0; for (const auto& introducer: address.ssu->introducers) { - if (!introducer.iTag) continue; - WriteString ("itag" + std::to_string(i), properties); + if (address.IsSSU2 ()) + WriteString ("ih" + boost::lexical_cast(i), properties); + else + WriteString ("ikey" + boost::lexical_cast(i), properties); properties << '='; - WriteString (std::to_string(introducer.iTag), properties); + char value[64]; + size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); + value[l] = 0; + WriteString (value, properties); + properties << ';'; + i++; + } + if (address.transportStyle == eTransportSSU) + { + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("iport" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iPort), properties); + properties << ';'; + i++; + } + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("itag" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iTag), properties); properties << ';'; i++; } } } - - if (address.transportStyle == eTransportSSU2) + if (address.transportStyle == eTransportSSU) + { + // write intro key + WriteString ("key", properties); + properties << '='; + char value[64]; + size_t l = ByteStreamToBase64 (address.i, 32, value, 64); + value[l] = 0; + WriteString (value, properties); + properties << ';'; + } + if (address.transportStyle == eTransportSSU || address.IsSSU2 ()) { // write mtu if (address.ssu && address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; - WriteString (std::to_string(address.ssu->mtu), properties); + WriteString (boost::lexical_cast(address.ssu->mtu), properties); properties << ';'; } } - if (isPublished && address.port) + if (isPublished || (address.ssu && !address.IsSSU2 ())) { WriteString ("port", properties); properties << '='; - WriteString (std::to_string(address.port), properties); + WriteString (boost::lexical_cast(address.port), properties); properties << ';'; } if (address.IsNTCP2 () || address.IsSSU2 ()) @@ -1463,11 +1375,9 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } - void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) + void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) { - auto [it, inserted] = m_Properties.emplace (key, value); - if (!inserted) - it->second = value; + m_Properties[key] = value; } void LocalRouterInfo::DeleteProperty (const std::string& key) @@ -1483,20 +1393,6 @@ 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 (); @@ -1508,73 +1404,5 @@ namespace data { return std::make_shared (); } - - std::shared_ptr LocalRouterInfo::NewAddress () const - { - return std::make_shared
(); - } - - RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const - { - return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); - } - - std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const - { - return std::make_shared (buf, len); - } - - bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) - { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) - { - for (auto& intro: addr->ssu->introducers) - if (intro.iTag == introducer.iTag) return false; // already presented - addr->ssu->introducers.push_back (introducer); - SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); - return true; - } - return false; - } - - bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) - { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) - { - for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) - if (h == it->iH) - { - addr->ssu->introducers.erase (it); - if (addr->ssu->introducers.empty ()) - SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6)); - return true; - } - } - return false; - } - - bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) - { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) - { - for (auto& it: addr->ssu->introducers) - if (h == it.iH) - { - it.iTag = iTag; - it.iExp = iExp; - return true; - } - } - return false; - } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index cb3ae499..a93f2a64 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,17 +11,12 @@ #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" @@ -44,57 +39,38 @@ 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_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_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_V4 = '4'; const char CAPS_FLAG_V6 = '6'; - const char CAPS_FLAG_SSU2_TESTING = 'B'; - const char CAPS_FLAG_SSU2_INTRODUCER = 'C'; + const char CAPS_FLAG_SSU_TESTING = 'B'; + const char CAPS_FLAG_SSU_INTRODUCER = 'C'; const uint8_t COST_NTCP2_PUBLISHED = 3; const uint8_t COST_NTCP2_NON_PUBLISHED = 14; const uint8_t COST_SSU2_DIRECT = 8; + const uint8_t COST_SSU_DIRECT = 9; + const uint8_t COST_SSU_THROUGH_INTRODUCERS = 11; const uint8_t COST_SSU2_NON_PUBLISHED = 15; const size_t MAX_RI_BUFFER_SIZE = 3072; // if RouterInfo exceeds 3K we consider it as malformed, might extend later - const int HIGH_CONGESTION_INTERVAL = 15*60; // in seconds, 15 minutes - const int INTRODUCER_UPDATE_INTERVAL = 20*60*1000; // in milliseconds, 20 minutes - class RouterInfo: public RoutingDestination { public: - enum SupportedTransportsIdx - { - eNTCP2V4Idx = 0, - eNTCP2V6Idx, - eSSU2V4Idx, - eSSU2V6Idx, - eNTCP2V6MeshIdx, - eNumTransports - }; - -#define TransportBit(tr) e##tr = (1 << e##tr##Idx) - enum SupportedTransports { - TransportBit(NTCP2V4), // 0x01 - TransportBit(NTCP2V6), // 0x02 - TransportBit(SSU2V4), // 0x04 - TransportBit(SSU2V6), // 0x08 - TransportBit(NTCP2V6Mesh), // 0x10 + eNTCP2V4 = 0x01, + eNTCP2V6 = 0x02, + eSSUV4 = 0x04, + eSSUV6 = 0x08, + eNTCP2V6Mesh = 0x10, + eSSU2V4 = 0x20, + eSSU2V6 = 0x40, eAllTransports = 0xFF }; typedef uint8_t CompatibleTransports; @@ -109,14 +85,6 @@ namespace data eUnreachable = 0x20 }; - enum Congestion - { - eLowCongestion = 0, - eMediumCongestion, - eHighCongestion, - eRejectAll - }; - enum AddressCaps { eV4 = 0x01, @@ -128,14 +96,18 @@ namespace data enum TransportStyle { eTransportUnknown = 0, - eTransportNTCP2, + eTransportNTCP, + eTransportSSU, eTransportSSU2 }; + typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { - Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; - IdentHash iH; + Introducer (): iPort (0), iExp (0) {}; + boost::asio::ip::address iHost; + int iPort; + IntroKey iKey; // or ih for SSU2 uint32_t iTag; uint32_t iExp; }; @@ -174,7 +146,7 @@ namespace data return !(*this == other); } - bool IsNTCP2 () const { return transportStyle == eTransportNTCP2; }; + bool IsNTCP2 () const { return transportStyle == eTransportNTCP; }; bool IsSSU2 () const { return transportStyle == eTransportSSU2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; bool IsReachableSSU () const { return (bool)ssu && (published || UsesIntroducer ()); }; @@ -193,25 +165,13 @@ 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 + typedef std::vector > Addresses; + RouterInfo (const std::string& fullPath); - RouterInfo (const RouterInfo& ) = delete; - RouterInfo& operator=(const RouterInfo& ) = delete; + RouterInfo (const RouterInfo& ) = default; + RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (std::shared_ptr&& buf, size_t len); RouterInfo (const uint8_t * buf, size_t len); virtual ~RouterInfo (); @@ -221,39 +181,40 @@ 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 (std::string_view key, std::string_view value) {}; + virtual void SetProperty (const std::string& key, const std::string& value) {}; virtual void ClearProperties () {}; - 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; + Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr + std::shared_ptr GetNTCP2AddressWithStaticKey (const uint8_t * key) const; + std::shared_ptr GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; + std::shared_ptr GetSSUAddress (bool v4only = true) const; + std::shared_ptr GetSSUV6Address () const; std::shared_ptr GetYggdrasilAddress () const; std::shared_ptr GetSSU2V4Address () const; std::shared_ptr GetSSU2V6Address () const; - std::shared_ptr GetSSU2Address (bool v4) const; - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps); // non published + void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port); // published - void RemoveNTCP2Address (bool v4); - void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps); // non published + const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); + void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps = 0); // non published void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, const boost::asio::ip::address& host, int port); // published - void RemoveSSU2Address (bool v4); + bool AddIntroducer (const Introducer& introducer); + bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps void UpdateSupportedTransports (); - 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 IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; + bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; + bool IsSSU (bool v4only = true) const; + bool IsSSUV6 () const { return m_SupportedTransports & eSSUV6; }; bool IsNTCP2 (bool v4only = true) const; bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; }; bool IsSSU2V4 () const { return m_SupportedTransports & eSSU2V4; }; bool IsSSU2V6 () const { return m_SupportedTransports & eSSU2V6; }; - bool IsV6 () const { return m_SupportedTransports & (eNTCP2V6 | eSSU2V6); }; - bool IsV4 () const { return m_SupportedTransports & (eNTCP2V4 | eSSU2V4); }; + bool IsV6 () const { return m_SupportedTransports & (eSSUV6 | eNTCP2V6 | eSSU2V6); }; + bool IsV4 () const { return m_SupportedTransports & (eSSUV4 | eNTCP2V4 | eSSU2V4); }; bool IsMesh () const { return m_SupportedTransports & eNTCP2V6Mesh; }; void EnableV6 (); void DisableV6 (); @@ -265,50 +226,33 @@ 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; + bool IsPeerTesting (bool v4) const; + bool IsIntroducer (bool v4) 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 * GetBuffer () const { return m_Buffer->data (); }; const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary - 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; }; + size_t GetBufferLen () const { return m_BufferLen; }; 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); + std::shared_ptr GetProfile () const; + void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; + + void 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 */ @@ -325,52 +269,36 @@ 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) { if (m_Buffer) m_Buffer->SetBufferLen (len); }; + void SetBufferLen (size_t len) { m_BufferLen = len; }; void RefreshTimestamp (); - CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; - void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; - void SetCongestion (Congestion c) { m_Congestion = c; }; - + const Addresses& GetAddresses () const { return *m_Addresses; }; + private: bool LoadFile (const std::string& fullPath); void ReadFromFile (const std::string& fullPath); - bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed + void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); - 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); + size_t ReadString (char* str, size_t len, std::istream& s) const; + void ExtractCaps (const char * value); + uint8_t ExtractAddressCaps (const char * value) const; template std::shared_ptr GetAddress (Filter filter) const; virtual std::shared_ptr NewBuffer () const; - virtual std::shared_ptr
NewAddress () const; - virtual AddressesPtr NewAddresses () const; - virtual std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const; private: FamilyID m_FamilyID; std::shared_ptr m_RouterIdentity; std::shared_ptr m_Buffer; - 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; + 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; 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 @@ -380,17 +308,11 @@ namespace data LocalRouterInfo () = default; void CreateBuffer (const PrivateKeys& privateKeys); void UpdateCaps (uint8_t caps); - bool UpdateCongestion (Congestion c); // returns true if updated - void SetProperty (std::string_view key, std::string_view value) override; + void SetProperty (const std::string& key, const std::string& 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: @@ -398,9 +320,6 @@ namespace data void UpdateCapsProperty (); void WriteString (const std::string& str, std::ostream& s) const; std::shared_ptr NewBuffer () const override; - std::shared_ptr
NewAddress () const override; - RouterInfo::AddressesPtr NewAddresses () const override; - std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp new file mode 100644 index 00000000..eec55857 --- /dev/null +++ b/libi2pd/SSU.cpp @@ -0,0 +1,996 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "NetDb.hpp" +#include "Config.h" +#include "util.h" +#include "SSU.h" + +#if defined(__linux__) && !defined(_NETINET_IN_H) + #include +#endif + +#ifdef _WIN32 +#include +#endif + +namespace i2p +{ +namespace transport +{ + SSUServer::SSUServer (int port): + m_IsRunning(false), m_Thread (nullptr), + m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), + m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), + m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), + m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), + m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service), + m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service), + m_IsSyncClockFromPeers (true) + { + } + + SSUServer::~SSUServer () + { + } + + void SSUServer::OpenSocket () + { + try + { + m_Socket.open (boost::asio::ip::udp::v4()); + m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); + m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); + m_Socket.bind (m_Endpoint); + LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); + } + catch ( std::exception & ex ) + { + LogPrint (eLogError, "SSU: Failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); + ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); + } + } + + void SSUServer::OpenSocketV6 () + { + try + { + m_SocketV6.open (boost::asio::ip::udp::v6()); + m_SocketV6.set_option (boost::asio::ip::v6_only (true)); + m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); + m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); +#if defined(__linux__) && !defined(_NETINET_IN_H) + if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address + { + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif + m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); + } +#endif + m_SocketV6.bind (m_EndpointV6); + LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); + } + catch ( std::exception & ex ) + { + LogPrint (eLogError, "SSU: Failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); + ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); + } + } + + void SSUServer::Start () + { + i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); + if (context.SupportsV4 ()) + { + OpenSocket (); + m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); + m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); + ScheduleTermination (); + ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers + } + if (context.SupportsV6 ()) + { + OpenSocketV6 (); + m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); + m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); + ScheduleTerminationV6 (); + ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers + } + SchedulePeerTestsCleanupTimer (); + } + + void SSUServer::Stop () + { + DeleteAllSessions (); + m_IsRunning = false; + m_TerminationTimer.cancel (); + m_TerminationTimerV6.cancel (); + m_IntroducersUpdateTimer.cancel (); + m_IntroducersUpdateTimerV6.cancel (); + m_Service.stop (); + m_Socket.close (); + m_SocketV6.close (); + m_ReceiversService.stop (); + m_ReceiversServiceV6.stop (); + if (m_ReceiversThread) + { + m_ReceiversThread->join (); + delete m_ReceiversThread; + m_ReceiversThread = nullptr; + } + if (m_ReceiversThreadV6) + { + m_ReceiversThreadV6->join (); + delete m_ReceiversThreadV6; + m_ReceiversThreadV6 = nullptr; + } + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + + void SSUServer::Run () + { + i2p::util::SetThreadName("SSU"); + + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: Server runtime exception: ", ex.what ()); + } + } + } + + void SSUServer::RunReceivers () + { + i2p::util::SetThreadName("SSUv4"); + + while (m_IsRunning) + { + try + { + m_ReceiversService.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: Receivers runtime exception: ", ex.what ()); + if (m_IsRunning) + { + // restart socket + m_Socket.close (); + OpenSocket (); + Receive (); + } + } + } + } + + void SSUServer::RunReceiversV6 () + { + i2p::util::SetThreadName("SSUv6"); + + while (m_IsRunning) + { + try + { + m_ReceiversServiceV6.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); + if (m_IsRunning) + { + m_SocketV6.close (); + OpenSocketV6 (); + ReceiveV6 (); + } + } + } + } + + void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress) + { + if (localAddress.is_v6 ()) + m_EndpointV6.address (localAddress); + else if (localAddress.is_v4 ()) + m_Endpoint.address (localAddress); + } + + void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) + { + m_Relays.emplace (tag, relay); + } + + void SSUServer::RemoveRelay (uint32_t tag) + { + m_Relays.erase (tag); + } + + std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) + { + auto it = m_Relays.find (tag); + if (it != m_Relays.end ()) + { + if (it->second->GetState () == eSessionStateEstablished) + return it->second; + else + m_Relays.erase (it); + } + return nullptr; + } + + void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) + { + boost::system::error_code ec; + if (to.protocol () == boost::asio::ip::udp::v4()) + m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec); + else + m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec); + + if (ec) + { + LogPrint (eLogError, "SSU: Send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")"); + } + } + + void SSUServer::Receive () + { + SSUPacket * packet = m_PacketsPool.AcquireMt (); + m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, + std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); + } + + void SSUServer::ReceiveV6 () + { + SSUPacket * packet = m_PacketsPool.AcquireMt (); + m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, + std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); + } + + void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) + { + if (!ecode + || ecode == boost::asio::error::connection_refused + || ecode == boost::asio::error::connection_reset + || ecode == boost::asio::error::network_unreachable + || ecode == boost::asio::error::host_unreachable +#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO + || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ + || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ + || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ +#endif + ) + // just try continue reading when received ICMP response otherwise socket can crash, + // but better to find out which host were sent it and mark that router as unreachable + { + packet->len = bytes_transferred; + std::vector packets; + packets.push_back (packet); + + boost::system::error_code ec; + size_t moreBytes = m_Socket.available(ec); + if (!ec) + { + while (moreBytes && packets.size () < 25) + { + packet = m_PacketsPool.AcquireMt (); + packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); + if (!ec) + { + packets.push_back (packet); + moreBytes = m_Socket.available(ec); + if (ec) break; + } + else + { + LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); + m_PacketsPool.ReleaseMt (packet); + break; + } + } + } + + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); + Receive (); + } + else + { + m_PacketsPool.ReleaseMt (packet); + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogError, "SSU: Receive error: code ", ecode.value(), ": ", ecode.message ()); + m_Socket.close (); + OpenSocket (); + Receive (); + } + } + } + + void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) + { + if (!ecode + || ecode == boost::asio::error::connection_refused + || ecode == boost::asio::error::connection_reset + || ecode == boost::asio::error::network_unreachable + || ecode == boost::asio::error::host_unreachable +#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO + || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ + || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ + || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ +#endif + ) + // just try continue reading when received ICMP response otherwise socket can crash, + // but better to find out which host were sent it and mark that router as unreachable + { + packet->len = bytes_transferred; + std::vector packets; + packets.push_back (packet); + + boost::system::error_code ec; + size_t moreBytes = m_SocketV6.available (ec); + if (!ec) + { + while (moreBytes && packets.size () < 25) + { + packet = m_PacketsPool.AcquireMt (); + packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); + if (!ec) + { + packets.push_back (packet); + moreBytes = m_SocketV6.available(ec); + if (ec) break; + } + else + { + LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); + m_PacketsPool.ReleaseMt (packet);; + break; + } + } + } + + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); + ReceiveV6 (); + } + else + { + m_PacketsPool.ReleaseMt (packet); + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); + m_SocketV6.close (); + OpenSocketV6 (); + ReceiveV6 (); + } + } + } + + void SSUServer::HandleReceivedPackets (std::vector packets, + std::map > * sessions) + { + if (!m_IsRunning) return; + std::shared_ptr session; + for (auto& packet: packets) + { + try + { + if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous + { + if (session) + { + session->FlushData (); + session = nullptr; + } + auto it = sessions->find (packet->from); + if (it != sessions->end ()) + session = it->second; + if (!session && packet->len > 0) + { + session = std::make_shared (*this, packet->from); + session->WaitForConnect (); + (*sessions)[packet->from] = session; + LogPrint (eLogDebug, "SSU: New session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + } + } + if (session) + session->ProcessNextMessage (packet->buf, packet->len, packet->from); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); + if (session) session->FlushData (); + session = nullptr; + } + } + m_PacketsPool.ReleaseMt (packets); + if (session) session->FlushData (); + } + + std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const + { + auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; + auto it = sessions.find (e); + if (it != sessions.end ()) + return it->second; + else + return nullptr; + } + + bool SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) + { + auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); + if (address) + return CreateSession (router, address, peerTest); + else + LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + return false; + } + + bool SSUServer::CreateSession (std::shared_ptr router, + std::shared_ptr address, bool peerTest) + { + if (router && address) + { + if (address->UsesIntroducer ()) + m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread + else + { + if (address->host.is_unspecified () || !address->port) return false; + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); + } + } + else + return false; + return true; + } + + void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) + { + auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; + auto it = sessions.find (remoteEndpoint); + if (it != sessions.end ()) + { + auto session = it->second; + if (peerTest && session->GetState () == eSessionStateEstablished) + session->SendPeerTest (); + } + else + { + // otherwise create new session + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + sessions[remoteEndpoint] = session; + + // connect + LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", + remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); + session->Connect (); + } + } + + void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, + std::shared_ptr address, bool peerTest) + { + if (router && address && address->UsesIntroducer ()) + { + if (address->IsV4 () && !i2p::context.SupportsV4 ()) return; + if (address->IsV6 () && !i2p::context.SupportsV6 ()) return; + if (!address->host.is_unspecified () && address->port) + { + // we rarely come here + auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions; + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto it = sessions.find (remoteEndpoint); + // check if session is presented already + if (it != sessions.end ()) + { + auto session = it->second; + if (peerTest && session->GetState () == eSessionStateEstablished) + session->SendPeerTest (); + return; + } + } + // create new session + int numIntroducers = address->ssu->introducers.size (); + if (numIntroducers > 0) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::shared_ptr introducerSession; + const i2p::data::RouterInfo::Introducer * introducer = nullptr; + // we might have a session to introducer already + auto offset = rand (); + for (int i = 0; i < numIntroducers; i++) + { + auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]); + if (!intr->iPort) continue; // skip invalid introducer + if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer + boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); + if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4 + { + if (!introducer) introducer = intr; + auto it = m_Sessions.find (ep); + if (it != m_Sessions.end ()) + { + introducerSession = it->second; + break; + } + } + if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6 + { + if (!introducer) introducer = intr; + auto it = m_SessionsV6.find (ep); + if (it != m_SessionsV6.end ()) + { + introducerSession = it->second; + break; + } + } + } + if (!introducer) + { + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented"); + return; + } + + if (introducerSession) // session found + LogPrint (eLogWarning, "SSU: Session to introducer already exists"); + else // create new + { + LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); + boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); + introducerSession = std::make_shared (*this, introducerEndpoint, router); + if (introducerEndpoint.address ().is_v4 ()) + m_Sessions[introducerEndpoint] = introducerSession; + else if (introducerEndpoint.address ().is_v6 ()) + m_SessionsV6[introducerEndpoint] = introducerSession; + } + if (!address->host.is_unspecified () && address->port) + { + // create session + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + if (address->host.is_v4 ()) + m_Sessions[remoteEndpoint] = session; + else if (address->host.is_v6 ()) + m_SessionsV6[remoteEndpoint] = session; + + // introduce + LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), + "] through introducer ", introducer->iHost, ":", introducer->iPort); + session->WaitForIntroduction (); + if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || + (address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) + { + uint8_t buf[1]; + Send (buf, 0, remoteEndpoint); // send HolePunch + } + } + introducerSession->Introduce (*introducer, router); + } + else + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); + } + } + + void SSUServer::DeleteSession (std::shared_ptr session) + { + if (session) + { + session->Close (); + auto& ep = session->GetRemoteEndpoint (); + if (ep.address ().is_v6 ()) + m_SessionsV6.erase (ep); + else + m_Sessions.erase (ep); + } + } + + void SSUServer::DeleteAllSessions () + { + for (auto& it: m_Sessions) + it.second->Close (); + m_Sessions.clear (); + + for (auto& it: m_SessionsV6) + it.second->Close (); + m_SessionsV6.clear (); + } + + template + std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only + { + std::vector > filteredSessions; + for (const auto& s :m_Sessions) + if (filter (s.second)) filteredSessions.push_back (s.second); + if (filteredSessions.size () > 0) + { + auto ind = rand () % filteredSessions.size (); + return filteredSessions[ind]; + } + return nullptr; + } + + std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only + { + return GetRandomV4Session ( + [excluded](std::shared_ptr session)->bool + { + return session->GetState () == eSessionStateEstablished && session != excluded; + } + ); + } + + template + std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only + { + std::vector > filteredSessions; + for (const auto& s :m_SessionsV6) + if (filter (s.second)) filteredSessions.push_back (s.second); + if (filteredSessions.size () > 0) + { + auto ind = rand () % filteredSessions.size (); + return filteredSessions[ind]; + } + return nullptr; + } + + std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only + { + return GetRandomV6Session ( + [excluded](std::shared_ptr session)->bool + { + return session->GetState () == eSessionStateEstablished && session != excluded; + } + ); + } + + std::list > SSUServer::FindIntroducers (int maxNumIntroducers, + bool v4, std::set& excluded) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::list > ret; + const auto& sessions = v4 ? m_Sessions : m_SessionsV6; + for (const auto& s : sessions) + { + if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished && + ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) + ret.push_back (s.second); + else if (s.second->GetRemoteIdentity ()) + excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ()); + } + if ((int)ret.size () > maxNumIntroducers) + { + // shink ret randomly + int sz = ret.size () - maxNumIntroducers; + for (int i = 0; i < sz; i++) + { + auto ind = rand () % ret.size (); + auto it = ret.begin (); + std::advance (it, ind); + ret.erase (it); + } + } + return ret; + } + + void SSUServer::RescheduleIntroducersUpdateTimer () + { + m_IntroducersUpdateTimer.cancel (); + m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); + m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, + this, std::placeholders::_1, true)); + } + + void SSUServer::ScheduleIntroducersUpdateTimer () + { + m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); + m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, + this, std::placeholders::_1, true)); + } + + void SSUServer::RescheduleIntroducersUpdateTimerV6 () + { + m_IntroducersUpdateTimerV6.cancel (); + m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); + m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, + this, std::placeholders::_1, false)); + } + + void SSUServer::ScheduleIntroducersUpdateTimerV6 () + { + m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); + m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, + this, std::placeholders::_1, false)); + } + + void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) + { + if (ecode != boost::asio::error::operation_aborted) + { + // timeout expired + if (v4) + { + if (i2p::context.GetStatus () == eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimer (); + return; + } + if (i2p::context.GetStatus () != eRouterStatusFirewalled) + { + // we don't need introducers + m_Introducers.clear (); + return; + } + // we are firewalled + if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4 + } + else + { + if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimerV6 (); + return; + } + if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) + { + // we don't need introducers + m_IntroducersV6.clear (); + return; + } + // we are firewalled + auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address (); + if (addr && addr->ssu && addr->ssu->introducers.empty ()) + i2p::context.SetUnreachable (false, true); // v6 + } + + std::list newList; + size_t numIntroducers = 0; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::set excluded; + auto& introducers = v4 ? m_Introducers : m_IntroducersV6; + for (const auto& it : introducers) + { + auto session = FindSession (it); + if (session) + { + if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) + session->SendKeepAlive (); + if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) + { + newList.push_back (it); + numIntroducers++; + if (session->GetRemoteIdentity ()) + excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ()); + } + else + session = nullptr; + } + if (!session) + i2p::context.RemoveIntroducer (it); + } + if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) + { + // create new + auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates + if (sessions.empty () && !introducers.empty ()) + { + // bump creation time for previous introducers if no new sessions found + LogPrint (eLogDebug, "SSU: No new introducers found. Trying to reuse existing"); + for (const auto& it : introducers) + { + auto session = FindSession (it); + if (session) + session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION); + } + // try again + excluded.clear (); + sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); + } + for (const auto& it1: sessions) + { + const auto& ep = it1->GetRemoteEndpoint (); + i2p::data::RouterInfo::Introducer introducer; + introducer.iHost = ep.address (); + introducer.iPort = ep.port (); + introducer.iTag = it1->GetRelayTag (); + introducer.iKey = it1->GetIntroKey (); + introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION; + if (i2p::context.AddIntroducer (introducer)) + { + newList.push_back (ep); + if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; + } + if (it1->GetRemoteIdentity ()) + excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ()); + } + } + introducers = newList; + if (introducers.size () < SSU_MAX_NUM_INTRODUCERS) + { + for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) + { + auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded); + if (introducer) + { + auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address (); + if (address && !address->host.is_unspecified () && address->port) + { + boost::asio::ip::udp::endpoint ep (address->host, address->port); + if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet + { + CreateDirectSession (introducer, ep, false); + excluded.insert (introducer->GetIdentHash ()); + } + } + } + else + { + LogPrint (eLogDebug, "SSU: Can't find more introducers"); + break; + } + } + } + if (v4) + ScheduleIntroducersUpdateTimer (); + else + ScheduleIntroducersUpdateTimerV6 (); + } + } + + void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) + { + m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; + } + + PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + return it->second.role; + else + return ePeerTestParticipantUnknown; + } + + std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + return it->second.session; + else + return nullptr; + } + + void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + it->second.role = role; + } + + void SSUServer::RemovePeerTest (uint32_t nonce) + { + m_PeerTests.erase (nonce); + } + + void SSUServer::SchedulePeerTestsCleanupTimer () + { + m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); + m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, + this, std::placeholders::_1)); + } + + void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + int numDeleted = 0; + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) + { + numDeleted++; + it = m_PeerTests.erase (it); + } + else + ++it; + } + if (numDeleted > 0) + LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); + // some cleaups. TODO: use separate timer + m_FragmentsPool.CleanUp (); + m_IncompleteMessagesPool.CleanUp (); + m_SentMessagesPool.CleanUp (); + + SchedulePeerTestsCleanupTimer (); + } + } + + void SSUServer::ScheduleTermination () + { + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout)); + m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, + this, std::placeholders::_1)); + } + + void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: m_Sessions) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + if (it.first != session->GetRemoteEndpoint ()) + LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted"); + m_Service.post ([session] + { + LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); + session->Failed (); + }); + } + else + it.second->CleanUp (ts); + ScheduleTermination (); + } + } + + void SSUServer::ScheduleTerminationV6 () + { + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout)); + m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, + this, std::placeholders::_1)); + } + + void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: m_SessionsV6) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + if (it.first != session->GetRemoteEndpoint ()) + LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); + m_Service.post ([session] + { + LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); + session->Failed (); + }); + } + else + it.second->CleanUp (ts); + ScheduleTerminationV6 (); + } + } +} +} diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h new file mode 100644 index 00000000..25ce4d40 --- /dev/null +++ b/libi2pd/SSU.h @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef SSU_H__ +#define SSU_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Crypto.h" +#include "util.h" +#include "I2PEndian.h" +#include "Identity.h" +#include "RouterInfo.h" +#include "I2NPProtocol.h" +#include "SSUSession.h" + +namespace i2p +{ +namespace transport +{ + const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds + const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds + const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour + const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes + const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const size_t SSU_MAX_NUM_INTRODUCERS = 3; + const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K + const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K + + struct SSUPacket + { + i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size + boost::asio::ip::udp::endpoint from; + size_t len; + }; + + class SSUServer + { + public: + + SSUServer (int port); + ~SSUServer (); + void Start (); + void Stop (); + bool CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); + bool CreateSession (std::shared_ptr router, + std::shared_ptr address, bool peerTest = false); + void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); + std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; + std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); + std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); + void DeleteSession (std::shared_ptr session); + void DeleteAllSessions (); + + boost::asio::io_service& GetService () { return m_Service; }; + i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; + i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; + i2p::util::MemoryPool& GetSentMessagesPool () { return m_SentMessagesPool; }; + + uint16_t GetPort () const { return m_Endpoint.port (); }; + bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; + void SetLocalAddress (const boost::asio::ip::address& localAddress); + + void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); + void AddRelay (uint32_t tag, std::shared_ptr relay); + void RemoveRelay (uint32_t tag); + std::shared_ptr FindRelaySession (uint32_t tag); + void RescheduleIntroducersUpdateTimer (); + void RescheduleIntroducersUpdateTimerV6 (); + + void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); + PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); + std::shared_ptr GetPeerTestSession (uint32_t nonce); + void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); + void RemovePeerTest (uint32_t nonce); + + private: + + void OpenSocket (); + void OpenSocketV6 (); + void Run (); + void RunReceivers (); + void RunReceiversV6 (); + void Receive (); + void ReceiveV6 (); + void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); + void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); + void HandleReceivedPackets (std::vector packets, + std::map >* sessions); + + void CreateSessionThroughIntroducer (std::shared_ptr router, + std::shared_ptr address, bool peerTest = false); + template + std::shared_ptr GetRandomV4Session (Filter filter); + template + std::shared_ptr GetRandomV6Session (Filter filter); + + std::list > FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded); + void ScheduleIntroducersUpdateTimer (); + void ScheduleIntroducersUpdateTimerV6 (); + void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); + + void SchedulePeerTestsCleanupTimer (); + void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); + + // timer + void ScheduleTermination (); + void HandleTerminationTimer (const boost::system::error_code& ecode); + void ScheduleTerminationV6 (); + void HandleTerminationTimerV6 (const boost::system::error_code& ecode); + + private: + + struct PeerTest + { + uint64_t creationTime; + PeerTestParticipant role; + std::shared_ptr session; // for Bob to Alice + }; + + volatile bool m_IsRunning; + std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; + boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; + boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; + boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; + boost::asio::ip::udp::socket m_Socket, m_SocketV6; + boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6, + m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; + bool m_IsSyncClockFromPeers; + std::list m_Introducers, m_IntroducersV6; // introducers we are connected to + std::map > m_Sessions, m_SessionsV6; + std::map > m_Relays; // we are introducer + std::map m_PeerTests; // nonce -> creation time in milliseconds + + i2p::util::MemoryPool m_FragmentsPool; + i2p::util::MemoryPool m_IncompleteMessagesPool; + i2p::util::MemoryPool m_SentMessagesPool; + i2p::util::MemoryPoolMt m_PacketsPool; + + public: + // for HTTP only + const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; + const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; + }; +} +} + +#endif diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 4540b4d2..6d14bbd7 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -1,32 +1,1682 @@ /* -* Copyright (c) 2022-2025, The PurpleI2P Project +* Copyright (c) 2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ -#include +#include +#include #include "Log.h" #include "RouterContext.h" #include "Transports.h" -#include "NetDb.hpp" #include "Config.h" +#include "Gzip.h" +#include "NetDb.hpp" #include "SSU2.h" namespace i2p { namespace transport { + static 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; + } + + SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter, + std::shared_ptr addr): + TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), + m_Server (server), m_Address (addr), m_DestConnID (0), m_SourceConnID (0), + m_State (eSSU2SessionStateUnknown), m_SendPacketNum (0), m_ReceivePacketNum (0), + m_IsDataReceived (false), m_WindowSize (SSU2_MAX_WINDOW_SIZE), m_RelayTag (0) + { + m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); + if (in_RemoteRouter && m_Address) + { + // outgoing + InitNoiseXKState1 (*m_NoiseState, m_Address->s); + m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port); + RAND_bytes ((uint8_t *)&m_DestConnID, 8); + RAND_bytes ((uint8_t *)&m_SourceConnID, 8); + } + else + { + // incoming + InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); + } + } + + SSU2Session::~SSU2Session () + { + } + + void SSU2Session::Connect () + { + auto token = m_Server.FindOutgoingToken (m_RemoteEndpoint); + if (token) + SendSessionRequest (token); + else + SendTokenRequest (); + } + + bool SSU2Session::Introduce (std::shared_ptr session, uint32_t relayTag) + { + // we are Alice + if (!session || !relayTag) return false; + // find local adddress to introduce + std::shared_ptr localAddress; + if (session->m_Address->IsV4 ()) + localAddress = i2p::context.GetRouterInfo ().GetSSU2V4Address (); + else if (session->m_Address->IsV6 ()) + localAddress = i2p::context.GetRouterInfo ().GetSSU2V6Address (); + if (localAddress) return false; + // create nonce + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + auto ts = i2p::util::GetSecondsSinceEpoch (); + // payload + uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + size_t payloadSize = 0; + payload[0] = eSSU2BlkRelayRequest; + payload[3] = 0; // flag + htobe32buf (payload + 4, nonce); + htobe32buf (payload + 8, relayTag); + htobe32buf (payload + 12, ts); + payload[16] = 2; // ver + size_t asz = CreateEndpoint (payload + 18, SSU2_MAX_PAYLOAD_SIZE - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); + if (!asz) return false; + payload[17] = asz; + payloadSize += asz + 17; + SignedData s; + s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue + s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash + s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash + s.Insert (payload + 4, 14 + asz); // nonce, relay tag, timestamp, ver, asz and Alice's endpoint + s.Sign (i2p::context.GetPrivateKeys (), payload + 17 + asz); + payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); + htobe16buf (payload + 1, payloadSize - 3); // size + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + // send + m_RelaySessions.emplace (nonce, std::make_pair (session, ts)); + session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); + session->m_DestConnID = ~session->m_SourceConnID; + m_Server.AddSession (session); + SendData (payload, payloadSize); + + return true; + } + + void SSU2Session::Terminate () + { + if (m_State != eSSU2SessionStateTerminated) + { + m_State = eSSU2SessionStateTerminated; + transports.PeerDisconnected (shared_from_this ()); + m_Server.RemoveSession (m_SourceConnID); + if (m_RelayTag) + m_Server.RemoveRelay (m_RelayTag); + m_SendQueue.clear (); + LogPrint (eLogDebug, "SSU2: Session terminated"); + } + } + + void SSU2Session::TerminateByTimeout () + { + SendTermination (); + m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + } + + void SSU2Session::Established () + { + m_State = eSSU2SessionStateEstablished; + m_EphemeralKeys = nullptr; + m_NoiseState.reset (nullptr); + m_SessionConfirmedFragment1.reset (nullptr); + SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT); + transports.PeerConnected (shared_from_this ()); + if (m_OnEstablished) m_OnEstablished (); + } + + void SSU2Session::Done () + { + m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + } + + void SSU2Session::SendI2NPMessages (const std::vector >& msgs) + { + m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), msgs)); + } + + void SSU2Session::PostI2NPMessages (std::vector > msgs) + { + for (auto it: msgs) + m_SendQueue.push_back (it); + SendQueue (); + } + + bool SSU2Session::SendQueue () + { + if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) + { + auto nextResend = i2p::util::GetSecondsSinceEpoch () + SSU2_RESEND_INTERVAL; + auto packet = std::make_shared(); + packet->payloadSize += CreateAckBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) + { + auto msg = m_SendQueue.front (); + size_t len = msg->GetNTCP2Length (); + if (len + 3 < SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize) + { + m_SendQueue.pop_front (); + packet->payloadSize += CreateI2NPBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize, std::move (msg)); + } + else if (len > SSU2_MAX_PAYLOAD_SIZE - 32) // message too long + { + m_SendQueue.pop_front (); + SendFragmentedMessage (msg); + } + else + { + // send right a way + if (packet->payloadSize + 16 < SSU2_MAX_PAYLOAD_SIZE) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->nextResendTime = nextResend; + m_SentPackets.emplace (packetNum, packet); + packet = std::make_shared(); + packet->payloadSize += CreateAckBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + } + }; + if (packet->payloadSize) + { + if (packet->payloadSize + 16 < SSU2_MAX_PAYLOAD_SIZE) + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + packet->nextResendTime = nextResend; + m_SentPackets.emplace (packetNum, packet); + } + return true; + } + return false; + } + + void SSU2Session::SendFragmentedMessage (std::shared_ptr msg) + { + uint32_t msgID; + memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); + auto nextResend = i2p::util::GetSecondsSinceEpoch () + SSU2_RESEND_INTERVAL; + auto packet = std::make_shared(); + packet->payloadSize += CreateAckBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + auto size = CreateFirstFragmentBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - 32 - packet->payloadSize, msg); + if (!size) return; + packet->payloadSize += size; + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + uint32_t firstPacketNum = SendData (packet->payload, packet->payloadSize); + packet->nextResendTime = nextResend; + m_SentPackets.emplace (firstPacketNum, packet); + uint8_t fragmentNum = 0; + while (msg->offset < msg->len) + { + packet = std::make_shared(); + packet->payloadSize += CreateFollowOnFragmentBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize - 16, msg, fragmentNum, msgID); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + uint32_t followonPacketNum = SendData (packet->payload, packet->payloadSize); + packet->nextResendTime = nextResend; + m_SentPackets.emplace (followonPacketNum, packet); + } + } + + void SSU2Session::Resend (uint64_t ts) + { + if (m_SentPackets.empty ()) return; + std::map > resentPackets; + for (auto it = m_SentPackets.begin (); it != m_SentPackets.end (); ) + if (ts > it->second->nextResendTime) + { + if (it->second->numResends > SSU2_MAX_NUM_RESENDS) + it = m_SentPackets.erase (it); + else + { + uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize); + it->second->numResends++; + it->second->nextResendTime = ts + it->second->numResends*SSU2_RESEND_INTERVAL; + m_LastActivityTimestamp = ts; + resentPackets.emplace (packetNum, it->second); + it = m_SentPackets.erase (it); + } + } + else + it++; + if (!resentPackets.empty ()) + { +#if (__cplusplus >= 201703L) // C++ 17 or higher + m_SentPackets.merge (resentPackets); +#else + m_SentPackets.insert (resentPackets.begin (), resentPackets.end ()); +#endif + } + SendQueue (); + } + + bool SSU2Session::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) + { + // we are Bob + m_SourceConnID = connID; + Header header; + header.h.connID = connID; + memcpy (header.buf + 8, buf + 8, 8); + header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); + switch (header.h.type) + { + case eSSU2SessionRequest: + ProcessSessionRequest (header, buf, len); + break; + case eSSU2TokenRequest: + ProcessTokenRequest (header, buf, len); + break; + default: + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); + return false; + } + } + return true; + } + + void SSU2Session::SendSessionRequest (uint64_t token) + { + // we are Alice + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + + Header header; + uint8_t headerX[48], payload[40]; + // fill packet + header.h.connID = m_DestConnID; // dest id + header.h.packetNum = 0; + header.h.type = eSSU2SessionRequest; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (headerX, &m_SourceConnID, 8); // source id + memcpy (headerX + 8, &token, 8); // token + memcpy (headerX + 16, m_EphemeralKeys->GetPublicKey (), 32); // X + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t payloadSize = 7; + payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1); + // KDF for session request + m_NoiseState->MixHash ({ {header.buf, 16}, {headerX, 16} }); // h = SHA256(h || header) + m_NoiseState->MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk); + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (m_Address->s, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // encrypt + const uint8_t nonce[12] = {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_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated + // send + m_Server.AddPendingOutgoingSession (shared_from_this ()); + m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); + } + + void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len) + { + // we are Bob + const uint8_t nonce[12] = {0}; + uint8_t headerX[48]; + i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); + memcpy (&m_DestConnID, headerX, 8); + uint64_t token; + memcpy (&token, headerX + 8, 8); + if (!token || token != m_Server.GetIncomingToken (m_RemoteEndpoint)) + { + LogPrint (eLogDebug, "SSU2: SessionRequest token mismatch. Retry"); + SendRetry (); + return; + } + // KDF for session request + m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) + m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || aepk); + uint8_t sharedSecret[32]; + i2p::context.GetSSU2StaticKeys ().Agree (headerX + 16, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // decrypt + uint8_t * payload = buf + 64; + std::vector decryptedPayload(len - 80); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) + { + LogPrint (eLogWarning, "SSU2: SessionRequest AEAD verification failed "); + return; + } + m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated + // payload + HandlePayload (decryptedPayload.data (), decryptedPayload.size ()); + + m_Server.AddSession (shared_from_this ()); + SendSessionCreated (headerX + 16); + } + + void SSU2Session::SendSessionCreated (const uint8_t * X) + { + // we are Bob + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + uint8_t kh2[32]; + i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessCreateHeader", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32) + + // fill packet + Header header; + uint8_t headerX[48], payload[64]; + header.h.connID = m_DestConnID; // dest id + header.h.packetNum = 0; + header.h.type = eSSU2SessionCreated; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (headerX, &m_SourceConnID, 8); // source id + RAND_bytes (headerX + 8, 8); // token + memcpy (headerX + 16, m_EphemeralKeys->GetPublicKey (), 32); // Y + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t payloadSize = 7; + payloadSize += CreateAddressBlock (payload + payloadSize, 64 - payloadSize, m_RemoteEndpoint); + if (m_RelayTag) + { + payload[payloadSize] = eSSU2BlkRelayTag; + htobe16buf (payload + payloadSize + 1, 4); + htobe32buf (payload + payloadSize + 3, m_RelayTag); + payloadSize += 7; + } + payloadSize += CreatePaddingBlock (payload + payloadSize, 64 - payloadSize); + // 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); + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (X, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // encrypt + const uint8_t nonce[12] = {0}; + 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); + // send + m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); + } + + bool SSU2Session::ProcessSessionCreated (uint8_t * buf, size_t len) + { + // we are Alice + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); + uint8_t kh2[32]; + i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessCreateHeader", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32) + header.ll[1] ^= CreateHeaderMask (kh2, buf + (len - 12)); + if (header.h.type != eSSU2SessionCreated) + // this situation is valid, because it might be Retry with different encryption + return false; + const uint8_t nonce[12] = {0}; + uint8_t headerX[48]; + i2p::crypto::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); + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (headerX + 16, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // decrypt + uint8_t * payload = buf + 64; + std::vector decryptedPayload(len - 80); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) + { + LogPrint (eLogWarning, "SSU2: SessionCreated AEAD verification failed "); + return false; + } + m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from SessionCreated) for SessionConfirmed + // payload + HandlePayload (decryptedPayload.data (), decryptedPayload.size ()); + + m_Server.AddSession (shared_from_this ()); + SendSessionConfirmed (headerX + 16); + KDFDataPhase (m_KeyDataSend, m_KeyDataReceive); + Established (); + + return true; + } + + void SSU2Session::SendSessionConfirmed (const uint8_t * Y) + { + // we are Alice + uint8_t kh2[32]; + i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessionConfirmed", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32) + // fill packet + Header header; + header.h.connID = m_DestConnID; // dest id + header.h.packetNum = 0; + header.h.type = eSSU2SessionConfirmed; + memset (header.h.flags, 0, 3); + header.h.flags[0] = 1; // frag, total fragments always 1 + // payload + const size_t maxPayloadSize = SSU2_MAX_PAYLOAD_SIZE - 48; // part 2 + uint8_t payload[maxPayloadSize + 16]; + size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ()); + // TODO: check is RouterInfo doesn't fit and split by two fragments + if (payloadSize < maxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, maxPayloadSize - payloadSize); + // KDF for Session Confirmed part 1 + m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) + // Encrypt part 1 + uint8_t part1[48]; + uint8_t nonce[12]; + CreateNonce (1, nonce); + 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 + uint8_t sharedSecret[32]; + i2p::context.GetSSU2StaticKeys ().Agree (Y, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // Encrypt part2 + memset (nonce, 0, 12); + 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); + // Encrypt header + header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); + header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); + // send + m_Server.Send (header.buf, 16, part1, 48, payload, payloadSize, m_RemoteEndpoint); + m_SendPacketNum++; + } + + bool SSU2Session::ProcessSessionConfirmed (uint8_t * buf, size_t len) + { + // we are Bob + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); + uint8_t kh2[32]; + i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessionConfirmed", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32) + header.ll[1] ^= CreateHeaderMask (kh2, buf + (len - 12)); + if (header.h.type != eSSU2SessionConfirmed) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); + return false; + } + // check if fragmented + if ((header.h.flags[0] & 0x0F) > 1) + { + // fragmented + if (!(header.h.flags[0] & 0xF0)) + { + // first fragment + m_SessionConfirmedFragment1.reset (new SessionConfirmedFragment); + m_SessionConfirmedFragment1->header = header; + memcpy (m_SessionConfirmedFragment1->payload, buf + 16, len - 16); + m_SessionConfirmedFragment1->payloadSize = len - 16; + return true; // wait for second fragment + } + else + { + // second fragment + if (!m_SessionConfirmedFragment1) return false; // out of sequence + uint8_t fullMsg[2*SSU2_MTU]; + header = m_SessionConfirmedFragment1->header; + memcpy (fullMsg + 16, m_SessionConfirmedFragment1->payload, m_SessionConfirmedFragment1->payloadSize); + memcpy (fullMsg + 16 + m_SessionConfirmedFragment1->payloadSize, buf + 16, len - 16); + buf = fullMsg; + len += m_SessionConfirmedFragment1->payloadSize; + } + } + // KDF for Session Confirmed part 1 + m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) + // decrypt part1 + uint8_t nonce[12]; + CreateNonce (1, nonce); + uint8_t S[32]; + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, 32, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, S, 32, false)) + { + LogPrint (eLogWarning, "SSU2: SessionConfirmed part 1 AEAD verification failed "); + return false; + } + m_NoiseState->MixHash (buf + 16, 48); // h = SHA256(h || ciphertext); + // KDF for Session Confirmed part 2 + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (S, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // decrypt part2 + memset (nonce, 0, 12); + uint8_t * payload = buf + 64; + std::vector decryptedPayload(len - 80); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) + { + LogPrint (eLogWarning, "SSU2: SessionConfirmed part 2 AEAD verification failed "); + return false; + } + m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || ciphertext); + // payload + // handle RouterInfo block that must be first + if (decryptedPayload[0] != eSSU2BlkRouterInfo) + { + LogPrint (eLogError, "SSU2: SessionConfirmed unexpected first block type ", (int)decryptedPayload[0]); + return false; + } + size_t riSize = bufbe16toh (decryptedPayload.data () + 1); + if (riSize + 3 > decryptedPayload.size ()) + { + LogPrint (eLogError, "SSU2: SessionConfirmed RouterInfo block is too long ", riSize); + return false; + } + LogPrint (eLogDebug, "SSU2: RouterInfo in SessionConfirmed"); + auto ri = ExtractRouterInfo (decryptedPayload.data () + 3, riSize); + if (!ri) + { + LogPrint (eLogError, "SSU2: SessionConfirmed malformed RouterInfo block"); + return false; + } + SetRemoteIdentity (ri->GetRouterIdentity ()); + m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now + m_Address = ri->GetSSU2AddressWithStaticKey (S, m_RemoteEndpoint.address ().is_v6 ()); + if (!m_Address) + { + LogPrint (eLogError, "SSU2: No SSU2 address with static key found in SessionConfirmed"); + return false; + } + i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, ri->GetBuffer (), ri->GetBufferLen ())); // TODO: should insert ri + // handle other blocks + HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3); + KDFDataPhase (m_KeyDataReceive, m_KeyDataSend); + Established (); + + SendQuickAck (); + + return true; + } + + void SSU2Session::KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba) + { + uint8_t keydata[64]; + i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) + // ab + i2p::crypto::HKDF (keydata, nullptr, 0, "HKDFSSU2DataKeys", keydata_ab); // keydata_ab = HKDF(keydata, ZEROLEN, "HKDFSSU2DataKeys", 64) + // ba + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "HKDFSSU2DataKeys", keydata_ba); // keydata_ba = HKDF(keydata + 32, ZEROLEN, "HKDFSSU2DataKeys", 64) + } + + void SSU2Session::SendTokenRequest () + { + // we are Alice + Header header; + uint8_t h[32], payload[40]; + // fill packet + header.h.connID = m_DestConnID; // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2TokenRequest; + 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 + memset (h + 24, 0, 8); // zero token + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t payloadSize = 7; + payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1); + // encrypt + uint8_t nonce[12]; + CreateNonce (be32toh (header.h.packetNum), nonce); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, m_Address->i, 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)); + memset (nonce, 0, 12); + i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); + // send + m_Server.AddPendingOutgoingSession (shared_from_this ()); + m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); + } + + void SSU2Session::ProcessTokenRequest (Header& header, uint8_t * buf, size_t len) + { + // we are Bob + 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); + memcpy (&m_DestConnID, h + 16, 8); + // decrypt + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t * payload = buf + 32; + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: TokenRequest AEAD verification failed "); + return; + } + // payload + HandlePayload (payload, len - 48); + SendRetry (); + } + + void SSU2Session::SendRetry () + { + // we are Bob + Header header; + uint8_t h[32], payload[64]; + // fill packet + header.h.connID = m_DestConnID; // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2Retry; + 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 + uint64_t token = m_Server.GetIncomingToken (m_RemoteEndpoint); + memcpy (h + 24, &token, 8); // token + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t payloadSize = 7; + payloadSize += CreateAddressBlock (payload + payloadSize, 64 - payloadSize, m_RemoteEndpoint); + payloadSize += CreatePaddingBlock (payload + payloadSize, 64 - payloadSize); + // encrypt + uint8_t nonce[12]; + CreateNonce (be32toh (header.h.packetNum), nonce); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, i2p::context.GetSSU2IntroKey (), nonce, payload, payloadSize + 16, true); + payloadSize += 16; + 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); + // send + m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); + } + + bool SSU2Session::ProcessRetry (uint8_t * buf, size_t len) + { + // we are Alice + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); + header.ll[1] ^= CreateHeaderMask (m_Address->i, buf + (len - 12)); + if (header.h.type != eSSU2Retry) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); + return false; + } + uint8_t nonce[12] = {0}; + uint64_t headerX[2]; // sourceConnID, token + i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); + m_Server.UpdateOutgoingToken (m_RemoteEndpoint, headerX[1], i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); + // 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, + m_Address->i, nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed "); + return false; + } + HandlePayload (payload, len - 48); + + InitNoiseXKState1 (*m_NoiseState, m_Address->s); // reset Noise TODO: check state + SendSessionRequest (headerX[1]); + return true; + } + + void SSU2Session::SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey) + { + // we are Charlie + Header header; + uint8_t h[32], payload[SSU2_MAX_PAYLOAD_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 + uint64_t token = m_Server.GetIncomingToken (ep); + memcpy (h + 24, &token, 8); // token + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t payloadSize = 7; + payloadSize += CreateAddressBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, ep); + payloadSize += CreateRelayResponseBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, nonce); + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - 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 + 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 != eSSU2HolePunch) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); + 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: HolePunch AEAD verification failed "); + return false; + } + m_Server.UpdateOutgoingToken (m_RemoteEndpoint, headerX[1], i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); + HandlePayload (payload, len - 48); + // connect to Charlie + if (m_State == eSSU2SessionStateIntroduced) + { + m_State = eSSU2SessionStateUnknown; + Connect (); + } + + return true; + } + + 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); + 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; + } + + uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len) + { + if (len < 8) + { + LogPrint (eLogWarning, "SSU2: Data message payload is too short ", (int)len); + return 0; + } + Header header; + header.h.connID = m_DestConnID; + header.h.packetNum = htobe32 (m_SendPacketNum); + header.h.type = eSSU2Data; + memset (header.h.flags, 0, 3); + uint8_t nonce[12]; + CreateNonce (m_SendPacketNum, nonce); + uint8_t payload[SSU2_MTU]; + i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MTU, true); + 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; + return m_SendPacketNum - 1; + } + + void SSU2Session::ProcessData (uint8_t * buf, size_t len) + { + Header header; + header.ll[0] = m_SourceConnID; + memcpy (header.buf + 8, buf + 8, 8); + header.ll[1] ^= CreateHeaderMask (m_KeyDataReceive + 32, buf + (len - 12)); + if (header.h.type != eSSU2Data) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); + return; + } + uint8_t payload[SSU2_MTU]; + size_t payloadSize = len - 32; + uint32_t packetNum = be32toh (header.h.packetNum); + uint8_t nonce[12]; + CreateNonce (packetNum, nonce); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16, + m_KeyDataReceive, nonce, payload, payloadSize, false)) + { + LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); + return; + } + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumReceivedBytes += len; + if (UpdateReceivePacketNum (packetNum)) + HandlePayload (payload, payloadSize); + } + + void SSU2Session::HandlePayload (const uint8_t * buf, size_t len) + { + size_t offset = 0; + while (offset < len) + { + uint8_t blk = buf[offset]; + offset++; + auto size = bufbe16toh (buf + offset); + offset += 2; + LogPrint (eLogDebug, "SSU2: Block type ", (int)blk, " of size ", size); + if (size > len) + { + LogPrint (eLogError, "SSU2: Unexpected block length ", size); + break; + } + switch (blk) + { + case eSSU2BlkDateTime: + LogPrint (eLogDebug, "SSU2: Datetime"); + break; + case eSSU2BlkOptions: + LogPrint (eLogDebug, "SSU2: Options"); + break; + case eSSU2BlkRouterInfo: + { + // not from SessionConfirmed + LogPrint (eLogDebug, "SSU2: RouterInfo"); + auto ri = ExtractRouterInfo (buf + offset, size); + if (ri) + i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, ri->GetBuffer (), ri->GetBufferLen ())); // TODO: should insert ri + break; + } + case eSSU2BlkI2NPMessage: + { + LogPrint (eLogDebug, "SSU2: I2NP message"); + auto nextMsg = NewI2NPShortMessage (); + nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header + memcpy (nextMsg->GetNTCP2Header (), buf + offset, size); + nextMsg->FromNTCP2 (); // SSU2 has the same format as NTCP2 + m_Handler.PutNextMessage (std::move (nextMsg)); + m_IsDataReceived = true; + break; + } + case eSSU2BlkFirstFragment: + LogPrint (eLogDebug, "SSU2: First fragment"); + HandleFirstFragment (buf + offset, size); + m_IsDataReceived = true; + break; + case eSSU2BlkFollowOnFragment: + LogPrint (eLogDebug, "SSU2: Follow-on fragment"); + HandleFollowOnFragment (buf + offset, size); + m_IsDataReceived = true; + break; + case eSSU2BlkTermination: + LogPrint (eLogDebug, "SSU2: Termination"); + Terminate (); + break; + case eSSU2BlkRelayRequest: + LogPrint (eLogDebug, "SSU2: RelayRequest"); + HandleRelayRequest (buf + offset, size); + break; + case eSSU2BlkRelayResponse: + LogPrint (eLogDebug, "SSU2: RelayResponse"); + HandleRelayResponse (buf + offset, size); + break; + case eSSU2BlkRelayIntro: + LogPrint (eLogDebug, "SSU2: RelayIntro"); + HandleRelayIntro (buf + offset, size); + break; + case eSSU2BlkPeerTest: + LogPrint (eLogDebug, "SSU2: PeerTest"); + HandlePeerTest (buf + offset, size); + break; + case eSSU2BlkNextNonce: + break; + case eSSU2BlkAck: + LogPrint (eLogDebug, "SSU2: Ack"); + HandleAck (buf + offset, size); + break; + case eSSU2BlkAddress: + { + boost::asio::ip::udp::endpoint ep; + if (ExtractEndpoint (buf + offset, size, ep)) + LogPrint (eLogInfo, "SSU2: Our external address is ", ep); + break; + } + case eSSU2BlkIntroKey: + break; + case eSSU2BlkRelayTagRequest: + LogPrint (eLogDebug, "SSU2: RelayTagRequest"); + HandleRelayRequest (buf + offset, size); + if (!m_RelayTag) + { + RAND_bytes ((uint8_t *)&m_RelayTag, 4); + m_Server.AddRelay (m_RelayTag, shared_from_this ()); + } + break; + case eSSU2BlkRelayTag: + LogPrint (eLogDebug, "SSU2: RelayTag"); + m_RelayTag = bufbe32toh (buf + offset); + break; + case eSSU2BlkNewToken: + { + LogPrint (eLogDebug, "SSU2: New token"); + uint64_t token; + memcpy (&token, buf + offset + 4, 8); + m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, bufbe32toh (buf + offset)); + break; + } + case eSSU2BlkPathChallenge: + break; + case eSSU2BlkPathResponse: + break; + case eSSU2BlkFirstPacketNumber: + break; + case eSSU2BlkPadding: + LogPrint (eLogDebug, "SSU2: Padding"); + break; + default: + LogPrint (eLogWarning, "SSU2: Unknown block type ", (int)blk); + } + offset += size; + } + } + + void SSU2Session::HandleAck (const uint8_t * buf, size_t len) + { + if (m_SentPackets.empty ()) return; + if (len < 5) return; + // acnt + uint32_t ackThrough = bufbe32toh (buf); + uint32_t firstPacketNum = ackThrough > buf[4] ? ackThrough - buf[4] : 0; + HandleAckRange (firstPacketNum, ackThrough); // acnt + // ranges + len -= 5; + const uint8_t * ranges = buf + 5; + while (len > 0 && firstPacketNum) + { + uint32_t lastPacketNum = firstPacketNum - 1; + if (*ranges > lastPacketNum) break; + lastPacketNum -= *ranges; ranges++; // nacks + if (*ranges > lastPacketNum) break; + firstPacketNum = lastPacketNum - *ranges; ranges++; // acks + len -= 2; + HandleAckRange (firstPacketNum, lastPacketNum); + } + } + + void SSU2Session::HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum) + { + if (firstPacketNum > lastPacketNum) return; + auto it = m_SentPackets.begin (); + while (it != m_SentPackets.end () && it->first < firstPacketNum) it++; // find first acked packet + if (it == m_SentPackets.end ()) return; // not found + auto it1 = it; + while (it1 != m_SentPackets.end () && it1->first <= lastPacketNum) it1++; + if (it1 != m_SentPackets.end () && it1 != m_SentPackets.begin ()) it1--; + m_SentPackets.erase (it, it1); + } + + void SSU2Session::HandleFirstFragment (const uint8_t * buf, size_t len) + { + uint32_t msgID; memcpy (&msgID, buf + 1, 4); + auto msg = NewI2NPMessage (); + // same format as I2NP message block + msg->len = msg->offset + len + 7; + memcpy (msg->GetNTCP2Header (), buf, len); + std::shared_ptr m; + bool found = false; + auto it = m_IncompleteMessages.find (msgID); + if (it != m_IncompleteMessages.end ()) + { + found = true; + m = it->second; + } + else + { + m = std::make_shared(); + m_IncompleteMessages.emplace (msgID, m); + } + m->msg = msg; + m->nextFragmentNum = 1; + m->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + if (found && ConcatOutOfSequenceFragments (m)) + { + // we have all follow-on fragments already + m->msg->FromNTCP2 (); + m_Handler.PutNextMessage (std::move (m->msg)); + m_IncompleteMessages.erase (it); + } + } + + void SSU2Session::HandleFollowOnFragment (const uint8_t * buf, size_t len) + { + if (len < 5) return; + uint8_t fragmentNum = buf[0] >> 1; + bool isLast = buf[0] & 0x01; + uint32_t msgID; memcpy (&msgID, buf + 1, 4); + auto it = m_IncompleteMessages.find (msgID); + if (it != m_IncompleteMessages.end ()) + { + if (it->second->nextFragmentNum == fragmentNum && it->second->msg) + { + // in sequence + it->second->msg->Concat (buf + 5, len - 5); + if (isLast) + { + it->second->msg->FromNTCP2 (); + m_Handler.PutNextMessage (std::move (it->second->msg)); + m_IncompleteMessages.erase (it); + } + else + { + it->second->nextFragmentNum++; + if (ConcatOutOfSequenceFragments (it->second)) + { + m_Handler.PutNextMessage (std::move (it->second->msg)); + m_IncompleteMessages.erase (it); + } + else + it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + } + return; + } + } + else + { + // follow-on fragment before first fragment + auto msg = std::make_shared (); + msg->nextFragmentNum = 0; + it = m_IncompleteMessages.emplace (msgID, msg).first; + } + // insert out of sequence fragment + auto fragment = std::make_shared (); + memcpy (fragment->buf, buf + 5, len -5); + fragment->len = len - 5; + fragment->isLast = isLast; + it->second->outOfSequenceFragments.emplace (fragmentNum, fragment); + it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + } + + bool SSU2Session::ConcatOutOfSequenceFragments (std::shared_ptr m) + { + if (!m) return false; + bool isLast = false; + for (auto it = m->outOfSequenceFragments.begin (); it != m->outOfSequenceFragments.end ();) + if (it->first == m->nextFragmentNum) + { + m->msg->Concat (it->second->buf, it->second->len); + isLast = it->second->isLast; + it = m->outOfSequenceFragments.erase (it); + m->nextFragmentNum++; + } + else + break; + return isLast; + } + + void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) + { + // we are Bob + uint32_t relayTag = bufbe32toh (buf + 5); // relay tag + auto session = m_Server.FindRelaySession (relayTag); + if (!session) + { + LogPrint (eLogWarning, "SSU2: Session with relay tag ", relayTag, " not found"); + return; // TODO: send relay response + } + session->m_RelaySessions.emplace (bufbe32toh (buf + 1), // nonce + std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()) ); + + // send relay intro to Charlie + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI + uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + size_t payloadSize = r ? CreateRouterInfoBlock (payload, SSU2_MAX_PAYLOAD_SIZE - len - 32, r) : 0; + if (!payloadSize && r) + SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + payloadSize += CreateRelayIntroBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, buf + 1, len -1); + if (payloadSize < SSU2_MAX_PAYLOAD_SIZE) + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + session->SendData (payload, payloadSize); + } + + void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len) + { + // we are Charlie + auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice + if (!r) + { + LogPrint (eLogError, "SSU2: RelayIntro unknown router to introduce"); + return; + } + SignedData 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]; + s.Insert (buf + 47, asz); // Alice Port, Alice IP + if (!s.Verify (r->GetIdentity (), buf + 47 + asz)) + { + LogPrint (eLogWarning, "SSU2: RelayIntro signature verification failed"); + return; // TODO: send relay response + } + + // send relay response to Bob + uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + size_t payloadSize = CreateRelayResponseBlock (payload, SSU2_MAX_PAYLOAD_SIZE, bufbe32toh (buf + 33)); + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + SendData (payload, payloadSize); + + // send HolePunch + boost::asio::ip::udp::endpoint ep; + if (ExtractEndpoint (buf + 47, asz, ep)) + { + auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice + if (r) + { + auto addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (addr) + SendHolePunch (bufbe32toh (buf + 33), ep, addr->i); + } + } + } + + void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) + { + if (m_State == eSSU2SessionStateIntroduced) return; // HolePunch from Charlie, TODO: verify address and signature + auto it = m_RelaySessions.find (bufbe32toh (buf + 2)); // nonce + if (it != m_RelaySessions.end ()) + { + if (it->second.first && it->second.first->IsEstablished ()) + // we are Bob, message from Charlie + it->second.first->SendData (buf, len); // forward to Alice as is + else + { + // we are Alice, message from Bob + if (!buf[1]) // status code accepted? + { + // verify signature + uint8_t csz = buf[11]; + SignedData 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)) + { + // update Charlie's endpoint and connect + if (it->second.first->m_State == eSSU2SessionStateIntroduced && + ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint)) + { + it->second.first->m_State = eSSU2SessionStateUnknown; + it->second.first->Connect (); + } + } + else + LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); + } + else + LogPrint (eLogWarning, "SSU2: RelayResponse status code=", (int)buf[1]); + } + m_RelaySessions.erase (it); + } + else + LogPrint (eLogWarning, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); + } + + void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len) + { + uint32_t nonce = bufbe32toh (buf + 37); + switch (buf[0]) // msg + { + case 1: // Bob for Alice + break; + case 2: // Charlie from Bob + 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_PAYLOAD_SIZE]; + size_t payloadSize = CreatePeerTestBlock (payload, SSU2_MAX_PAYLOAD_SIZE, 4, buf + 3, buf + 35, len -35); + if (payloadSize < SSU2_MAX_PAYLOAD_SIZE) + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + it->second.first->SendData (payload, payloadSize); + } + break; + } + case 4: // Alice from Bob + break; + case 5: // Alice from Chralie 1 + break; + case 6: // Chralie from Alice + break; + case 7: // Alice from Charlie 2 + break; + default: + LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); + } + } + + bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep) + { + if (size < 2) return false; + int port = bufbe16toh (buf); + if (size == 6) + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), buf + 2, 4); + ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port); + } + else if (size == 18) + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), buf + 2, 16); + ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6 (bytes), port); + } + else + { + LogPrint (eLogWarning, "SSU2: Address size ", int(size), " is not supported"); + return false; + } + return true; + } + + size_t SSU2Session::CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) + { + if (len < 6) return 0; + htobe16buf (buf, ep.port ()); + size_t size = 0; + if (ep.address ().is_v4 ()) + { + memcpy (buf + 2, ep.address ().to_v4 ().to_bytes ().data (), 4); + size = 6; + } + else if (ep.address ().is_v6 ()) + { + if (len < 18) return 0; + memcpy (buf + 2, ep.address ().to_v6 ().to_bytes ().data (), 16); + size = 18; + } + else + { + LogPrint (eLogWarning, "SSU2: Wrong address type ", ep.address ().to_string ()); + return 0; + } + return size; + } + + size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) + { + if (len < 9) return 0; + buf[0] = eSSU2BlkAddress; + size_t size = CreateEndpoint (buf + 3, len - 3, ep); + if (!size) return 0; + htobe16buf (buf + 1, size); + return size + 3; + } + + size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r) + { + if (!r || len < 5) return 0; + buf[0] = eSSU2BlkRouterInfo; + size_t size = r->GetBufferLen (); + if (size + 5 < len) + { + memcpy (buf + 5, r->GetBuffer (), size); + buf[3] = 0; // flag + } + else + { + i2p::data::GzipDeflator deflator; + size = deflator.Deflate (r->GetBuffer (), r->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; + buf[0] = eSSU2BlkAck; + uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); + htobe32buf (buf + 3, ackThrough); // Ack Through + uint8_t acnt = 0; + int numRanges = 0; + if (ackThrough) + { + if (m_OutOfSequencePackets.empty ()) + acnt = std::min ((int)ackThrough, 255); // no gaps + else + { + auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num + while (it != m_OutOfSequencePackets.rend () && *it == ackThrough - acnt - 1) + { + acnt++; + it++; + } + // ranges + uint32_t lastNum = ackThrough - acnt; + it++; + while (it != m_OutOfSequencePackets.rend () && lastNum > m_ReceivePacketNum && numRanges < 8) + { + if (lastNum - (*it) < 255) + { + buf[7 + numRanges*2] = lastNum - (*it); // NACKs + lastNum = *it; + uint8_t numAcks = 0; + while (it != m_OutOfSequencePackets.rend () && numAcks < 255 && lastNum > m_ReceivePacketNum && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + buf[7 + numRanges*2 + 1] = numAcks; // Acks + numRanges++; it++; + if (numAcks == 255) break; + } + else + break; + } + } + } + buf[7] = acnt; // acnt + htobe16buf (buf + 1, 5 + numRanges*2); + return 8; + } + + size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) + { + if (len < minSize) return 0; + uint8_t paddingSize = rand () & 0x0F; // 0 - 15 + if (paddingSize > len) paddingSize = len; + else if (paddingSize < minSize) paddingSize = minSize; + if (paddingSize) + { + buf[0] = eSSU2BlkPadding; + htobe16buf (buf + 1, paddingSize); + memset (buf + 3, 0, paddingSize); + } + else + return 0; + return paddingSize + 3; + } + + size_t SSU2Session::CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg) + { + msg->ToNTCP2 (); + auto msgBuf = msg->GetNTCP2Header (); + auto msgLen = msg->GetNTCP2Length (); + if (msgLen + 3 > len) msgLen = len - 3; + buf[0] = eSSU2BlkI2NPMessage; + htobe16buf (buf + 1, msgLen); // size + memcpy (buf + 3, msgBuf, msgLen); + return msgLen + 3; + } + + size_t SSU2Session::CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg) + { + if (len < 12) return 0; + msg->ToNTCP2 (); + auto msgBuf = msg->GetNTCP2Header (); + auto msgLen = msg->GetNTCP2Length (); + if (msgLen + 3 <= len) return 0; + msgLen = len - 3; + buf[0] = eSSU2BlkFirstFragment; + htobe16buf (buf + 1, msgLen); // size + memcpy (buf + 3, msgBuf, msgLen); + msg->offset = (msgBuf - buf) + msgLen; + return msgLen + 3; + } + + size_t SSU2Session::CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID) + { + if (len < 8) return 0; + bool isLast = true; + auto msgLen = msg->len - msg->offset; + if (msgLen + 8 > len) + { + msgLen = len - 8; + isLast = false; + } + buf[0] = eSSU2BlkFollowOnFragment; + htobe16buf (buf + 1, msgLen); // size + fragmentNum++; + buf[3] = fragmentNum << 1; + if (isLast) buf[3] |= 0x01; + memcpy (buf + 4, &msgID, 4); + memcpy (buf + 8, msg->buf + msg->offset, msgLen); + msg->offset += msgLen; + return msgLen + 8; + } + + size_t SSU2Session::CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen) + { + buf[0] = eSSU2BlkRelayIntro; + size_t payloadSize = 1/* flag */ + 32/* Alice router hash */ + introDataLen; + if (payloadSize + 3 > len) return 0; + htobe16buf (buf + 1, payloadSize); // size + buf[3] = 0; // flag + memcpy (buf + 4, GetRemoteIdentity ()->GetIdentHash (), 32); // Alice router hash + memcpy (buf + 36, introData, introDataLen); + return payloadSize + 3; + } + + size_t SSU2Session::CreateRelayResponseBlock (uint8_t * buf, size_t len, uint32_t nonce) + { + buf[0] = eSSU2BlkRelayResponse; + buf[3] = 0; // flag + buf[4] = 0; // code, accept + htobe32buf (buf + 5, nonce); // nonce + htobe32buf (buf + 9, i2p::util::GetSecondsSinceEpoch ()); // timestamp + buf[13] = 2; // ver + size_t csz = CreateEndpoint (buf + 15, len - 15, boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port)); + if (!csz) return 0; + buf[14] = csz; // csz + // signature + SignedData s; + s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue + s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash + s.Insert (buf + 5, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint + s.Sign (i2p::context.GetPrivateKeys (), buf + 15 + csz); + size_t payloadSize = 12 + csz + i2p::context.GetIdentity ()->GetSignatureLen (); + htobe16buf (buf + 1, payloadSize); // size + return payloadSize + 3; + } + + size_t SSU2Session::CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, + const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen) + { + buf[0] = eSSU2BlkPeerTest; + size_t payloadSize = 3/* msg, code, flag */ + 32/* router hash */ + signedDataLen; + if (payloadSize + 3 > len) return 0; + htobe16buf (buf + 1, payloadSize); // size + buf[3] = msg; // msg + buf[4] = 0; // code, TODO: + buf[5] = 0; //flag + memcpy (buf + 6, routerHash, 32); // router hash + memcpy (buf + 38, signedData, signedDataLen); + return payloadSize + 3; + } + + std::shared_ptr SSU2Session::ExtractRouterInfo (const uint8_t * buf, size_t size) + { + if (size < 2) return nullptr; + // TODO: handle frag + std::shared_ptr ri; + if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) + { + 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) + ri = std::make_shared(uncompressed, uncompressedSize); + else + LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize); + } + else + ri = std::make_shared(buf + 2, size - 2); + return ri; + } + + void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } + + bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) + { + if (packetNum <= m_ReceivePacketNum) return false; // duplicate + if (packetNum == m_ReceivePacketNum + 1) + { + for (auto it = m_OutOfSequencePackets.begin (); it != m_OutOfSequencePackets.end ();) + { + if (*it == packetNum + 1) + { + packetNum++; + it = m_OutOfSequencePackets.erase (it); + } + else + break; + } + m_ReceivePacketNum = packetNum; + } + else + m_OutOfSequencePackets.insert (packetNum); + return true; + } + + void SSU2Session::SendQuickAck () + { + uint8_t payload[SSU2_MTU]; + size_t payloadSize = CreateAckBlock (payload, SSU2_MTU); + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MTU - payloadSize); + SendData (payload, payloadSize); + } + + void SSU2Session::SendTermination () + { + uint8_t payload[32]; + size_t payloadSize = 12; + payload[0] = eSSU2BlkTermination; + htobe16buf (payload + 1, 9); + memset (payload + 3, 0, 9); + payloadSize += CreatePaddingBlock (payload + payloadSize, 32 - payloadSize); + SendData (payload, payloadSize); + } + + void SSU2Session::CleanUp (uint64_t ts) + { + for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) + { + if (ts > it->second->lastFragmentInsertTime + SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) + { + LogPrint (eLogWarning, "SSU2: message ", it->first, " was not completed in ", SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); + it = m_IncompleteMessages.erase (it); + } + else + ++it; + } + if (m_OutOfSequencePackets.size () > 255) + { + m_ReceivePacketNum = *m_OutOfSequencePackets.rbegin (); + m_OutOfSequencePackets.clear (); + } + for (auto it = m_RelaySessions.begin (); it != m_RelaySessions.end ();) + { + if (ts > it->second.second + SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT) + { + LogPrint (eLogWarning, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted"); + 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; + } + } + + void SSU2Session::FlushData () + { + bool sent = SendQueue (); // if we have something to send + if (m_IsDataReceived) + { + if (!sent) SendQuickAck (); + m_Handler.Flush (); + m_IsDataReceived = false; + } + } + SSU2Server::SSU2Server (): RunnableServiceWithWork ("SSU2"), m_ReceiveService ("SSU2r"), m_SocketV4 (m_ReceiveService.GetService ()), m_SocketV6 (m_ReceiveService.GetService ()), - m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()), - m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()), - m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()), - m_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0), - m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL), m_IsForcedFirewalled4 (false), - m_IsForcedFirewalled6 (false), m_IsThroughProxy (false) + m_TerminationTimer (GetService ()), m_ResendTimer (GetService ()) { } @@ -35,35 +1685,13 @@ namespace transport if (!IsRunning ()) { StartIOService (); - i2p::config::GetOption ("ssu2.published", m_IsPublished); - i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); bool found = false; - auto addresses = i2p::context.GetRouterInfo ().GetAddresses (); - if (!addresses) return; - for (const auto& address: *addresses) + auto& addresses = i2p::context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) { if (!address) continue; if (address->transportStyle == i2p::data::RouterInfo::eTransportSSU2) { - if (m_IsThroughProxy) - { - found = true; - if (address->IsV6 ()) - { - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); - if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE) - mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; - i2p::context.SetMTU (mtu, false); - } - else - { - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); - if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE) - mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; - i2p::context.SetMTU (mtu, true); - } - continue; // we don't need port for proxy - } auto port = address->port; if (!port) { @@ -80,259 +1708,62 @@ 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)); - boost::asio::post (m_ReceiveService.GetService (), + OpenSocket (boost::asio::ip::udp::endpoint (boost::asio::ip::udp::v4(), port)); + m_ReceiveService.GetService ().post( [this]() { Receive (m_SocketV4); }); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } 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)); - boost::asio::post (m_ReceiveService.GetService (), - [this]() + OpenSocket (boost::asio::ip::udp::endpoint (boost::asio::ip::udp::v6(), port)); + m_ReceiveService.GetService ().post( + [this]() { Receive (m_SocketV6); }); - ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers } } else - LogPrint (eLogCritical, "SSU2: Can't start server because port not specified"); + LogPrint (eLogError, "SSU2: Can't start server because port not specified"); } } if (found) - { - if (m_IsThroughProxy) - ConnectToProxy (); m_ReceiveService.Start (); - } ScheduleTermination (); - ScheduleCleanup (); - ScheduleResend (false); } } void SSU2Server::Stop () { - if (IsRunning ()) - { - m_TerminationTimer.cancel (); - m_CleanupTimer.cancel (); - m_ResendTimer.cancel (); - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimerV6.cancel (); - } - - auto sessions = m_Sessions; - for (auto& it: sessions) - { - it.second->RequestTermination (eSSU2TerminationReasonRouterShutdown); - it.second->Done (); - } - if (context.SupportsV4 () || context.SupportsV6 ()) m_ReceiveService.Stop (); - m_SocketV4.close (); - m_SocketV6.close (); - if (m_UDPAssociateSocket) - { - m_UDPAssociateSocket->close (); - m_UDPAssociateSocket.reset (nullptr); - } + if (IsRunning ()) + m_TerminationTimer.cancel (); StopIOService (); - - m_Sessions.clear (); - m_SessionsByRouterHash.clear (); - m_PendingOutgoingSessions.clear (); - m_Relays.clear (); - m_PeerTests.clear (); - m_Introducers.clear (); - m_IntroducersV6.clear (); - m_ConnectedRecently.clear (); - m_RequestedPeerTests.clear (); - - m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue); - m_ReceivedPacketsQueue.clear (); } - void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) - { - if (localAddress.is_unspecified ()) return; - if (localAddress.is_v4 ()) - { - m_AddressV4 = localAddress; - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); - if (!mtu) mtu = i2p::util::net::GetMTU (localAddress); - if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; - if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; - i2p::context.SetMTU (mtu, true); - } - else if (localAddress.is_v6 ()) - { - m_AddressV6 = localAddress; - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); - if (!mtu) - { - int maxMTU = i2p::util::net::GetMaxMTU (localAddress.to_v6 ()); - mtu = i2p::util::net::GetMTU (localAddress); - if (mtu > maxMTU) mtu = maxMTU; - } - else - if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; - if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; - i2p::context.SetMTU (mtu, false); - } - } - - bool SSU2Server::IsSupported (const boost::asio::ip::address& addr) const - { - if (m_IsThroughProxy) - return m_SocketV4.is_open (); - if (addr.is_v4 ()) - { - if (m_SocketV4.is_open ()) - return true; - } - else if (addr.is_v6 ()) - { - if (m_SocketV6.is_open ()) - return true; - } - return false; - } - - uint16_t SSU2Server::GetPort (bool v4) const - { - boost::system::error_code ec; - boost::asio::ip::udp::endpoint ep = (v4 || m_IsThroughProxy) ? m_SocketV4.local_endpoint (ec) : m_SocketV6.local_endpoint (ec); - if (ec) return 0; - return ep.port (); - } - - bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max) - { - if (!ep.port () || ep.address ().is_unspecified ()) return false; - std::lock_guard l(m_ConnectedRecentlyMutex); - auto it = m_ConnectedRecently.find (ep); - if (it != m_ConnectedRecently.end ()) - { - if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION)) - return true; - else if (max) - m_ConnectedRecently.erase (it); - } - return false; - } - - void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts) - { - if (!ep.port () || ep.address ().is_unspecified () || - i2p::util::GetSecondsSinceEpoch () > ts + SSU2_MAX_HOLE_PUNCH_EXPIRATION) return; - std::lock_guard l(m_ConnectedRecentlyMutex); - auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts); - if (!added && ts > it->second) - it->second = ts; // renew timestamp of existing endpoint - } - - void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr from) - { - if (offset) - { - if (m_PendingTimeOffset) // one more - { - if (m_PendingTimeOffsetFrom && from && - m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers - { - if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW) - { - offset = (m_PendingTimeOffset + offset)/2; // average - LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds"); - i2p::util::AdjustTimeOffset (offset); - } - else - LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted"); - m_PendingTimeOffset = 0; - m_PendingTimeOffsetFrom = nullptr; - } - else - LogPrint (eLogWarning, "SSU2: Time offsets from same router. Clock not adjusted"); - } - else - { - m_PendingTimeOffset = offset; // first - m_PendingTimeOffsetFrom = from; - } - } - else - { - m_PendingTimeOffset = 0; // reset - m_PendingTimeOffsetFrom = nullptr; - } - } - boost::asio::ip::udp::socket& SSU2Server::OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint) { boost::asio::ip::udp::socket& socket = localEndpoint.address ().is_v6 () ? m_SocketV6 : m_SocketV4; try { - if (socket.is_open ()) - socket.close (); socket.open (localEndpoint.protocol ()); if (localEndpoint.address ().is_v6 ()) socket.set_option (boost::asio::ip::v6_only (true)); - - 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.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)); socket.bind (localEndpoint); LogPrint (eLogInfo, "SSU2: Start listening on ", localEndpoint); } catch (std::exception& ex ) { - LogPrint (eLogWarning, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what(), ". Actual endpoint is ", socket.local_endpoint ()); - // we can continue without binding being firewalled + LogPrint (eLogError, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what()); + ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint, ": ", ex.what ()); } return socket; } @@ -340,56 +1771,32 @@ namespace transport void SSU2Server::Receive (boost::asio::ip::udp::socket& socket) { Packet * packet = m_PacketsPool.AcquireMt (); - socket.async_receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, + socket.async_receive_from (boost::asio::buffer (packet->buf, SSU2_MTU), packet->from, std::bind (&SSU2Server::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet, std::ref (socket))); } void SSU2Server::HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred, Packet * packet, boost::asio::ip::udp::socket& socket) { - if (!ecode - || ecode == boost::asio::error::connection_refused - || ecode == boost::asio::error::connection_reset - || ecode == boost::asio::error::network_reset - || ecode == boost::asio::error::network_unreachable - || ecode == boost::asio::error::host_unreachable -#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO - || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ - || ecode.value() == boost::winapi::WSAENETRESET_ // 10052 - || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ - || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ -#endif - ) - // just try continue reading when received ICMP response otherwise socket can crash, - // but better to find out which host were sent it and mark that router as unreachable + if (!ecode) { 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::list packets; + std::vector packets; packets.push_back (packet); - while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH) - { + while (moreBytes && packets.size () < 32) + { packet = m_PacketsPool.AcquireMt (); - packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); + packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MTU), packet->from, 0, ec); if (!ec) { i2p::transport::transports.UpdateReceivedBytes (packet->len); - if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE) - packets.push_back (packet); - else // drop too short packets - m_PacketsPool.ReleaseMt (packet); + packets.push_back (packet); moreBytes = socket.available(ec); if (ec) break; } @@ -400,10 +1807,10 @@ namespace transport break; } } - InsertToReceivedPacketsQueue (packets); + GetService ().post (std::bind (&SSU2Server::HandleReceivedPackets, this, packets)); } else - InsertToReceivedPacketsQueue (packet); + GetService ().post (std::bind (&SSU2Server::HandleReceivedPacket, this, packet)); Receive (socket); } else @@ -412,93 +1819,39 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU2: Receive error: code ", ecode.value(), ": ", ecode.message ()); - if (m_IsThroughProxy) - { - m_UDPAssociateSocket.reset (nullptr); - m_ProxyRelayEndpoint.reset (nullptr); - m_SocketV4.close (); - ConnectToProxy (); - } - else - { - auto ep = socket.local_endpoint (); - LogPrint (eLogCritical, "SSU2: Reopening socket in HandleReceivedFrom: code ", ecode.value(), ": ", ecode.message ()); - OpenSocket (ep); - Receive (socket); - } + auto ep = socket.local_endpoint (); + socket.close (); + OpenSocket (ep); + Receive (socket); } } } - void SSU2Server::HandleReceivedPackets (std::list&& packets) + void SSU2Server::HandleReceivedPacket (Packet * packet) { - if (packets.empty ()) return; - if (m_IsThroughProxy) - for (auto it: packets) - ProcessNextPacketFromProxy (it->buf, it->len); - else - for (auto it: packets) - ProcessNextPacket (it->buf, it->len, it->from); - m_PacketsPool.ReleaseMt (packets); - if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) - m_LastSession->FlushData (); + if (packet) + { + ProcessNextPacket (packet->buf, packet->len, packet->from); + m_PacketsPool.ReleaseMt (packet); + if (m_LastSession) m_LastSession->FlushData (); + } } - void SSU2Server::InsertToReceivedPacketsQueue (Packet * packet) + void SSU2Server::HandleReceivedPackets (std::vector packets) { - 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 (); }); - } + for (auto& packet: packets) + ProcessNextPacket (packet->buf, packet->len, packet->from); + m_PacketsPool.ReleaseMt (packets); + if (m_LastSession) m_LastSession->FlushData (); + } - 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) + void SSU2Server::AddSession (std::shared_ptr session) { if (session) { - if (m_Sessions.emplace (session->GetConnID (), session).second) - { - if (session->GetState () != eSSU2SessionStatePeerTest) - AddSessionByRouterHash (session); - return true; - } + m_Sessions.emplace (session->GetConnID (), session); + AddSessionByRouterHash (session); } - return false; } void SSU2Server::RemoveSession (uint64_t connID) @@ -506,28 +1859,13 @@ namespace transport auto it = m_Sessions.find (connID); if (it != m_Sessions.end ()) { - 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; + auto ident = it->second->GetRemoteIdentity (); + if (ident) + m_SessionsByRouterHash.erase (ident->GetIdentHash ()); 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) @@ -535,93 +1873,24 @@ namespace transport auto ident = session->GetRemoteIdentity (); if (ident) { - std::shared_ptr oldSession; + auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); + if (!ret.second) { - 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); + LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " aready exists"); // terminate existing - boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession)); - } + GetService ().post (std::bind (&SSU2Session::Terminate, ret.first->second)); + // update session + ret.first->second = session; + } } } } - bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) + void SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) { - if (!session) return false; - std::lock_guard l(m_PendingOutgoingSessionsMutex); - return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; - } - - std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) - { - std::lock_guard l(m_SessionsByRouterHashMutex); - auto it = m_SessionsByRouterHash.find (ident); - if (it != m_SessionsByRouterHash.end ()) - { - if (!it->second.expired ()) - { - auto s = it->second.lock (); - if (s && s->GetState () != eSSU2SessionStateTerminated) - return s; - } - m_SessionsByRouterHash.erase (it); - } - return nullptr; - } - - std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const - { - std::lock_guard l(m_PendingOutgoingSessionsMutex); - auto it = m_PendingOutgoingSessions.find (ep); - if (it != m_PendingOutgoingSessions.end ()) - return it->second; - return nullptr; - } - - void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) - { - std::lock_guard l(m_PendingOutgoingSessionsMutex); - m_PendingOutgoingSessions.erase (ep); - } - - std::shared_ptr SSU2Server::GetRandomPeerTestSession ( - i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) - { - if (m_Sessions.empty ()) return nullptr; - int ind = m_Rng () % m_Sessions.size (); - auto it = m_Sessions.begin (); - std::advance (it, ind); - while (it != m_Sessions.end ()) - { - if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) && - it->second->GetRemoteIdentity ()->GetIdentHash () != excluded) - return it->second; - it++; - } - // not found, try from beginning - it = m_Sessions.begin (); - while (it != m_Sessions.end () && ind) - { - if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) && - it->second->GetRemoteIdentity ()->GetIdentHash () != excluded) - return it->second; - it++; ind--; - } - return nullptr; + if (session) + m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session); } void SSU2Server::AddRelay (uint32_t tag, std::shared_ptr relay) @@ -639,51 +1908,14 @@ namespace transport auto it = m_Relays.find (tag); if (it != m_Relays.end ()) { - if (!it->second.expired ()) - { - auto s = it->second.lock (); - if (s && s->IsEstablished ()) - return s; - } - m_Relays.erase (it); + if (it->second->IsEstablished ()) + return it->second; + else + 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; @@ -704,45 +1936,19 @@ namespace transport switch (m_LastSession->GetState ()) { case eSSU2SessionStateEstablished: - case eSSU2SessionStateSessionConfirmedSent: - m_LastSession->ProcessData (buf, len, senderEndpoint); + m_LastSession->ProcessData (buf, len); break; - case eSSU2SessionStateSessionCreatedSent: - if (!m_LastSession->ProcessSessionConfirmed (buf, len)) - { - m_LastSession->Done (); - m_LastSession = nullptr; - } + case eSSU2SessionStateUnknown: + m_LastSession->ProcessSessionConfirmed (buf, len); break; case eSSU2SessionStateIntroduced: - if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) - m_LastSession->SetRemoteEndpoint (senderEndpoint); - if (m_LastSession->GetRemoteEndpoint ().address () == senderEndpoint.address ()) // port might be different - m_LastSession->ProcessHolePunch (buf, len); - else - { - LogPrint (eLogWarning, "SSU2: HolePunch address ", senderEndpoint.address (), - " doesn't match RelayResponse ", m_LastSession->GetRemoteEndpoint ().address ()); - m_LastSession->Done (); - m_LastSession = nullptr; - } + m_LastSession->SetRemoteEndpoint (senderEndpoint); + m_LastSession->ProcessHolePunch (buf, len); break; case eSSU2SessionStatePeerTest: 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) - m_LastSession->RequestTermination (eSSU2TerminationReasonIdleTimeout); // send termination again - break; - case eSSU2SessionStateClosingConfirmed: - case eSSU2SessionStateTerminated: - m_LastSession = nullptr; - break; default: LogPrint (eLogWarning, "SSU2: Invalid session state ", (int)m_LastSession->GetState ()); } @@ -753,320 +1959,137 @@ namespace transport auto it1 = m_PendingOutgoingSessions.find (senderEndpoint); if (it1 != m_PendingOutgoingSessions.end ()) { - if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent && - it1->second->ProcessSessionCreated (buf, len)) - { - std::lock_guard l(m_PendingOutgoingSessionsMutex); + if (it1->second->ProcessSessionCreated (buf, len)) m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint - } else it1->second->ProcessRetry (buf, len); } - else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) + else { // assume new incoming session auto session = std::make_shared (*this); session->SetRemoteEndpoint (senderEndpoint); session->ProcessFirstIncomingMessage (connID, buf, len); } - else - LogPrint (eLogError, "SSU2: Incoming packet received from invalid endpoint ", senderEndpoint); } } void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { - if (m_IsThroughProxy) - { - SendThroughProxy (header, headerLen, nullptr, 0, payload, payloadLen, to); - return; - } - std::vector bufs { boost::asio::buffer (header, headerLen), boost::asio::buffer (payload, payloadLen) }; - boost::system::error_code ec; if (to.address ().is_v6 ()) - { - if (!m_SocketV6.is_open ()) return; m_SocketV6.send_to (bufs, to, 0, ec); - } else - { - if (!m_SocketV4.is_open ()) return; m_SocketV4.send_to (bufs, to, 0, ec); - } - if (!ec) i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen); else - { - LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError, - "SSU2: Send exception: ", ec.message (), " to ", to); - } + LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); } void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { - if (m_IsThroughProxy) - { - SendThroughProxy (header, headerLen, headerX, headerXLen, payload, payloadLen, to); - return; - } - std::vector bufs { boost::asio::buffer (header, headerLen), boost::asio::buffer (headerX, headerXLen), boost::asio::buffer (payload, payloadLen) }; - boost::system::error_code ec; if (to.address ().is_v6 ()) - { - if (!m_SocketV6.is_open ()) return; m_SocketV6.send_to (bufs, to, 0, ec); - } else - { - if (!m_SocketV4.is_open ()) return; m_SocketV4.send_to (bufs, to, 0, ec); - } if (!ec) i2p::transport::transports.UpdateSentBytes (headerLen + headerXLen + payloadLen); else - { - LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError, - "SSU2: Send exception: ", ec.message (), " to ", to); - } + LogPrint (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) + std::shared_ptr address) { if (router && address) { - // check if no session - auto existingSession = FindSession (router->GetIdentHash ()); - if (existingSession) - { - // session with router found, trying to send peer test if requested - 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::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 (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)); + if (address->UsesIntroducer ()) + GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, router, address)); else - return false; + GetService ().post ( + [this, router, address]() + { + auto session = std::make_shared (*this, router, address); + session->Connect (); + }); } else return false; return true; } - void SSU2Server::ConnectThroughIntroducer (std::shared_ptr session) + void SSU2Server::ConnectThroughIntroducer (std::shared_ptr router, + std::shared_ptr address) { - if (!session) return; - auto address = session->GetAddress (); - if (!address) return; - session->WaitForIntroduction (); - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::vector indices; int i = 0; + auto session = std::make_shared (*this, router, address); + session->SetState (eSSU2SessionStateIntroduced); // try to find existing session first for (auto& it: address->ssu->introducers) { - if (it.iTag && ts < it.iExp) + auto it1 = m_SessionsByRouterHash.find (it.iKey); + if (it1 != m_SessionsByRouterHash.end ()) { - auto s = FindSession (it.iH); - if (s) - { - auto addr = s->GetAddress (); - if (addr && addr->IsIntroducer ()) - { - s->Introduce (session, it.iTag); - return; - } - } - else - indices.push_back(i); - } - i++; + it1->second->Introduce (session, it.iTag); + return; + } } // 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 ()) + for (auto& it: address->ssu->introducers) { - if (indices.size () > 1) - std::shuffle (indices.begin(), indices.end(), m_Rng); - - for (auto ind: indices) + r = i2p::data::netdb.FindRouter (it.iKey); + if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) { - 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); + relayTag = it.iTag; + if (relayTag) break; } } if (r) { - if (relayTag && addr) + if (relayTag) { // introducer and tag found connect to it through SSU2 - auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); - if (!s) + auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (addr) { - s = std::make_shared (*this, r, addr); - s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); }); + auto 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: newRouters) - i2p::data::netdb.RequestDestination (it); - session->Done (); // don't wait for connect timeout + for (auto& it: address->ssu->introducers) + i2p::data::netdb.RequestDestination (it.iKey); } } - bool SSU2Server::StartPeerTest (std::shared_ptr router, bool v4) - { - if (!router) return false; - auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address (); - if (!addr) return false; - auto session = FindSession (router->GetIdentHash ()); - if (session) - { - auto remoteAddr = session->GetAddress (); - if (!remoteAddr || !remoteAddr->IsPeerTesting () || - (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; - if (session->IsEstablished ()) - boost::asio::post (GetService (), [session]() { session->SendPeerTest (); }); - else - session->SetOnEstablished ([session]() { session->SendPeerTest (); }); - return true; - } - else - CreateSession (router, addr, true); - return true; - } - void SSU2Server::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds( - SSU2_TERMINATION_CHECK_TIMEOUT + m_Rng () % SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU2_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimer.async_wait (std::bind (&SSU2Server::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -1076,72 +2099,34 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); - + for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) { - std::lock_guard l(m_PendingOutgoingSessionsMutex); - for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) + if (it->second->IsTerminationTimeoutExpired (ts)) { - if (it->second->IsTerminationTimeoutExpired (ts)) - { - //it->second->Terminate (); - it = m_PendingOutgoingSessions.erase (it); - } - else - it++; - } - } - - for (auto it: m_Sessions) - { - auto state = it.second->GetState (); - if (state == eSSU2SessionStateTerminated || state == eSSU2SessionStateClosing) - it.second->Done (); - else if (it.second->IsTerminationTimeoutExpired (ts)) - { - if (it.second->IsEstablished ()) - it.second->RequestTermination (eSSU2TerminationReasonIdleTimeout); - else - it.second->Done (); - } - else - it.second->CleanUp (ts); - } - - ScheduleTermination (); - } - } - - void SSU2Server::ScheduleCleanup () - { - m_CleanupTimer.expires_from_now (boost::posix_time::seconds(SSU2_CLEANUP_INTERVAL)); - m_CleanupTimer.async_wait (std::bind (&SSU2Server::HandleCleanupTimer, - this, std::placeholders::_1)); - } - - void SSU2Server::HandleCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_Relays.begin (); it != m_Relays.begin ();) - { - if (it->second.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); + //it->second->Terminate (); + it = m_PendingOutgoingSessions.erase (it); } else it++; } - + + for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) + { + if (it->second->IsTerminationTimeoutExpired (ts)) + { + if (it->second->IsEstablished ()) + it->second->TerminateByTimeout (); + if (it->second == m_LastSession) + m_LastSession = nullptr; + it = m_Sessions.erase (it); + } + else + { + it->second->CleanUp (ts); + it++; + } + } + for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) { if (ts > it->second.second) @@ -1158,46 +2143,13 @@ 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 (); - m_FragmentsPool.CleanUp (); - ScheduleCleanup (); + ScheduleTermination (); } } - void SSU2Server::ScheduleResend (bool more) + void SSU2Server::ScheduleResend () { - 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.expires_from_now (boost::posix_time::seconds(SSU2_RESEND_INTERVAL)); m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer, this, std::placeholders::_1)); } @@ -1206,17 +2158,10 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - size_t resentPacketsNum = 0; - auto ts = i2p::util::GetMillisecondsSinceEpoch (); + auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it: m_Sessions) - { - if (ts >= it.second->GetLastResendTime () + SSU2_RESEND_CHECK_TIMEOUT) - resentPacketsNum += it.second->Resend (ts); - if (resentPacketsNum > SSU2_MAX_RESEND_PACKETS) break; - } - for (auto it: m_PendingOutgoingSessions) it.second->Resend (ts); - ScheduleResend (resentPacketsNum > SSU2_MAX_RESEND_PACKETS); + ScheduleResend (); } } @@ -1225,581 +2170,23 @@ namespace transport m_OutgoingTokens[ep] = {token, exp}; } - uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) + uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const { auto it = m_OutgoingTokens.find (ep); if (it != m_OutgoingTokens.end ()) - { - if (i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_THRESHOLD > it->second.second) - { - // token expired - m_OutgoingTokens.erase (it); - return 0; - } return it->second.first; - } return 0; } uint64_t SSU2Server::GetIncomingToken (const boost::asio::ip::udp::endpoint& ep) { - auto ts = i2p::util::GetSecondsSinceEpoch (); auto it = m_IncomingTokens.find (ep); if (it != m_IncomingTokens.end ()) - { - if (ts + SSU2_TOKEN_EXPIRATION_THRESHOLD <= it->second.second) - return it->second.first; - else // token expired - m_IncomingTokens.erase (it); - } + return it->second.first; uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - if (!token) token = 1; // token can't be zero - m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)); + m_IncomingTokens.emplace (ep, std::make_pair (token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT)); return token; } - - std::pair SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) - { - uint64_t token; - RAND_bytes ((uint8_t *)&token, 8); - if (!token) token = 1; // token can't be zero - uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT; - auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires); - if (!inserted) - it->second = { token, expires }; // override - return it->second; - } - - std::vector > SSU2Server::FindIntroducers (int maxNumIntroducers, - bool v4, const std::unordered_set& excluded) - { - 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)))) - 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, impliedList; - auto& introducers = v4 ? m_Introducers : m_IntroducersV6; - std::unordered_set excluded; - for (const auto& [ident, tag] : introducers) - { - 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 ({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 (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 () && !impliedList.empty ()) - { - LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing"); - for (const auto& it : impliedList) - { - auto session = FindSession (it.first); - if (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 = tag; - introducer.iH = it->GetRemoteIdentity ()->GetIdentHash (); - introducer.iExp = exp; - excluded.insert (it->GetRemoteIdentity ()->GetIdentHash ()); - if (i2p::context.AddSSU2Introducer (introducer, v4)) - { - LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ", - i2p::data::GetIdentHashAbbreviation (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 || numOldSessions) - { - // 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) - { - auto address = v4 ? introducer->GetSSU2V4Address () : introducer->GetSSU2V6Address (); - if (address) - { - CreateSession (introducer, address); - excluded.insert (introducer->GetIdentHash ()); - } - } - else - { - LogPrint (eLogDebug, "SSU2: Can't find more introducers"); - break; - } - } - } - 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_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - } - - void SSU2Server::RescheduleIntroducersUpdateTimer () - { - if (m_IsPublished) - { - m_IntroducersUpdateTimer.cancel (); - i2p::context.ClearSSU2Introducers (true); - m_Introducers.clear (); - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds( - (SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - } - - void SSU2Server::ScheduleIntroducersUpdateTimerV6 () - { - if (m_IsPublished) - { - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds( - SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - } - - void SSU2Server::RescheduleIntroducersUpdateTimerV6 () - { - if (m_IsPublished) - { - m_IntroducersUpdateTimerV6.cancel (); - i2p::context.ClearSSU2Introducers (false); - m_IntroducersV6.clear (); - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds( - (SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - } - - void SSU2Server::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) - { - if (ecode != boost::asio::error::operation_aborted) - { - // timeout expired - if (v4) - { - if (i2p::context.GetTesting ()) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimer (); - return; - } - if (i2p::context.GetStatus () != eRouterStatusFirewalled) - { - // we don't need introducers - i2p::context.ClearSSU2Introducers (true); - m_Introducers.clear (); - return; - } - // we are firewalled - auto addr = i2p::context.GetRouterInfo ().GetSSU2V4Address (); - if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachable (true, false); // v4 - - UpdateIntroducers (true); - ScheduleIntroducersUpdateTimer (); - } - else - { - if (i2p::context.GetTestingV6 ()) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimerV6 (); - return; - } - if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) - { - // we don't need introducers - i2p::context.ClearSSU2Introducers (false); - m_IntroducersV6.clear (); - return; - } - // we are firewalled - auto addr = i2p::context.GetRouterInfo ().GetSSU2V6Address (); - if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachable (false, true); // v6 - - UpdateIntroducers (false); - ScheduleIntroducersUpdateTimerV6 (); - } - } - } - - bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, - const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) - { - return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); - } - - bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, - const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) - { - return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); - } - - void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) - { - m_ChaCha20 (msg, msgLen, key, nonce, out); - } - - void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, - const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) - { - if (!m_ProxyRelayEndpoint) return; - size_t requestHeaderSize = 0; - memset (m_UDPRequestHeader, 0, 3); - if (to.address ().is_v6 ()) - { - m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV6; - memcpy (m_UDPRequestHeader + 4, to.address ().to_v6().to_bytes().data(), 16); - requestHeaderSize = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; - } - else - { - m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4; - memcpy (m_UDPRequestHeader + 4, to.address ().to_v4().to_bytes().data(), 4); - requestHeaderSize = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; - } - htobe16buf (m_UDPRequestHeader + requestHeaderSize - 2, to.port ()); - - std::vector bufs; - bufs.push_back (boost::asio::buffer (m_UDPRequestHeader, requestHeaderSize)); - bufs.push_back (boost::asio::buffer (header, headerLen)); - if (headerX) bufs.push_back (boost::asio::buffer (headerX, headerXLen)); - bufs.push_back (boost::asio::buffer (payload, payloadLen)); - - boost::system::error_code ec; - m_SocketV4.send_to (bufs, *m_ProxyRelayEndpoint, 0, ec); // TODO: implement ipv6 proxy - if (!ec) - i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen); - else - LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); - } - - void SSU2Server::ProcessNextPacketFromProxy (uint8_t * buf, size_t len) - { - if (buf[2]) // FRAG - { - LogPrint (eLogWarning, "SSU2: Proxy packet fragmentation is not supported"); - return; - } - size_t offset = 0; - boost::asio::ip::udp::endpoint ep; - switch (buf[3]) // ATYP - { - case SOCKS5_ATYP_IPV4: - { - offset = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE; - if (offset > len) return; - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf + 4, 4); - uint16_t port = bufbe16toh (buf + 8); - ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port); - break; - } - case SOCKS5_ATYP_IPV6: - { - offset = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE; - if (offset > len) return; - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), buf + 4, 16); - uint16_t port = bufbe16toh (buf + 20); - ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6 (bytes), port); - break; - } - default: - { - LogPrint (eLogWarning, "SSU2: Unknown ATYP ", (int)buf[3], " from proxy relay"); - return; - } - } - ProcessNextPacket (buf + offset, len - offset, ep); - } - - void SSU2Server::ConnectToProxy () - { - if (!m_ProxyEndpoint) return; - m_UDPAssociateSocket.reset (new boost::asio::ip::tcp::socket (m_ReceiveService.GetService ())); - m_UDPAssociateSocket->async_connect (*m_ProxyEndpoint, - [this] (const boost::system::error_code& ecode) - { - if (ecode) - { - LogPrint (eLogError, "SSU2: Can't connect to proxy ", *m_ProxyEndpoint, " ", ecode.message ()); - m_UDPAssociateSocket.reset (nullptr); - ReconnectToProxy (); - } - else - HandshakeWithProxy (); - }); - } - - void SSU2Server::HandshakeWithProxy () - { - if (!m_UDPAssociateSocket) return; - m_UDPRequestHeader[0] = SOCKS5_VER; - m_UDPRequestHeader[1] = 1; // 1 method - m_UDPRequestHeader[2] = 0; // no authentication - boost::asio::async_write (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, 3), boost::asio::transfer_all(), - [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message()); - m_UDPAssociateSocket.reset (nullptr); - ReconnectToProxy (); - } - else - ReadHandshakeWithProxyReply (); - }); - } - - void SSU2Server::ReadHandshakeWithProxyReply () - { - if (!m_UDPAssociateSocket) return; - boost::asio::async_read (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, 2), boost::asio::transfer_all(), - [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message()); - m_UDPAssociateSocket.reset (nullptr); - ReconnectToProxy (); - } - else - { - if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1]) - SendUDPAssociateRequest (); - else - { - LogPrint(eLogError, "SSU2: Invalid proxy reply"); - m_UDPAssociateSocket.reset (nullptr); - } - } - }); - } - - void SSU2Server::SendUDPAssociateRequest () - { - if (!m_UDPAssociateSocket) return; - m_UDPRequestHeader[0] = SOCKS5_VER; - m_UDPRequestHeader[1] = SOCKS5_CMD_UDP_ASSOCIATE; - m_UDPRequestHeader[2] = 0; // RSV - m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4; // TODO: implement ipv6 proxy - memset (m_UDPRequestHeader + 4, 0, 6); // address and port all zeros - boost::asio::async_write (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), boost::asio::transfer_all(), - [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message()); - m_UDPAssociateSocket.reset (nullptr); - ReconnectToProxy (); - } - else - ReadUDPAssociateReply (); - }); - } - - void SSU2Server::ReadUDPAssociateReply () - { - if (!m_UDPAssociateSocket) return; - boost::asio::async_read (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), boost::asio::transfer_all(), - [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message()); - m_UDPAssociateSocket.reset (nullptr); - ReconnectToProxy (); - } - else - { - if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1]) - { - if (m_UDPRequestHeader[3] == SOCKS5_ATYP_IPV4) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), m_UDPRequestHeader + 4, 4); - uint16_t port = bufbe16toh (m_UDPRequestHeader + 8); - m_ProxyRelayEndpoint.reset (new boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port)); - m_SocketV4.open (boost::asio::ip::udp::v4 ()); - Receive (m_SocketV4); - ReadUDPAssociateSocket (); - } - else - { - LogPrint(eLogError, "SSU2: Proxy UDP associate unsupported ATYP ", (int)m_UDPRequestHeader[3]); - m_UDPAssociateSocket.reset (nullptr); - } - } - else - { - LogPrint(eLogError, "SSU2: Proxy UDP associate error ", (int)m_UDPRequestHeader[1]); - m_UDPAssociateSocket.reset (nullptr); - } - } - }); - } - - void SSU2Server::ReadUDPAssociateSocket () - { - if (!m_UDPAssociateSocket) return; - m_UDPAssociateSocket->async_read_some (boost::asio::buffer (m_UDPRequestHeader, 1), - [this] (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint(eLogWarning, "SSU2: Proxy UDP Associate socket error ", ecode.message()); - m_UDPAssociateSocket.reset (nullptr); - m_ProxyRelayEndpoint.reset (nullptr); - m_SocketV4.close (); - ConnectToProxy (); // try to reconnect immediately - } - else - ReadUDPAssociateSocket (); - }); - } - - void SSU2Server::ReconnectToProxy () - { - LogPrint(eLogInfo, "SSU2: Reconnect to proxy after ", SSU2_PROXY_CONNECT_RETRY_TIMEOUT, " seconds"); - if (m_ProxyConnectRetryTimer) - m_ProxyConnectRetryTimer->cancel (); - else - m_ProxyConnectRetryTimer.reset (new boost::asio::deadline_timer (m_ReceiveService.GetService ())); - m_ProxyConnectRetryTimer->expires_from_now (boost::posix_time::seconds (SSU2_PROXY_CONNECT_RETRY_TIMEOUT)); - m_ProxyConnectRetryTimer->async_wait ( - [this](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - m_UDPAssociateSocket.reset (nullptr); - m_ProxyRelayEndpoint.reset (nullptr); - LogPrint(eLogInfo, "SSU2: Reconnecting to proxy"); - ConnectToProxy (); - } - }); - } - - bool SSU2Server::SetProxy (const std::string& address, uint16_t port) - { - boost::system::error_code ecode; - auto addr = boost::asio::ip::make_address (address, ecode); - if (!ecode && !addr.is_unspecified () && port) - { - m_IsThroughProxy = true; - m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint (addr, port)); - } - else - { - if (ecode) - LogPrint (eLogError, "SSU2: Invalid proxy address ", address, " ", ecode.message()); - return false; - } - return true; - } } } diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 3be9f2d1..cce60549 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022-2025, The PurpleI2P Project +* Copyright (c) 2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,63 +9,255 @@ #ifndef SSU2_H__ #define SSU2_H__ +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include "util.h" -#include "SSU2Session.h" -#include "SSU2OutOfSession.h" -#include "Socks5.h" +#include +#include "Crypto.h" +#include "RouterInfo.h" +#include "TransportSession.h" namespace i2p { namespace transport { - const int SSU2_TERMINATION_CHECK_TIMEOUT = 23; // in seconds - const int SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // in seconds - const int SSU2_CLEANUP_INTERVAL = 72; // in seconds - const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds - const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds - const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 4; // in milliseconds - const int SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE = 9; // in milliseconds - const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time - const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024; -#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 = 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; + const int SSU2_CONNECT_TIMEOUT = 5; // 5 seconds + const int SSU2_TERMINATION_TIMEOUT = 330; // 5.5 minutes + const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int SSU2_TOKEN_EXPIRATION_TIMEOUT = 9; // in seconds + const int SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT = 10; // in seconds + const int SSU2_PEER_TEST_EXPIRATION_TIMEOUT = 60; // 60 seconds + const size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K + const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K + const size_t SSU2_MTU = 1488; + const size_t SSU2_MAX_PAYLOAD_SIZE = SSU2_MTU - 32; + const int SSU2_RESEND_INTERVAL = 3; // in seconds + const int SSU2_MAX_NUM_RESENDS = 5; + const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const size_t SSU2_MAX_WINDOW_SIZE = 128; // in packets + + enum SSU2MessageType + { + eSSU2SessionRequest = 0, + eSSU2SessionCreated = 1, + eSSU2SessionConfirmed = 2, + eSSU2Data = 6, + eSSU2PeerTest = 7, + eSSU2Retry = 9, + eSSU2TokenRequest = 10, + eSSU2HolePunch = 11 + }; + + enum SSU2BlockType + { + eSSU2BlkDateTime = 0, + eSSU2BlkOptions, // 1 + eSSU2BlkRouterInfo, // 2 + eSSU2BlkI2NPMessage, // 3 + eSSU2BlkFirstFragment, // 4 + eSSU2BlkFollowOnFragment, // 5 + eSSU2BlkTermination, // 6 + eSSU2BlkRelayRequest, // 7 + eSSU2BlkRelayResponse, // 8 + eSSU2BlkRelayIntro, // 9 + eSSU2BlkPeerTest, // 10 + eSSU2BlkNextNonce, // 11 + eSSU2BlkAck, // 12 + eSSU2BlkAddress, // 13 + eSSU2BlkIntroKey, // 14 + eSSU2BlkRelayTagRequest, // 15 + eSSU2BlkRelayTag, // 16 + eSSU2BlkNewToken, // 17 + eSSU2BlkPathChallenge, // 18 + eSSU2BlkPathResponse, // 19 + eSSU2BlkFirstPacketNumber, // 20 + eSSU2BlkPadding = 254 + }; + + enum SSU2SessionState + { + eSSU2SessionStateUnknown, + eSSU2SessionStateIntroduced, + eSSU2SessionStatePeerTest, + eSSU2SessionStateEstablished, + eSSU2SessionStateTerminated, + eSSU2SessionStateFailed + }; + + struct SSU2IncompleteMessage + { + struct Fragment + { + uint8_t buf[SSU2_MTU]; + size_t len; + bool isLast; + }; + + std::shared_ptr msg; + int nextFragmentNum; + uint32_t lastFragmentInsertTime; // in seconds + std::map > outOfSequenceFragments; + }; + + // RouterInfo flags + const uint8_t SSU2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; + const uint8_t SSU2_ROUTER_INFO_FLAG_GZIP = 0x02; + + class SSU2Server; + class SSU2Session: public TransportSession, public std::enable_shared_from_this + { + union Header + { + uint64_t ll[2]; + uint8_t buf[16]; + struct + { + uint64_t connID; + uint32_t packetNum; + uint8_t type; + uint8_t flags[3]; + } h; + }; + + struct SentPacket + { + uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + size_t payloadSize = 0; + uint32_t nextResendTime; // in seconds + int numResends = 0; + }; + + struct SessionConfirmedFragment + { + Header header; + uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + size_t payloadSize; + }; + + typedef std::function OnEstablished; + + public: + + SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter = nullptr, + std::shared_ptr addr = nullptr); + ~SSU2Session (); + + void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; }; + const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; }; + void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; }; + + void Connect (); + bool Introduce (std::shared_ptr session, uint32_t relayTag); + void Terminate (); + void TerminateByTimeout (); + void CleanUp (uint64_t ts); + void FlushData (); + void Done () override; + void SendI2NPMessages (const std::vector >& msgs) override; + void Resend (uint64_t ts); + bool IsEstablished () const { return m_State == eSSU2SessionStateEstablished; }; + 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); + bool ProcessSessionCreated (uint8_t * buf, size_t len); + bool ProcessSessionConfirmed (uint8_t * buf, size_t len); + bool ProcessRetry (uint8_t * buf, size_t len); + bool ProcessHolePunch (uint8_t * buf, size_t len); + bool ProcessPeerTest (uint8_t * buf, size_t len); + void ProcessData (uint8_t * buf, size_t len); + + private: + + void Established (); + void PostI2NPMessages (std::vector > msgs); + bool SendQueue (); + void SendFragmentedMessage (std::shared_ptr msg); + + void ProcessSessionRequest (Header& header, uint8_t * buf, size_t len); + void ProcessTokenRequest (Header& header, uint8_t * buf, size_t len); + + void SendSessionRequest (uint64_t token = 0); + void SendSessionCreated (const uint8_t * X); + void SendSessionConfirmed (const uint8_t * Y); + void KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba); + void SendTokenRequest (); + void SendRetry (); + uint32_t SendData (const uint8_t * buf, size_t len); // returns packet num + void SendQuickAck (); + void SendTermination (); + void SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey); + + void HandlePayload (const uint8_t * buf, size_t len); + void HandleAck (const uint8_t * buf, size_t len); + void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum); + bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep); + size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); + std::shared_ptr ExtractRouterInfo (const uint8_t * buf, size_t size); + void CreateNonce (uint64_t seqn, uint8_t * nonce); + bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate + void HandleFirstFragment (const uint8_t * buf, size_t len); + void HandleFollowOnFragment (const uint8_t * buf, size_t len); + bool ConcatOutOfSequenceFragments (std::shared_ptr m); // true if message complete + void HandleRelayRequest (const uint8_t * buf, size_t len); + void HandleRelayIntro (const uint8_t * buf, size_t len); + void HandleRelayResponse (const uint8_t * buf, size_t len); + void HandlePeerTest (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 CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r); + 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, uint32_t nonce); // Charlie + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + + private: + + SSU2Server& m_Server; + std::shared_ptr m_EphemeralKeys; + std::unique_ptr m_NoiseState; + std::unique_ptr m_SessionConfirmedFragment1; // for Bob if applicable + std::shared_ptr m_Address; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + uint64_t m_DestConnID, m_SourceConnID; + SSU2SessionState m_State; + uint8_t m_KeyDataSend[64], m_KeyDataReceive[64]; + uint32_t m_SendPacketNum, m_ReceivePacketNum; + std::set m_OutOfSequencePackets; // packet nums > receive packet num + std::map > m_SentPackets; // packetNum -> packet + std::map > m_IncompleteMessages; // I2NP + std::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice + std::map, uint64_t > > m_PeerTests; // same as for relay sessions + std::list > m_SendQueue; + i2p::I2NPMessagesHandler m_Handler; + bool m_IsDataReceived; + size_t m_WindowSize; + uint32_t m_RelayTag; // between Bob and Charlie + OnEstablished m_OnEstablished; // callback from Established + }; class SSU2Server: private i2p::util::RunnableServiceWithWork { struct Packet { - uint8_t buf[SSU2_MAX_PACKET_SIZE]; + uint8_t buf[SSU2_MTU]; size_t len; boost::asio::ip::udp::endpoint from; }; - + class ReceiveService: public i2p::util::RunnableService { public: ReceiveService (const std::string& name): RunnableService (name) {}; - auto& GetService () { return GetIOService (); }; + boost::asio::io_service& GetService () { return GetIOService (); }; void Start () { StartIOService (); }; void Stop () { StopIOService (); }; }; @@ -77,66 +269,28 @@ namespace transport void Start (); void Stop (); - 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); + boost::asio::io_service& GetService () { return GetIOService (); }; - bool AddSession (std::shared_ptr session); + void AddSession (std::shared_ptr session); void RemoveSession (uint64_t connID); - void RequestRemoveSession (uint64_t connID); void AddSessionByRouterHash (std::shared_ptr session); - bool AddPendingOutgoingSession (std::shared_ptr session); - void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep); - std::shared_ptr FindSession (const i2p::data::IdentHash& ident); - std::shared_ptr FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const; - std::shared_ptr GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports, - const i2p::data::IdentHash& excluded); + void AddPendingOutgoingSession (std::shared_ptr session); 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, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); bool CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest = false); - bool StartPeerTest (std::shared_ptr router, bool v4); + std::shared_ptr address); void UpdateOutgoingToken (const boost::asio::ip::udp::endpoint& ep, uint64_t token, uint32_t exp); - uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep); + uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const; uint64_t GetIncomingToken (const boost::asio::ip::udp::endpoint& ep); - std::pair NewIncomingToken (const boost::asio::ip::udp::endpoint& ep); - - void RescheduleIntroducersUpdateTimer (); - void RescheduleIntroducersUpdateTimerV6 (); - - i2p::util::MemoryPool& GetSentPacketsPool () { return m_SentPacketsPool; }; - i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; - i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; private: @@ -144,84 +298,31 @@ 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 HandleReceivedPackets (std::list&& packets); + void HandleReceivedPacket (Packet * packet); + void HandleReceivedPackets (std::vector packets); void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void InsertToReceivedPacketsQueue (Packet * packet); - void InsertToReceivedPacketsQueue (std::list& packets); - void HandleReceivedPacketsQueue (); - + void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleCleanup (); - void HandleCleanupTimer (const boost::system::error_code& ecode); - - void ScheduleResend (bool more); + void ScheduleResend (); 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::vector > FindIntroducers (int maxNumIntroducers, - bool v4, const std::unordered_set& excluded); - void UpdateIntroducers (bool v4); - void ScheduleIntroducersUpdateTimer (); - void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); - void ScheduleIntroducersUpdateTimerV6 (); - - void SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, - const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); - void ProcessNextPacketFromProxy (uint8_t * buf, size_t len); - void ConnectToProxy (); - void ReconnectToProxy (); - void HandshakeWithProxy (); - void ReadHandshakeWithProxyReply (); - void SendUDPAssociateRequest (); - void ReadUDPAssociateReply (); - void ReadUDPAssociateSocket (); // handle if closed by peer + void ConnectThroughIntroducer (std::shared_ptr router, + std::shared_ptr address); private: ReceiveService m_ReceiveService; boost::asio::ip::udp::socket m_SocketV4, m_SocketV6; - boost::asio::ip::address m_AddressV4, m_AddressV6; std::unordered_map > m_Sessions; - std::unordered_map > m_SessionsByRouterHash; - mutable std::mutex m_SessionsByRouterHashMutex; + std::map > m_SessionsByRouterHash; std::map > m_PendingOutgoingSessions; - mutable std::mutex m_PendingOutgoingSessionsMutex; std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) - 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 + std::map > m_Relays; // we are introducer, relay tag -> session i2p::util::MemoryPoolMt m_PacketsPool; - i2p::util::MemoryPool m_SentPacketsPool; - i2p::util::MemoryPool m_IncompleteMessagesPool; - i2p::util::MemoryPool m_FragmentsPool; - boost::asio::deadline_timer m_TerminationTimer, m_CleanupTimer, m_ResendTimer, - m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6; + boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer; 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]; - std::unique_ptr m_ProxyEndpoint; - std::unique_ptr m_UDPAssociateSocket; - std::unique_ptr m_ProxyRelayEndpoint; - std::unique_ptr m_ProxyConnectRetryTimer; public: diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp deleted file mode 100644 index dc626b16..00000000 --- a/libi2pd/SSU2OutOfSession.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* -* 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 deleted file mode 100644 index e8c55c3c..00000000 --- a/libi2pd/SSU2OutOfSession.h +++ /dev/null @@ -1,86 +0,0 @@ -/* -* 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 deleted file mode 100644 index 8f58ec90..00000000 --- a/libi2pd/SSU2Session.cpp +++ /dev/null @@ -1,3214 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include "Log.h" -#include "Transports.h" -#include "Gzip.h" -#include "NetDb.hpp" -#include "SSU2.h" -#include "SSU2Session.h" - -namespace i2p -{ -namespace transport -{ - void SSU2IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) - { - if (msg->len + fragmentSize > msg->maxLen) - { - LogPrint (eLogInfo, "SSU2: I2NP message size ", msg->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (msg->len + fragmentSize); - *newMsg = *msg; - msg = newMsg; - } - if (msg->Concat (fragment, fragmentSize) < fragmentSize) - LogPrint (eLogError, "SSU2: I2NP buffer overflow ", msg->maxLen); - nextFragmentNum++; - } - - bool SSU2IncompleteMessage::ConcatOutOfSequenceFragments () - { - bool isLast = false; - while (outOfSequenceFragments) - { - if (outOfSequenceFragments->fragmentNum == nextFragmentNum) - { - AttachNextFragment (outOfSequenceFragments->buf, outOfSequenceFragments->len); - isLast = outOfSequenceFragments->isLast; - if (isLast) - outOfSequenceFragments = nullptr; - else - outOfSequenceFragments = outOfSequenceFragments->next; - } - else - break; - } - return isLast; - } - - void SSU2IncompleteMessage::AddOutOfSequenceFragment (std::shared_ptr fragment) - { - if (!fragment || !fragment->fragmentNum) return; // fragment 0 not allowed - if (fragment->fragmentNum < nextFragmentNum) return; // already processed - if (!outOfSequenceFragments) - outOfSequenceFragments = fragment; - else - { - auto frag = outOfSequenceFragments; - std::shared_ptr prev; - do - { - if (fragment->fragmentNum < frag->fragmentNum) break; // found - if (fragment->fragmentNum == frag->fragmentNum) return; // duplicate - prev = frag; frag = frag->next; - } - while (frag); - fragment->next = frag; - if (prev) - prev->next = fragment; - else - outOfSequenceFragments = fragment; - } - lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - } - - SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter, - std::shared_ptr addr, bool noise): - TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), - m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0), - m_RemoteVersion (0), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), - m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0), - m_IsDataReceived (false), m_RTT (SSU2_UNKNOWN_RTT), - m_MsgLocalExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX), - m_MsgLocalSemiExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX / 2), - m_WindowSize (SSU2_MIN_WINDOW_SIZE), - m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()), - m_TerminationReason (eSSU2TerminationReasonNormalClose), - m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size - m_LastResendTime (0), m_LastResendAttemptTime (0), m_NumRanges (0) - { - if (noise) - m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); - if (in_RemoteRouter && m_Address) - { - // outgoing - 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 - if (noise) - InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); - } - } - - SSU2Session::~SSU2Session () - { - } - - void SSU2Session::Connect () - { - if (m_State == eSSU2SessionStateUnknown || m_State == eSSU2SessionStateTokenReceived) - { - LogPrint(eLogDebug, "SSU2: Connecting to ", GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ")"); - ScheduleConnectTimer (); - auto token = m_Server.FindOutgoingToken (m_RemoteEndpoint); - if (token) - SendSessionRequest (token); - else - { - m_State = eSSU2SessionStateUnknown; - SendTokenRequest (); - } - } - } - - void SSU2Session::ScheduleConnectTimer () - { - m_ConnectTimer.cancel (); - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU2_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSU2Session::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - - void SSU2Session::HandleConnectTimer (const boost::system::error_code& ecode) - { - if (!ecode && m_State != eSSU2SessionStateTerminated) - { - // timeout expired - if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer - LogPrint (eLogWarning, "SSU2: Session was not introduced after ", SSU2_CONNECT_TIMEOUT, " seconds"); - else - LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds"); - Terminate (); - } - } - - bool SSU2Session::Introduce (std::shared_ptr session, uint32_t relayTag) - { - // we are Alice - if (!session || !relayTag) return false; - // find local address to introduce - auto localAddress = session->FindLocalAddress (); - 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::GetMillisecondsSinceEpoch (); - // payload - 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/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; - 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 + 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/1000)); - session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); - session->m_DestConnID = ~session->m_SourceConnID; - m_Server.AddSession (session); - int32_t packetNum = SendData (packet->payload, packet->payloadSize); - packet->sendTime = ts; - m_SentPackets.emplace (packetNum, packet); - - return true; - } - - void SSU2Session::WaitForIntroduction () - { - m_State = eSSU2SessionStateIntroduced; - ScheduleConnectTimer (); - } - - void SSU2Session::ConnectAfterIntroduction () - { - if (m_State == eSSU2SessionStateIntroduced) - { - // we are Alice - // keep ConnIDs used for introduction, because Charlie waits for SessionRequest from us - m_State = eSSU2SessionStateTokenReceived; - // move session to pending outgoing - if (m_Server.AddPendingOutgoingSession (shared_from_this ())) - { - m_Server.RemoveSession (GetConnID ()); - // update endpoint in profile because we know it now - auto identity = GetRemoteIdentity (); - if (identity) - { - auto profile = i2p::data::GetRouterProfile (identity->GetIdentHash ()); - if (profile) profile->SetLastEndpoint (m_RemoteEndpoint); - } - // connect - LogPrint (eLogDebug, "SSU2: Connecting after introduction to ", GetIdentHashBase64()); - Connect (); - } - else - { - LogPrint (eLogError, "SSU2: Session ", GetConnID (), " is already pending"); - m_Server.RequestRemoveSession (GetConnID ()); - } - } - } - - void SSU2Session::SendPeerTest () - { - // we are Alice - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - // session for message 5 - 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 (); - packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, nonce); - if (packet->payloadSize > 0) - { - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); - packet->sendTime = ts; - m_SentPackets.emplace (packetNum, packet); - LogPrint (eLogDebug, "SSU2: PeerTest msg=1 sent to ", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ())); - } - } - - void SSU2Session::SendKeepAlive () - { - if (IsEstablished ()) - { - uint8_t payload[20]; - size_t payloadSize = CreatePaddingBlock (payload, 20, 8); - SendData (payload, payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); - } - } - - void SSU2Session::Terminate () - { - if (m_State != eSSU2SessionStateTerminated) - { - m_State = eSSU2SessionStateTerminated; - m_ConnectTimer.cancel (); - m_OnEstablished = nullptr; - if (m_RelayTag) - m_Server.RemoveRelay (m_RelayTag); - m_Server.AddConnectedRecently (m_RemoteEndpoint, GetLastActivityTimestamp ()); - m_SentHandshakePacket.reset (nullptr); - m_SessionConfirmedFragment.reset (nullptr); - m_PathChallenge.reset (nullptr); - if (!m_IntermediateQueue.empty ()) - m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue); - for (auto& it: m_SendQueue) - it->Drop (); - m_SendQueue.clear (); - SetSendQueueSize (0); - m_SentPackets.clear (); - m_IncompleteMessages.clear (); - m_RelaySessions.clear (); - m_ReceivedI2NPMsgIDs.clear (); - m_Server.RemoveSession (m_SourceConnID); - transports.PeerDisconnected (shared_from_this ()); - auto remoteIdentity = GetRemoteIdentity (); - if (remoteIdentity) - LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated"); - else - LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), " terminated"); - } - } - - void SSU2Session::RequestTermination (SSU2TerminationReason reason) - { - if (m_State == eSSU2SessionStateEstablished || m_State == eSSU2SessionStateClosing) - { - m_TerminationReason = reason; - SendTermination (); - m_State = eSSU2SessionStateClosing; - } - else - Done (); - } - - void SSU2Session::Established () - { - m_State = eSSU2SessionStateEstablished; - m_EphemeralKeys = nullptr; - m_NoiseState.reset (nullptr); - m_SessionConfirmedFragment.reset (nullptr); - 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 (); - m_OnEstablished = nullptr; - } - } - - void SSU2Session::Done () - { - boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::Terminate, shared_from_this ())); - } - - void SSU2Session::SendLocalRouterInfo (bool update) - { - if (update || !IsOutgoing ()) - { - auto s = shared_from_this (); - 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.CopyRouterInfoBuffer ()); - if (payloadSize) - { - if (payloadSize < s->m_MaxPayloadSize) - payloadSize += s->CreatePaddingBlock (payload + payloadSize, s->m_MaxPayloadSize - payloadSize); - s->SendData (payload, payloadSize); - } - else - s->SendFragmentedMessage (CreateDatabaseStoreMsg ()); - }); - } - - } - - void SSU2Session::SendI2NPMessages (std::list >& msgs) - { - if (m_State == eSSU2SessionStateTerminated || msgs.empty ()) - { - msgs.clear (); - return; - } - bool empty = false; - { - std::lock_guard l(m_IntermediateQueueMutex); - empty = m_IntermediateQueue.empty (); - m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); - } - if (empty) - boost::asio::post (m_Server.GetService (), std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ())); - } - - void SSU2Session::PostI2NPMessages () - { - if (m_State == eSSU2SessionStateTerminated) return; - std::list > msgs; - { - std::lock_guard l(m_IntermediateQueueMutex); - m_IntermediateQueue.swap (msgs); - } - uint64_t mts = i2p::util::GetMonotonicMicroseconds (); - bool isSemiFull = false; - if (m_SendQueue.size ()) - { - int64_t queueLag = (int64_t)mts - (int64_t)m_SendQueue.front ()->GetEnqueueTime (); - isSemiFull = queueLag > m_MsgLocalSemiExpirationTimeout; - if (isSemiFull) - { - LogPrint (eLogWarning, "SSU2: Outgoing messages queue to ", - i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), - " is semi-full (size = ", m_SendQueue.size (), ", lag = ", queueLag / 1000, ", rtt = ", (int)m_RTT, ")"); - } - } - if (isSemiFull) - { - for (auto it: msgs) - { - if (it->onDrop) - it->Drop (); // drop earlier because we can handle it - else - { - it->SetEnqueueTime (mts); - m_SendQueue.push_back (std::move (it)); - } - } - } - else - { - for (auto& it: msgs) it->SetEnqueueTime (mts); - m_SendQueue.splice (m_SendQueue.end (), msgs); - } - if (IsEstablished ()) - { - SendQueue (); - if (m_SendQueue.size () > 0) // windows is full - Resend (i2p::util::GetMillisecondsSinceEpoch ()); - } - SetSendQueueSize (m_SendQueue.size ()); - } - - void SSU2Session::MoveSendQueue (std::shared_ptr other) - { - if (!other || m_SendQueue.empty ()) return; - std::list > msgs; - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it: m_SendQueue) - if (!it->IsExpired (ts)) - msgs.push_back (it); - else - it->Drop (); - m_SendQueue.clear (); - if (!msgs.empty ()) - other->SendI2NPMessages (msgs); - } - - bool SSU2Session::SendQueue () - { - if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize && 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; - packet->payloadSize += ackBlockSize; - while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) - { - auto msg = m_SendQueue.front (); - 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; - } - size_t len = msg->GetNTCP2Length () + 3; - if (len > m_MaxPayloadSize) // message too long - { - m_SendQueue.pop_front (); - if (SendFragmentedMessage (msg)) - ackBlockSent = true; - } - else if (packet->payloadSize + len <= m_MaxPayloadSize) - { - m_SendQueue.pop_front (); - packet->payloadSize += CreateI2NPBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, std::move (msg)); - } - else - { - // create new packet and copy ack block - auto newPacket = m_Server.GetSentPacketsPool ().AcquireShared (); - memcpy (newPacket->payload, packet->payload, ackBlockSize); - newPacket->payloadSize = ackBlockSize; - // complete current packet - if (packet->payloadSize > ackBlockSize) // more than just ack block - { - ackBlockSent = true; - // try to add padding - if (packet->payloadSize + 16 < m_MaxPayloadSize) - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - } - else - { - // reduce ack block - if (len + 8 < m_MaxPayloadSize) - { - // keep Ack block and drop some ranges - ackBlockSent = true; - packet->payloadSize = m_MaxPayloadSize - len; - if (packet->payloadSize & 0x01) packet->payloadSize--; // make it even - htobe16buf (packet->payload + 1, packet->payloadSize - 3); // new block size - } - else // drop Ack block completely - packet->payloadSize = 0; - // msg fits single packet - m_SendQueue.pop_front (); - packet->payloadSize += CreateI2NPBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, std::move (msg)); - } - // send right a way - uint32_t packetNum = SendData (packet->payload, packet->payloadSize); - packet->sendTime = ts; - m_SentPackets.emplace (packetNum, packet); - packet = newPacket; // just ack block - } - }; - if (packet->payloadSize > ackBlockSize) - { - // last - ackBlockSent = true; - if (packet->payloadSize + 16 < m_MaxPayloadSize) - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); - packet->sendTime = ts; - m_SentPackets.emplace (packetNum, packet); - } - return ackBlockSent; - } - return false; - } - - bool SSU2Session::SendFragmentedMessage (std::shared_ptr msg) - { - if (!msg) return false; - size_t lastFragmentSize = (msg->GetNTCP2Length () + 3 - m_MaxPayloadSize) % (m_MaxPayloadSize - 8); - size_t extraSize = m_MaxPayloadSize - lastFragmentSize; - bool ackBlockSent = false; - uint32_t msgID; - memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); - if (extraSize >= 8) - { - packet->payloadSize = CreateAckBlock (packet->payload, extraSize); - ackBlockSent = true; - if (packet->payloadSize + 12 < m_MaxPayloadSize) - { - uint32_t packetNum = SendData (packet->payload, packet->payloadSize); - packet->sendTime = ts; - m_SentPackets.emplace (packetNum, packet); - packet = m_Server.GetSentPacketsPool ().AcquireShared (); - } - else - extraSize -= packet->payloadSize; - } - 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; - extraSize -= offset; - packet->payloadSize += size; - uint32_t firstPacketNum = SendData (packet->payload, packet->payloadSize); - packet->sendTime = ts; - m_SentPackets.emplace (firstPacketNum, packet); - uint8_t fragmentNum = 0; - while (msg->offset < msg->len) - { - offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0; - packet = m_Server.GetSentPacketsPool ().AcquireShared (); - packet->payloadSize = CreateFollowOnFragmentBlock (packet->payload, m_MaxPayloadSize - offset, msg, fragmentNum, msgID); - extraSize -= offset; - uint8_t flags = 0; - if (msg->offset >= msg->len && packet->payloadSize + 16 < m_MaxPayloadSize) // last fragment - { - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - if (fragmentNum > 2) // 3 or more fragments - flags |= SSU2_FLAG_IMMEDIATE_ACK_REQUESTED; - } - uint32_t followonPacketNum = SendData (packet->payload, packet->payloadSize, flags); - packet->sendTime = ts; - m_SentPackets.emplace (followonPacketNum, packet); - } - return ackBlockSent; - } - - size_t SSU2Session::Resend (uint64_t ts) - { - if (ts + SSU2_RESEND_ATTEMPT_MIN_INTERVAL < m_LastResendAttemptTime) return 0; - m_LastResendAttemptTime = ts; - // resend handshake packet - if (m_SentHandshakePacket && ts >= m_SentHandshakePacket->sendTime + SSU2_HANDSHAKE_RESEND_INTERVAL) - { - LogPrint (eLogDebug, "SSU2: Resending ", (int)m_State); - ResendHandshakePacket (); - m_SentHandshakePacket->sendTime = ts; - return 0; - } - // resend data packets - 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 + 1) * m_RTO) - { - if (it->second->numResends > SSU2_MAX_NUM_RESENDS) - { - LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session"); - m_SentPackets.clear (); - m_SendQueue.clear (); - SetSendQueueSize (0); - RequestTermination (eSSU2TerminationReasonTimeout); - return resentPackets.size (); - } - else - { - 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); - it = m_SentPackets.erase (it); - } - } - else - it++; - if (!resentPackets.empty ()) - { - m_LastResendTime = ts; - m_SentPackets.merge (resentPackets); - m_WindowSize >>= 1; // /2 - if (m_WindowSize < SSU2_MIN_WINDOW_SIZE) m_WindowSize = SSU2_MIN_WINDOW_SIZE; - return resentPackets.size (); - } - return 0; - } - - void SSU2Session::ResendHandshakePacket () - { - if (m_SentHandshakePacket) - { - m_Server.Send (m_SentHandshakePacket->header.buf, 16, m_SentHandshakePacket->headerX, 48, - m_SentHandshakePacket->payload, m_SentHandshakePacket->payloadSize, m_RemoteEndpoint); - if (m_SessionConfirmedFragment && m_State == eSSU2SessionStateSessionConfirmedSent) - // resend second fragment of SessionConfirmed - m_Server.Send (m_SessionConfirmedFragment->header.buf, 16, - m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize, m_RemoteEndpoint); - } - } - - bool SSU2Session::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) - { - // we are Bob - m_SourceConnID = connID; - Header header; - header.h.connID = connID; - memcpy (header.buf + 8, buf + 8, 8); - header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); - switch (header.h.type) - { - case eSSU2SessionRequest: - ProcessSessionRequest (header, buf, len); - break; - case eSSU2TokenRequest: - ProcessTokenRequest (header, buf, len); - break; - case eSSU2PeerTest: - { - // TODO: remove later - if (len < 32) - { - LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); - break; - } - const uint8_t nonce[12] = {0}; - uint64_t headerX[2]; - m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); - LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); - break; - } - case eSSU2HolePunch: - LogPrint (eLogDebug, "SSU2: Late HolePunch for ", connID); - break; - default: - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " from ", m_RemoteEndpoint, " of ", len, " bytes"); - return false; - } - } - return true; - } - - void SSU2Session::SendSessionRequest (uint64_t token) - { - // we are Alice - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - m_SentHandshakePacket.reset (new HandshakePacket); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - m_SentHandshakePacket->sendTime = ts; - - Header& header = m_SentHandshakePacket->header; - uint8_t * headerX = m_SentHandshakePacket->headerX, - * payload = m_SentHandshakePacket->payload; - // fill packet - header.h.connID = m_DestConnID; // dest id - 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 - header.h.flags[2] = 0; // flag - memcpy (headerX, &m_SourceConnID, 8); // source id - memcpy (headerX + 8, &token, 8); // token - memcpy (headerX + 16, m_EphemeralKeys->GetPublicKey (), 32); // X - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (ts + 500)/1000); - size_t payloadSize = 7; - if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ()) - { - if (!m_Server.IsMaxNumIntroducers (m_RemoteEndpoint.address ().is_v4 ()) || - m_Server.GetRng ()() & 0x01) // request tag with probability 1/2 if we have enough introducers - { - // relay tag request - payload[payloadSize] = eSSU2BlkRelayTagRequest; - memset (payload + payloadSize + 1, 0, 2); // size = 0 - payloadSize += 3; - } - } - payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1); - // KDF for session request - m_NoiseState->MixHash ({ {header.buf, 16}, {headerX, 16} }); // h = SHA256(h || header) - m_NoiseState->MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk); - uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_Address->s, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - // encrypt - 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)); - m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); - m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated - m_SentHandshakePacket->payloadSize = payloadSize; - // send - if (m_State == eSSU2SessionStateTokenReceived || m_Server.AddPendingOutgoingSession (shared_from_this ())) - { - m_State = eSSU2SessionStateSessionRequestSent; - m_HandshakeInterval = ts; - m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); - } - else - { - LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending"); - Terminate (); - } - } - - void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len) - { - // we are Bob - if (len < 88) - { - LogPrint (eLogWarning, "SSU2: SessionRequest message too short ", len); - return; - } - const uint8_t nonce[12] = {0}; - uint8_t headerX[48]; - m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); - memcpy (&m_DestConnID, headerX, 8); - uint64_t token; - memcpy (&token, headerX + 8, 8); - if (!token || token != m_Server.GetIncomingToken (m_RemoteEndpoint)) - { - LogPrint (eLogDebug, "SSU2: SessionRequest token mismatch. Retry"); - SendRetry (); - return; - } - // KDF for session request - m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) - m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || aepk); - uint8_t sharedSecret[32]; - i2p::context.GetSSU2StaticKeys ().Agree (headerX + 16, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - // decrypt - uint8_t * payload = buf + 64; - std::vector decryptedPayload(len - 80); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, - m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) - { - LogPrint (eLogWarning, "SSU2: SessionRequest AEAD verification failed "); - return; - } - m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated - // payload - m_State = eSSU2SessionStateSessionRequestReceived; - HandlePayload (decryptedPayload.data (), decryptedPayload.size ()); - - if (m_TerminationReason == eSSU2TerminationReasonNormalClose) - { - m_Server.AddSession (shared_from_this ()); - SendSessionCreated (headerX + 16); - } - else - SendRetry (); - } - - void SSU2Session::SendSessionCreated (const uint8_t * X) - { - // we are Bob - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - m_SentHandshakePacket.reset (new HandshakePacket); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - m_SentHandshakePacket->sendTime = ts; - - uint8_t kh2[32]; - i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessCreateHeader", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32) - // fill packet - Header& header = m_SentHandshakePacket->header; - uint8_t * headerX = m_SentHandshakePacket->headerX, - * payload = m_SentHandshakePacket->payload; - header.h.connID = m_DestConnID; // dest id - 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 - header.h.flags[2] = 0; // flag - memcpy (headerX, &m_SourceConnID, 8); // source id - memset (headerX + 8, 0, 8); // token = 0 - memcpy (headerX + 16, m_EphemeralKeys->GetPublicKey (), 32); // Y - // payload - size_t maxPayloadSize = m_MaxPayloadSize - 48; - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (ts + 500)/1000); - size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, maxPayloadSize - payloadSize, m_RemoteEndpoint); - if (m_RelayTag) - { - payload[payloadSize] = eSSU2BlkRelayTag; - htobe16buf (payload + payloadSize + 1, 4); - htobe32buf (payload + payloadSize + 3, m_RelayTag); - payloadSize += 7; - } - auto token = m_Server.NewIncomingToken (m_RemoteEndpoint); - if (ts + SSU2_TOKEN_EXPIRATION_THRESHOLD > token.second) // not expired? - { - payload[payloadSize] = eSSU2BlkNewToken; - htobe16buf (payload + payloadSize + 1, 12); - htobe32buf (payload + payloadSize + 3, token.second - SSU2_TOKEN_EXPIRATION_THRESHOLD); // expires - memcpy (payload + payloadSize + 7, &token.first, 8); // token - payloadSize += 15; - } - payloadSize += CreatePaddingBlock (payload + payloadSize, maxPayloadSize - payloadSize); - // 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); - uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (X, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - // encrypt - 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)); - 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); - } - - bool SSU2Session::ProcessSessionCreated (uint8_t * buf, size_t len) - { - // we are Alice - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); - uint8_t kh2[32]; - i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessCreateHeader", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32) - header.ll[1] ^= CreateHeaderMask (kh2, buf + (len - 12)); - if (header.h.type != eSSU2SessionCreated) - // this situation is valid, because it might be Retry with different encryption - return false; - if (len < 80) - { - LogPrint (eLogWarning, "SSU2: SessionCreated message too short ", len); - return false; - } - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - const uint8_t nonce[12] = {0}; - uint8_t headerX[48]; - 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); - uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (headerX + 16, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - // decrypt - uint8_t * payload = buf + 64; - std::vector decryptedPayload(len - 80); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, - 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 - // payload - m_State = eSSU2SessionStateSessionCreatedReceived; - HandlePayload (decryptedPayload.data (), decryptedPayload.size ()); - - m_Server.AddSession (shared_from_this ()); - AdjustMaxPayloadSize (); - SendSessionConfirmed (headerX + 16); - KDFDataPhase (m_KeyDataSend, m_KeyDataReceive); - - return true; - } - - void SSU2Session::SendSessionConfirmed (const uint8_t * Y) - { - // we are Alice - m_SentHandshakePacket.reset (new HandshakePacket); - m_SentHandshakePacket->sendTime = i2p::util::GetMillisecondsSinceEpoch (); - - uint8_t kh2[32]; - i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessionConfirmed", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32) - // fill packet - Header& header = m_SentHandshakePacket->header; - header.h.connID = m_DestConnID; // dest id - header.h.packetNum = 0; // 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.CopyRouterInfoBuffer ()); - if (!payloadSize) - { - // split by two fragments - maxPayloadSize += m_MaxPayloadSize; - payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ()); - header.h.flags[0] = 0x02; // frag 0, total fragments 2 - // TODO: check if we need more fragments - } - if (payloadSize < maxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, maxPayloadSize - payloadSize); - // KDF for Session Confirmed part 1 - m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) - // Encrypt part 1 - uint8_t * part1 = m_SentHandshakePacket->headerX; - uint8_t nonce[12]; - 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 - uint8_t sharedSecret[32]; - i2p::context.GetSSU2StaticKeys ().Agree (Y, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - // Encrypt part2 - 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); - m_SentHandshakePacket->payloadSize = payloadSize; - if (header.h.flags[0] > 1) - { - if (payloadSize > m_MaxPayloadSize - 48) - { - payloadSize = m_MaxPayloadSize - 48 - (m_Server.GetRng ()() % 16); - if (m_SentHandshakePacket->payloadSize - payloadSize < 24) - payloadSize -= 24; - } - else - header.h.flags[0] = 1; - } - // Encrypt header - header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); - m_State = eSSU2SessionStateSessionConfirmedSent; - // send - m_Server.Send (header.buf, 16, part1, 48, payload, payloadSize, m_RemoteEndpoint); - m_SendPacketNum++; - if (m_SentHandshakePacket->payloadSize > payloadSize) - { - // send second fragment - m_SessionConfirmedFragment.reset (new HandshakePacket); - Header& header = m_SessionConfirmedFragment->header; - header.h.connID = m_DestConnID; // dest id - header.h.packetNum = 0; - header.h.type = eSSU2SessionConfirmed; - memset (header.h.flags, 0, 3); - header.h.flags[0] = 0x12; // frag 1, total fragments 2 - m_SessionConfirmedFragment->payloadSize = m_SentHandshakePacket->payloadSize - payloadSize; - memcpy (m_SessionConfirmedFragment->payload, m_SentHandshakePacket->payload + payloadSize, m_SessionConfirmedFragment->payloadSize); - m_SentHandshakePacket->payloadSize = payloadSize; - header.ll[0] ^= CreateHeaderMask (m_Address->i, m_SessionConfirmedFragment->payload + (m_SessionConfirmedFragment->payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (kh2, m_SessionConfirmedFragment->payload + (m_SessionConfirmedFragment->payloadSize - 12)); - m_Server.Send (header.buf, 16, m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize, m_RemoteEndpoint); - } - } - - bool SSU2Session::ProcessSessionConfirmed (uint8_t * buf, size_t len) - { - // we are Bob - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); - uint8_t kh2[32]; - i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "SessionConfirmed", kh2, 32); // k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32) - header.ll[1] ^= CreateHeaderMask (kh2, buf + (len - 12)); - if (header.h.type != eSSU2SessionConfirmed) - { - LogPrint (eLogInfo, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2SessionConfirmed); - // 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) - { - // fragmented - if (numFragments > 2) - { - LogPrint (eLogError, "SSU2: Too many fragments ", (int)numFragments, " in SessionConfirmed from ", m_RemoteEndpoint); - return false; - } - if (len < 32) - { - LogPrint (eLogWarning, "SSU2: SessionConfirmed fragment too short ", len); - if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); - return false; - } - if (!(header.h.flags[0] & 0xF0)) - { - // first fragment - if (!m_SessionConfirmedFragment) - { - m_SessionConfirmedFragment.reset (new HandshakePacket); - m_SessionConfirmedFragment->header = header; - memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); - m_SessionConfirmedFragment->payloadSize = len - 16; - return true; // wait for second fragment - } - else if (m_SessionConfirmedFragment->isSecondFragment) - { - // we have second fragment - m_SessionConfirmedFragment->header = header; - memmove (m_SessionConfirmedFragment->payload + (len - 16), m_SessionConfirmedFragment->payload, m_SessionConfirmedFragment->payloadSize); - memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); - m_SessionConfirmedFragment->payloadSize += (len - 16); - m_SessionConfirmedFragment->isSecondFragment = false; - buf = m_SessionConfirmedFragment->payload - 16; - len = m_SessionConfirmedFragment->payloadSize + 16; - } - else - return true; - } - else - { - // second fragment - if (!m_SessionConfirmedFragment) - { - // out of sequence, save it - m_SessionConfirmedFragment.reset (new HandshakePacket); - memcpy (m_SessionConfirmedFragment->payload, buf + 16, len - 16); - m_SessionConfirmedFragment->payloadSize = len - 16; - m_SessionConfirmedFragment->isSecondFragment = true; - return true; - } - header = m_SessionConfirmedFragment->header; - if (m_SessionConfirmedFragment->payloadSize + (len - 16) <= SSU2_MAX_PACKET_SIZE*2) - { - memcpy (m_SessionConfirmedFragment->payload + m_SessionConfirmedFragment->payloadSize, buf + 16, len - 16); - m_SessionConfirmedFragment->payloadSize += (len - 16); - } - buf = m_SessionConfirmedFragment->payload - 16; - len = m_SessionConfirmedFragment->payloadSize + 16; - } - } - if (len < 80) - { - LogPrint (eLogWarning, "SSU2: SessionConfirmed message too short ", len); - if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); - return false; - } - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - // KDF for Session Confirmed part 1 - m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header) - // decrypt part1 - uint8_t nonce[12]; - CreateNonce (1, nonce); - uint8_t S[32]; - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, 32, m_NoiseState->m_H, 32, - m_NoiseState->m_CK + 32, nonce, S, 32, false)) - { - LogPrint (eLogWarning, "SSU2: SessionConfirmed part 1 AEAD verification failed "); - if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); - return false; - } - m_NoiseState->MixHash (buf + 16, 48); // h = SHA256(h || ciphertext); - // KDF for Session Confirmed part 2 and data phase - uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (S, sharedSecret); - m_NoiseState->MixKey (sharedSecret); - KDFDataPhase (m_KeyDataReceive, m_KeyDataSend); - // decrypt part2 - memset (nonce, 0, 12); - uint8_t * payload = buf + 64; - std::vector decryptedPayload(len - 80); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, - m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) - { - LogPrint (eLogWarning, "SSU2: SessionConfirmed part 2 AEAD verification failed "); - if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); - return false; - } - m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || ciphertext); - if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr); - // payload - // handle RouterInfo block that must be first - if (decryptedPayload[0] != eSSU2BlkRouterInfo) - { - LogPrint (eLogError, "SSU2: SessionConfirmed unexpected first block type ", (int)decryptedPayload[0]); - return false; - } - size_t riSize = bufbe16toh (decryptedPayload.data () + 1); - if (riSize + 3 > decryptedPayload.size ()) - { - LogPrint (eLogError, "SSU2: SessionConfirmed RouterInfo block is too long ", riSize); - return false; - } - LogPrint (eLogDebug, "SSU2: RouterInfo in SessionConfirmed"); - auto ri = ExtractRouterInfo (decryptedPayload.data () + 3, riSize); - if (!ri) - { - 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)) - { - LogPrint (eLogError, "SSU2: Wrong static key in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); - return false; - } - if (m_Address->published && m_RemoteEndpoint.address () != m_Address->host && - (!m_RemoteEndpoint.address ().is_v6 () || - memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address - { - if (isOlder) // older router? - i2p::data::UpdateRouterProfile (ri->GetIdentHash (), - [](std::shared_ptr profile) - { - if (profile) profile->Duplicated (); // mark router as duplicated in profile - }); - else - LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host, - " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); - return false; - } - if (!m_Address->published) - { - if (ri->HasProfile ()) - ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); - else - i2p::data::UpdateRouterProfile (ri->GetIdentHash (), - [ep = m_RemoteEndpoint](std::shared_ptr profile) - { - if (profile) profile->SetLastEndpoint (ep); - }); - } - SetRemoteIdentity (ri->GetRouterIdentity ()); - AdjustMaxPayloadSize (); - m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now - m_RemoteTransports = ri->GetCompatibleTransports (false); - m_RemotePeerTestTransports = 0; - if (ri->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4; - if (ri->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6; - m_RemoteVersion = ri->GetVersion (); - - // handle other blocks - HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3); - Established (); - - SendQuickAck (); - - return true; - } - - void SSU2Session::KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba) - { - uint8_t keydata[64]; - i2p::crypto::HKDF (m_NoiseState->m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) - // ab - i2p::crypto::HKDF (keydata, nullptr, 0, "HKDFSSU2DataKeys", keydata_ab); // keydata_ab = HKDF(keydata, ZEROLEN, "HKDFSSU2DataKeys", 64) - // ba - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "HKDFSSU2DataKeys", keydata_ba); // keydata_ba = HKDF(keydata + 32, ZEROLEN, "HKDFSSU2DataKeys", 64) - } - - void SSU2Session::SendTokenRequest () - { - // we are Alice - Header header; - uint8_t h[32], payload[41]; - // fill packet - header.h.connID = m_DestConnID; // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2TokenRequest; - 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 - memset (h + 24, 0, 8); // zero token - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - size_t payloadSize = 7; - payloadSize += CreatePaddingBlock (payload + payloadSize, 25 - payloadSize, 1); - // encrypt - uint8_t nonce[12]; - CreateNonce (be32toh (header.h.packetNum), nonce); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, m_Address->i, 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)); - memset (nonce, 0, 12); - 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); - else - { - LogPrint (eLogWarning, "SSU2: TokenRequest request to ", m_RemoteEndpoint, " already pending"); - Terminate (); - } - } - - void SSU2Session::ProcessTokenRequest (Header& header, uint8_t * buf, size_t len) - { - // we are Bob - if (len < 48) - { - LogPrint (eLogWarning, "SSU2: Incorrect TokenRequest len ", len); - return; - } - uint8_t nonce[12] = {0}; - uint8_t h[32]; - memcpy (h, header.buf, 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); - uint8_t * payload = buf + 32; - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, - i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) - { - LogPrint (eLogWarning, "SSU2: TokenRequest AEAD verification failed "); - return; - } - // payload - m_State = eSSU2SessionStateTokenRequestReceived; - HandlePayload (payload, len - 48); - SendRetry (); - } - - void SSU2Session::SendRetry () - { - // we are Bob - Header header; - uint8_t h[32], payload[72]; - // fill packet - header.h.connID = m_DestConnID; // dest id - RAND_bytes (header.buf + 8, 4); // random packet num - header.h.type = eSSU2Retry; - 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 - uint64_t token = 0; - if (m_TerminationReason == eSSU2TerminationReasonNormalClose) - token = m_Server.GetIncomingToken (m_RemoteEndpoint); - memcpy (h + 24, &token, 8); // token - // payload - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, 56 - payloadSize, m_RemoteEndpoint); - if (m_TerminationReason != eSSU2TerminationReasonNormalClose) - payloadSize += CreateTerminationBlock (payload + payloadSize, 56 - payloadSize); - payloadSize += CreatePaddingBlock (payload + payloadSize, 56 - payloadSize); - // encrypt - uint8_t nonce[12]; - CreateNonce (be32toh (header.h.packetNum), nonce); - i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, i2p::context.GetSSU2IntroKey (), nonce, payload, payloadSize + 16, true); - payloadSize += 16; - header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); - header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12)); - memset (nonce, 0, 12); - 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); - } - - bool SSU2Session::ProcessRetry (uint8_t * buf, size_t len) - { - // we are Alice - Header header; - memcpy (header.buf, buf, 16); - header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); - header.ll[1] ^= CreateHeaderMask (m_Address->i, buf + (len - 12)); - if (header.h.type != eSSU2Retry) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2Retry); - return false; - } - if (len < 48) - { - LogPrint (eLogWarning, "SSU2: Retry message too short ", len); - return false; - } - uint8_t nonce[12] = {0}; - uint64_t headerX[2]; // sourceConnID, token - m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); - uint64_t token = headerX[1]; - if (token) - m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); - // decrypt and handle payload - uint8_t * payload = buf + 32; - CreateNonce (be32toh (header.h.packetNum), nonce); - uint8_t h[32]; - memcpy (h, header.buf, 16); - memcpy (h + 16, &headerX, 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, - m_Address->i, nonce, payload, len - 48, false)) - { - LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed"); - return false; - } - m_State = eSSU2SessionStateTokenReceived; - HandlePayload (payload, len - 48); - if (!token) - { - // we should handle payload even for zero token to handle Datetime block and adjust clock in case of clock skew - LogPrint (eLogWarning, "SSU2: Retry token is zero"); - return false; - } - InitNoiseXKState1 (*m_NoiseState, m_Address->s); // reset Noise TODO: check state - SendSessionRequest (token); - return true; - } - - bool SSU2Session::ProcessHolePunch (uint8_t * buf, size_t len) - { - // we are Alice - LogPrint (eLogDebug, "SSU2: HolePunch"); - 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 != eSSU2HolePunch) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2HolePunch); - return false; - } - if (len < 48) - { - LogPrint (eLogWarning, "SSU2: HolePunch message too short ", len); - return false; - } - uint8_t nonce[12] = {0}; - uint64_t headerX[2]; // sourceConnID, token - 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; - 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: HolePunch AEAD verification failed "); - return false; - } - HandlePayload (payload, len - 48); - m_IsDataReceived = false; - // connect to Charlie - ConnectAfterIntroduction (); - - return true; - } - - bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len) - { - 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) - { - if (len < 8) - { - LogPrint (eLogWarning, "SSU2: Data message payload is too short ", (int)len); - return 0; - } - Header header; - header.h.connID = m_DestConnID; - header.h.packetNum = htobe32 (m_SendPacketNum); - header.h.type = eSSU2Data; - memset (header.h.flags, 0, 3); - if (flags) header.h.flags[0] = flags; - uint8_t nonce[12]; - CreateNonce (m_SendPacketNum, nonce); - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - 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++; - UpdateNumSentBytes (len + 32); - return m_SendPacketNum - 1; - } - - void SSU2Session::ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) - { - Header header; - header.ll[0] = m_SourceConnID; - memcpy (header.buf + 8, buf + 8, 8); - header.ll[1] ^= CreateHeaderMask (m_KeyDataReceive + 32, buf + (len - 12)); - if (header.h.type != eSSU2Data) - { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2Data); - if (IsEstablished ()) - SendQuickAck (); // in case it was SessionConfirmed - else - ResendHandshakePacket (); // assume we receive - return; - } - if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()) && - (!m_PathChallenge || from != m_PathChallenge->second)) // path challenge was not sent to this endpoint yet - { - LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); - SendPathChallenge (from); - } - if (len < 32) - { - LogPrint (eLogWarning, "SSU2: Data message too short ", len); - return; - } - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = len - 32; - uint32_t packetNum = be32toh (header.h.packetNum); - uint8_t nonce[12]; - CreateNonce (packetNum, nonce); - if (!m_Server.AEADChaCha20Poly1305Decrypt (buf + 16, payloadSize, header.buf, 16, - m_KeyDataReceive, nonce, payload, payloadSize)) - { - LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); - return; - } - UpdateNumReceivedBytes (len); - if (header.h.flags[0] & SSU2_FLAG_IMMEDIATE_ACK_REQUESTED) m_IsDataReceived = true; - if (!packetNum || UpdateReceivePacketNum (packetNum)) - HandlePayload (payload, payloadSize); - } - - void SSU2Session::HandlePayload (const uint8_t * buf, size_t len) - { - size_t offset = 0; - while (offset < len) - { - uint8_t blk = buf[offset]; - offset++; - auto size = bufbe16toh (buf + offset); - offset += 2; - LogPrint (eLogDebug, "SSU2: Block type ", (int)blk, " of size ", size); - if (offset + size > len) - { - LogPrint (eLogError, "SSU2: Unexpected block length ", size); - break; - } - switch (blk) - { - case eSSU2BlkDateTime: - LogPrint (eLogDebug, "SSU2: Datetime"); - HandleDateTime (buf + offset, size); - break; - case eSSU2BlkOptions: - LogPrint (eLogDebug, "SSU2: Options"); - break; - case eSSU2BlkRouterInfo: - LogPrint (eLogDebug, "SSU2: RouterInfo"); - HandleRouterInfo (buf + offset, size); - break; - case eSSU2BlkI2NPMessage: - { - LogPrint (eLogDebug, "SSU2: I2NP message"); - auto nextMsg = (buf[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); - nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header - memcpy (nextMsg->GetNTCP2Header (), buf + offset, size); - nextMsg->FromNTCP2 (); // SSU2 has the same format as NTCP2 - HandleI2NPMsg (std::move (nextMsg)); - m_IsDataReceived = true; - break; - } - case eSSU2BlkFirstFragment: - LogPrint (eLogDebug, "SSU2: First fragment"); - HandleFirstFragment (buf + offset, size); - m_IsDataReceived = true; - break; - case eSSU2BlkFollowOnFragment: - LogPrint (eLogDebug, "SSU2: Follow-on fragment"); - HandleFollowOnFragment (buf + offset, size); - m_IsDataReceived = true; - break; - case eSSU2BlkTermination: - { - if (size >= 9) - { - uint8_t rsn = buf[offset + 8]; // reason - LogPrint (eLogDebug, "SSU2: Termination reason=", (int)rsn); - if (IsEstablished () && rsn != eSSU2TerminationReasonTerminationReceived) - RequestTermination (eSSU2TerminationReasonTerminationReceived); - else if (m_State != eSSU2SessionStateTerminated) - { - if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived) - m_State = eSSU2SessionStateClosingConfirmed; - Done (); - } - } - else - LogPrint(eLogWarning, "SSU2: Unexpected termination block size ", size); - break; - } - case eSSU2BlkRelayRequest: - LogPrint (eLogDebug, "SSU2: RelayRequest"); - HandleRelayRequest (buf + offset, size); - m_IsDataReceived = true; - break; - case eSSU2BlkRelayResponse: - LogPrint (eLogDebug, "SSU2: RelayResponse"); - HandleRelayResponse (buf + offset, size); - m_IsDataReceived = true; - break; - case eSSU2BlkRelayIntro: - LogPrint (eLogDebug, "SSU2: RelayIntro"); - HandleRelayIntro (buf + offset, size); - m_IsDataReceived = true; - break; - case eSSU2BlkPeerTest: - LogPrint (eLogDebug, "SSU2: PeerTest msg=", (int)buf[offset], " code=", (int)buf[offset+1]); - HandlePeerTest (buf + offset, size); - if (buf[offset] < 5) - m_IsDataReceived = true; - break; - case eSSU2BlkNextNonce: - break; - case eSSU2BlkAck: - LogPrint (eLogDebug, "SSU2: Ack"); - HandleAck (buf + offset, size); - break; - case eSSU2BlkAddress: - LogPrint (eLogDebug, "SSU2: Address"); - HandleAddress (buf + offset, size); - break; - case eSSU2BlkIntroKey: - break; - case eSSU2BlkRelayTagRequest: - LogPrint (eLogDebug, "SSU2: RelayTagRequest"); - if (!m_RelayTag) - { - 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: - LogPrint (eLogDebug, "SSU2: RelayTag"); - m_RelayTag = bufbe32toh (buf + offset); - break; - case eSSU2BlkNewToken: - { - LogPrint (eLogDebug, "SSU2: New token"); - uint64_t token; - memcpy (&token, buf + offset + 4, 8); - m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, bufbe32toh (buf + offset)); - break; - } - case eSSU2BlkPathChallenge: - LogPrint (eLogDebug, "SSU2: Path challenge"); - SendPathResponse (buf + offset, size); - break; - case eSSU2BlkPathResponse: - { - LogPrint (eLogDebug, "SSU2: Path response"); - if (m_PathChallenge) - { - if (buf64toh (buf + offset) == m_PathChallenge->first) - { - m_RemoteEndpoint = m_PathChallenge->second; - m_PathChallenge.reset (nullptr); - } - } - break; - } - case eSSU2BlkFirstPacketNumber: - break; - case eSSU2BlkPadding: - LogPrint (eLogDebug, "SSU2: Padding"); - break; - default: - LogPrint (eLogWarning, "SSU2: Unknown block type ", (int)blk); - } - offset += size; - } - } - - void SSU2Session::HandleDateTime (const uint8_t * buf, size_t len) - { - int64_t offset = (int64_t)i2p::util::GetSecondsSinceEpoch () - (int64_t)bufbe32toh (buf); - switch (m_State) - { - case eSSU2SessionStateSessionRequestReceived: - case eSSU2SessionStateTokenRequestReceived: - case eSSU2SessionStateEstablished: - if (std::abs (offset) > SSU2_CLOCK_SKEW) - m_TerminationReason = eSSU2TerminationReasonClockSkew; - break; - case eSSU2SessionStateSessionCreatedReceived: - case eSSU2SessionStateTokenReceived: - if ((m_RemoteEndpoint.address ().is_v4 () && i2p::context.GetTesting ()) || - (m_RemoteEndpoint.address ().is_v6 () && i2p::context.GetTestingV6 ())) - { - if (m_Server.IsSyncClockFromPeers ()) - { - if (std::abs (offset) > SSU2_CLOCK_THRESHOLD) - { - LogPrint (eLogWarning, "SSU2: Time offset ", offset, " from ", m_RemoteEndpoint); - m_Server.AdjustTimeOffset (-offset, GetRemoteIdentity ()); - } - else - m_Server.AdjustTimeOffset (0, nullptr); - } - else if (std::abs (offset) > SSU2_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU2: Clock skew detected ", offset, ". Check your clock"); - i2p::context.SetError (eRouterErrorClockSkew); - } - } - break; - default: ; - }; - } - - void SSU2Session::HandleRouterInfo (const uint8_t * buf, size_t len) - { - if (len < 2) return; - // not from SessionConfirmed, we must add it instantly to use in next block - std::shared_ptr newRi; - if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) // compressed? - { - auto ri = ExtractRouterInfo (buf, len); - if (ri) - newRi = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); - } - else // use buffer directly. TODO: handle frag - newRi = i2p::data::netdb.AddRouterInfo (buf + 2, len - 2); - - if (newRi) - { - auto remoteIdentity = GetRemoteIdentity (); - if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) - { - // peer's RouterInfo update - SetRemoteIdentity (newRi->GetIdentity ()); - auto address = m_RemoteEndpoint.address ().is_v6 () ? newRi->GetSSU2V6Address () : newRi->GetSSU2V4Address (); - if (address) - { - m_Address = address; - if (IsOutgoing () && m_RelayTag && !address->IsIntroducer ()) - m_RelayTag = 0; // not longer introducer - } - } - } - } - - void SSU2Session::HandleAck (const uint8_t * buf, size_t len) - { - if (m_State == eSSU2SessionStateSessionConfirmedSent) - { - Established (); - return; - } - if (m_SentPackets.empty ()) return; - if (len < 5) return; - // acnt - uint32_t ackThrough = bufbe32toh (buf); - uint32_t firstPacketNum = ackThrough > buf[4] ? ackThrough - buf[4] : 0; - HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt - // ranges - len -= 5; - if (!len || m_SentPackets.empty ()) return; // don't handle ranges if nothing to acknowledge - const uint8_t * ranges = buf + 5; - while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS) - { - uint32_t lastPacketNum = firstPacketNum - 1; - if (*ranges > lastPacketNum) break; - lastPacketNum -= *ranges; ranges++; // nacks - if (*ranges > lastPacketNum + 1) break; - firstPacketNum = lastPacketNum - *ranges + 1; ranges++; // acks - len -= 2; - HandleAckRange (firstPacketNum, lastPacketNum, 0); - } - } - - void SSU2Session::HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts) - { - if (firstPacketNum > lastPacketNum) return; - auto it = m_SentPackets.begin (); - while (it != m_SentPackets.end () && it->first < firstPacketNum) it++; // find first acked packet - if (it == m_SentPackets.end () || it->first > lastPacketNum) return; // not found - auto it1 = it; - int numPackets = 0; - while (it1 != m_SentPackets.end () && it1->first <= lastPacketNum) - { - if (ts && !it1->second->numResends) - { - if (ts > it1->second->sendTime) - { - auto rtt = ts - it1->second->sendTime; - if (m_RTT != SSU2_UNKNOWN_RTT) - m_RTT = SSU2_RTT_EWMA_ALPHA * rtt + (1.0 - SSU2_RTT_EWMA_ALPHA) * m_RTT; - else - m_RTT = rtt; - m_RTO = m_RTT*SSU2_kAPPA; - m_MsgLocalExpirationTimeout = std::max (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN, - std::min (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX, - (unsigned int)(m_RTT * 1000 * I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR))); - m_MsgLocalSemiExpirationTimeout = m_MsgLocalExpirationTimeout / 2; - if (m_RTO < SSU2_MIN_RTO) m_RTO = SSU2_MIN_RTO; - if (m_RTO > SSU2_MAX_RTO) m_RTO = SSU2_MAX_RTO; - } - ts = 0; // update RTT one time per range - } - it1++; - numPackets++; - } - m_SentPackets.erase (it, it1); - if (numPackets > 0) - { - m_WindowSize += numPackets; - if (m_WindowSize > SSU2_MAX_WINDOW_SIZE) m_WindowSize = SSU2_MAX_WINDOW_SIZE; - } - } - - void SSU2Session::HandleAddress (const uint8_t * buf, size_t len) - { - boost::asio::ip::udp::endpoint ep; - if (ExtractEndpoint (buf, len, ep)) - { - LogPrint (eLogInfo, "SSU2: Our external address is ", ep); - if (!i2p::transport::transports.IsInReservedRange (ep.address ())) - { - i2p::context.UpdateAddress (ep.address ()); - // check our port - bool isV4 = ep.address ().is_v4 (); - if (ep.port () != m_Server.GetPort (isV4)) - { - LogPrint (eLogInfo, "SSU2: Our port ", ep.port (), " received from ", m_RemoteEndpoint, " is different from ", m_Server.GetPort (isV4)); - if (isV4) - { - if (i2p::context.GetTesting ()) - i2p::context.SetError (eRouterErrorSymmetricNAT); - else if (m_State == eSSU2SessionStatePeerTest) - i2p::context.SetError (eRouterErrorFullConeNAT); - } - else - { - if (i2p::context.GetTestingV6 ()) - i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT); - else if (m_State == eSSU2SessionStatePeerTest) - i2p::context.SetErrorV6 (eRouterErrorFullConeNAT); - } - } - else - { - if (isV4) - { - if (i2p::context.GetError () == eRouterErrorSymmetricNAT) - { - if (m_State == eSSU2SessionStatePeerTest) - i2p::context.SetStatus (eRouterStatusOK); - i2p::context.SetError (eRouterErrorNone); - } - else if (i2p::context.GetError () == eRouterErrorFullConeNAT) - i2p::context.SetError (eRouterErrorNone); - } - else - { - if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT) - { - if (m_State == eSSU2SessionStatePeerTest) - i2p::context.SetStatusV6 (eRouterStatusOK); - i2p::context.SetErrorV6 (eRouterErrorNone); - } - else if (i2p::context.GetErrorV6 () == eRouterErrorFullConeNAT) - i2p::context.SetErrorV6 (eRouterErrorNone); - } - } - } - } - } - - 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); - // same format as I2NP message block - msg->len = msg->offset + len + 7; - memcpy (msg->GetNTCP2Header (), buf, len); - std::shared_ptr m; - bool found = false; - auto it = m_IncompleteMessages.find (msgID); - if (it != m_IncompleteMessages.end ()) - { - found = true; - m = it->second; - } - else - { - m = m_Server.GetIncompleteMessagesPool ().AcquireShared (); - m_IncompleteMessages.emplace (msgID, m); - } - m->msg = msg; - m->nextFragmentNum = 1; - m->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - if (found && m->ConcatOutOfSequenceFragments ()) - { - // we have all follow-on fragments already - m->msg->FromNTCP2 (); - HandleI2NPMsg (std::move (m->msg)); - m_IncompleteMessages.erase (it); - } - } - - void SSU2Session::HandleFollowOnFragment (const uint8_t * buf, size_t len) - { - if (len < 5) return; - uint8_t fragmentNum = buf[0] >> 1; - if (!fragmentNum || fragmentNum >= SSU2_MAX_NUM_FRAGMENTS) - { - LogPrint (eLogWarning, "SSU2: Invalid follow-on fragment num ", fragmentNum); - return; - } - bool isLast = buf[0] & 0x01; - uint32_t msgID; memcpy (&msgID, buf + 1, 4); - auto it = m_IncompleteMessages.find (msgID); - if (it != m_IncompleteMessages.end ()) - { - if (fragmentNum < it->second->nextFragmentNum) return; // duplicate - if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS && - it->second->msg) - { - // in sequence - it->second->AttachNextFragment (buf + 5, len - 5); - if (isLast) - { - it->second->msg->FromNTCP2 (); - HandleI2NPMsg (std::move (it->second->msg)); - m_IncompleteMessages.erase (it); - } - else - { - if (it->second->ConcatOutOfSequenceFragments ()) - { - HandleI2NPMsg (std::move (it->second->msg)); - m_IncompleteMessages.erase (it); - } - else - it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - } - return; - } - } - else - { - // follow-on fragment before first fragment - auto msg = m_Server.GetIncompleteMessagesPool ().AcquireShared (); - msg->nextFragmentNum = 0; - it = m_IncompleteMessages.emplace (msgID, msg).first; - } - // insert out of sequence fragment - auto fragment = m_Server.GetFragmentsPool ().AcquireShared (); - memcpy (fragment->buf, buf + 5, len -5); - fragment->len = len - 5; - fragment->fragmentNum = fragmentNum; - fragment->isLast = isLast; - it->second->AddOutOfSequenceFragment (fragment); - } - - void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) - { - // we are Bob - if (len < 9) return; - auto mts = i2p::util::GetMillisecondsSinceEpoch (); - uint32_t nonce = bufbe32toh (buf + 1); // nonce - uint32_t relayTag = bufbe32toh (buf + 5); // relay tag - auto session = m_Server.FindRelaySession (relayTag); - if (!session) - { - LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found"); - // send relay response back to Alice - 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; - } - 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"); - - 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; - boost::asio::ip::udp::endpoint ep; - std::shared_ptr addr; - auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice - if (r) - { - 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)) - { - // obtain and check endpoint and address for HolePunch - if (ExtractEndpoint (buf + 47, asz, ep)) - { - if (!ep.address ().is_unspecified () && ep.port ()) - { - if (m_Server.IsSupported (ep.address ())) - { - 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 invalid endpoint"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } - } - else - { - LogPrint (eLogWarning, "SSU2: RelayIntro can't extract endpoint"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } - } - else - { - LogPrint (eLogWarning, "SSU2: RelayIntro signature verification failed"); - code = eSSU2RelayResponseCodeCharlieSignatureFailure; - } - } - else if (!attempts) - { - // RouterInfo might come in the next packet, try again - auto vec = std::make_shared >(len); - memcpy (vec->data (), buf, len); - auto s = shared_from_this (); - boost::asio::post (m_Server.GetService (), [s, vec, attempts]() - { - LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1); - s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1); - }); - return; - } - else - { - LogPrint (eLogWarning, "SSU2: RelayIntro unknown router to introduce"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } - // send relay response to Bob - 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) - { - // HolePunch from Charlie - // TODO: verify address and signature - // verify nonce - if (~htobe64 (((uint64_t)nonce << 32) | nonce) != m_DestConnID) - LogPrint (eLogWarning, "SSU2: Relay response nonce mismatch ", nonce, " connID=", m_DestConnID); - if (len >= 8) - { - // new token - uint64_t token; - memcpy (&token, buf + len - 8, 8); - m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); - } - return; - } - auto it = m_RelaySessions.find (nonce); - if (it != m_RelaySessions.end ()) - { - auto relaySession = it->second.first; - m_RelaySessions.erase (it); - if (relaySession && relaySession->IsEstablished ()) - { - // we are Bob, message from Charlie - auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); - uint8_t * payload = packet->payload; - payload[0] = eSSU2BlkRelayResponse; - htobe16buf (payload + 1, len); - memcpy (payload + 3, buf, len); // forward to Alice as is - packet->payloadSize = len + 3; - packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); - if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) - { - // sometimes Alice doesn't ack this RelayResponse in older versions - packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); - relaySession->m_SentPackets.emplace (packetNum, packet); - } - } - else - { - // we are Alice, message from Bob - if (!buf[1]) // status code accepted? - { - // verify signature - uint8_t csz = (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 (relaySession->GetRemoteIdentity (), buf + 12 + csz)) - { - if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet - { - // update Charlie's endpoint - if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) - { - // update token - uint64_t token; - memcpy (&token, buf + len - 8, 8); - m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, - token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); - // connect to Charlie, HolePunch will be ignored - relaySession->ConnectAfterIntroduction (); - } - else - LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); - } - } - else - { - LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); - relaySession->Done (); - } - } - else - { - LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); - relaySession->Done (); - } - } - } - else - LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); - } - - void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len) - { - // msgs 1-4 - if (len < 3) return; - uint8_t msg = buf[0]; - size_t offset = 3; // points to signed data - if (msg == 2 || msg == 4) offset += 32; // hash is presented for msg 2 and 4 only - if (len < offset + 5) return; - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - uint32_t nonce = bufbe32toh (buf + offset + 1); - switch (msg) // msg - { - case 1: // Bob from Alice - { - auto session = m_Server.GetRandomPeerTestSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6, - GetRemoteIdentity ()->GetIdentHash ()); - if (session) // session with Charlie - { - if (m_Server.AddPeerTest (nonce, shared_from_this (), ts/1000)) - { - auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); - // Alice's RouterInfo - auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); - if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; - packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; - if (!packet->payloadSize && r) - session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (packet->payloadSize + len + 48 > m_MaxPayloadSize) - { - // doesn't fit one message, send RouterInfo in separate message - uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); - packet->sendTime = ts; - session->m_SentPackets.emplace (packetNum, packet); - packet = m_Server.GetSentPacketsPool ().AcquireShared (); // new packet - } - // PeerTest to Charlie - packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, 2, - eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); - uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize, SSU2_FLAG_IMMEDIATE_ACK_REQUESTED); - packet->sendTime = ts; - session->m_SentPackets.emplace (packetNum, packet); - } - else - LogPrint (eLogInfo, "SSU2: Peer test 1 nonce ", nonce, " already exists. Ignored"); - } - else - { - // Charlie not found, send error back to Alice - 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); - 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]; - 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<128> s; - s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue - s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash - s.Insert (buf + 3, 32); // ahash - s.Insert (newSignedData.data (), asz + 10); // ver, nonce, ts, asz, Alice's endpoint - s.Sign (i2p::context.GetPrivateKeys (), newSignedData.data () + 10 + asz); - // send response (msg 3) back and msg 5 if accepted - SSU2PeerTestCode code = eSSU2PeerTestCodeAccept; - auto r = i2p::data::netdb.FindRouter (buf + 3); // find Alice - if (r) - { - size_t signatureLen = r->GetIdentity ()->GetSignatureLen (); - if (len >= offset + asz + 10 + signatureLen) - { - s.Reset (); - s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue - s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash - s.Insert (buf + offset, asz + 10); // signed data - if (s.Verify (r->GetIdentity (), buf + offset + asz + 10)) - { - if (!m_Server.FindSession (r->GetIdentity ()->GetIdentHash ())) - { - boost::asio::ip::udp::endpoint ep; - std::shared_ptr addr; - if (ExtractEndpoint (buf + offset + 10, asz, ep) && !ep.address ().is_unspecified () && ep.port ()) - addr = r->GetSSU2Address (ep.address ().is_v4 ()); - if (addr && m_Server.IsSupported (ep.address ()) && - i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ())) - { - if (!m_Server.IsConnectedRecently (ep)) // no alive hole punch - { - // send msg 5 to Alice - auto session = std::make_shared (m_Server, - 0, htobe64 (((uint64_t)nonce << 32) | nonce)); - session->m_RemoteEndpoint = ep; // might be different - m_Server.AddSession (session); - session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr); - } - else - code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; - } - else - code = eSSU2PeerTestCodeCharlieUnsupportedAddress; - } - else - code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; - } - else - code = eSSU2PeerTestCodeCharlieSignatureFailure; - } - else // maformed message - code = eSSU2PeerTestCodeCharlieReasonUnspecified; - } - else - code = eSSU2PeerTestCodeCharlieAliceIsUnknown; - // send msg 3 back to Bob - auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); - packet->payloadSize = CreatePeerTestBlock (packet->payload, m_MaxPayloadSize, 3, - code, nullptr, newSignedData.data (), newSignedData.size ()); - 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 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; - 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 - 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 - packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize, 4, - (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - 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 (eLogDebug, "SSU2: Unknown peer test 3 nonce ", nonce); - break; - } - case 4: // Alice from Bob - { - auto session = m_Server.GetRequestedPeerTest (nonce); - if (session) - { - if (buf[1] == eSSU2PeerTestCodeAccept) - { - if (GetRouterStatus () == eRouterStatusUnknown) - SetTestingState (true); - auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie - if (r && len >= offset + 9) - { - uint8_t asz = buf[offset + 9]; - 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)) - { - session->SetRemoteIdentity (r->GetIdentity ()); - auto addr = r->GetSSU2Address (m_Address->IsV4 ()); - if (addr && addr->IsPeerTesting ()) - { - if (session->GetMsgNumReceived () >= 5) - { - // 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 - { - session->m_Address = addr; - if (GetTestingState ()) - { - // schedule msg 6 with delay - if (!addr->host.is_unspecified () && addr->port) - { - session->SetRemoteEndpoint (boost::asio::ip::udp::endpoint (addr->host, addr->port)); - session->SendPeerTest (6, buf + offset, len - offset, addr, true); - } - SetTestingState (false); - if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ()) - { - SetRouterStatus (eRouterStatusFirewalled); - session->SetStatusChanged (); - if (m_Address->IsV4 ()) - m_Server.RescheduleIntroducersUpdateTimer (); - else - m_Server.RescheduleIntroducersUpdateTimerV6 (); - } - } - } - LogPrint (eLogDebug, "SSU2: Peer test 4 received from ", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), - " with information about ", i2p::data::GetIdentHashAbbreviation (i2p::data::IdentHash (buf + 3))); - } - else - { - LogPrint (eLogWarning, "SSU2: Peer test 4 address not found or not supported"); - session->Done (); - } - } - else - { - LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed"); - session->Done (); - } - } - else - { - LogPrint (eLogWarning, "SSU2: Peer test 4 router not found"); - session->Done (); - } - } - else - { - LogPrint (eLogInfo, "SSU2: Peer test 4 error code ", (int)buf[1], " from ", - i2p::data::GetIdentHashAbbreviation (buf[1] < 64 ? GetRemoteIdentity ()->GetIdentHash () : i2p::data::IdentHash (buf + 3))); - if (GetTestingState () && GetRouterStatus () != eRouterStatusFirewalled) - SetRouterStatus (eRouterStatusUnknown); - session->Done (); - } - } - else - LogPrint (eLogDebug, "SSU2: Unknown peer test 4 nonce ", nonce); - break; - } - default: - LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); - } - } - - void SSU2Session::HandleI2NPMsg (std::shared_ptr&& msg) - { - if (!msg) return; - uint32_t msgID = msg->GetMsgID (); - if (!msg->IsExpired ()) - { - // m_LastActivityTimestamp is updated in ProcessData before - if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)GetLastActivityTimestamp ()).second) - m_Handler.PutNextMessage (std::move (msg)); - else - LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received"); - } - else - LogPrint (eLogDebug, "SSU2: Message ", msgID, " expired"); - } - - bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep) - { - if (size < 2) return false; - int port = bufbe16toh (buf); - if (size == 6) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf + 2, 4); - ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port); - } - else if (size == 18) - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), buf + 2, 16); - ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6 (bytes), port); - } - else - { - LogPrint (eLogWarning, "SSU2: Address size ", int(size), " is not supported"); - return false; - } - return true; - } - - size_t SSU2Session::CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) - { - if (len < 6) return 0; - htobe16buf (buf, ep.port ()); - size_t size = 0; - if (ep.address ().is_v4 ()) - { - memcpy (buf + 2, ep.address ().to_v4 ().to_bytes ().data (), 4); - size = 6; - } - else if (ep.address ().is_v6 ()) - { - if (len < 18) return 0; - memcpy (buf + 2, ep.address ().to_v6 ().to_bytes ().data (), 16); - size = 18; - } - else - { - LogPrint (eLogWarning, "SSU2: Wrong address type ", ep.address ().to_string ()); - return 0; - } - return size; - } - - std::shared_ptr SSU2Session::FindLocalAddress () const - { - 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 (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); - } - } - } - - RouterStatus SSU2Session::GetRouterStatus () const - { - if (m_Address) - { - if (m_Address->IsV4 ()) - return i2p::context.GetStatus (); - if (m_Address->IsV6 ()) - return i2p::context.GetStatusV6 (); - } - return eRouterStatusUnknown; - } - - void SSU2Session::SetRouterStatus (RouterStatus status) const - { - if (m_Address) - { - if (m_Address->IsV4 ()) - i2p::context.SetStatus (status); - else if (m_Address->IsV6 ()) - i2p::context.SetStatusV6 (status); - } - } - - bool SSU2Session::GetTestingState () const - { - if (m_Address) - { - if (m_Address->IsV4 ()) - return i2p::context.GetTesting (); - if (m_Address->IsV6 ()) - return i2p::context.GetTestingV6 (); - } - return false; - } - - void SSU2Session::SetTestingState (bool testing) const - { - if (m_Address) - { - if (m_Address->IsV4 ()) - i2p::context.SetTesting (testing); - else if (m_Address->IsV6 ()) - i2p::context.SetTestingV6 (testing); - } - if (!testing) - m_Server.AdjustTimeOffset (0, nullptr); // reset time offset when testing is over - } - - size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) - { - if (len < 9) return 0; - buf[0] = eSSU2BlkAddress; - size_t size = CreateEndpoint (buf + 3, len - 3, ep); - if (!size) return 0; - htobe16buf (buf + 1, size); - return size + 3; - } - - size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r) - { - 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 = riBuffer->GetBufferLen (); - if (size + 5 < len) - { - memcpy (buf + 5, riBuffer->data (), size); - buf[3] = 0; // flag - } - else - { - i2p::data::GzipDeflator deflator; - deflator.SetCompressionLevel (9); - 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; - buf[0] = eSSU2BlkAck; - uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); - htobe32buf (buf + 3, ackThrough); // Ack Through - uint16_t acnt = 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 - while (it != m_OutOfSequencePackets.rend () && *it == ackThrough - acnt - 1) - { - acnt++; - if (acnt >= SSU2_MAX_NUM_ACK_PACKETS) - break; - else - it++; - } - // ranges - 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) - { - auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); - acnt = SSU2_MAX_NUM_ACNT; - if (d.quot > maxNumRanges) - { - d.quot = maxNumRanges; - d.rem = 0; - } - // Acks only 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++; - } - } - 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) - { - 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 + m_NumRanges*2); - return 8 + m_NumRanges*2; - } - - size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) - { - if (len < 3 || len < minSize) return 0; - size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 - if (paddingSize + 3 > len) paddingSize = len - 3; - else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; - buf[0] = eSSU2BlkPadding; - htobe16buf (buf + 1, paddingSize); - memset (buf + 3, 0, paddingSize); - return paddingSize + 3; - } - - size_t SSU2Session::CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg) - { - msg->ToNTCP2 (); - auto msgBuf = msg->GetNTCP2Header (); - auto msgLen = msg->GetNTCP2Length (); - if (msgLen + 3 > len) msgLen = len - 3; - buf[0] = eSSU2BlkI2NPMessage; - htobe16buf (buf + 1, msgLen); // size - memcpy (buf + 3, msgBuf, msgLen); - return msgLen + 3; - } - - size_t SSU2Session::CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg) - { - if (len < 12) return 0; - msg->ToNTCP2 (); - auto msgBuf = msg->GetNTCP2Header (); - auto msgLen = msg->GetNTCP2Length (); - if (msgLen + 3 <= len) return 0; - msgLen = len - 3; - buf[0] = eSSU2BlkFirstFragment; - htobe16buf (buf + 1, msgLen); // size - memcpy (buf + 3, msgBuf, msgLen); - msg->offset = (msgBuf - msg->buf) + msgLen; - return msgLen + 3; - } - - size_t SSU2Session::CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID) - { - if (len < 8) return 0; - bool isLast = true; - auto msgLen = msg->len - msg->offset; - if (msgLen + 8 > len) - { - msgLen = len - 8; - isLast = false; - } - buf[0] = eSSU2BlkFollowOnFragment; - htobe16buf (buf + 1, msgLen + 5); // size - fragmentNum++; - buf[3] = fragmentNum << 1; - if (isLast) buf[3] |= 0x01; - memcpy (buf + 4, &msgID, 4); - memcpy (buf + 8, msg->buf + msg->offset, msgLen); - msg->offset += msgLen; - return msgLen + 8; - } - - size_t SSU2Session::CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen) - { - buf[0] = eSSU2BlkRelayIntro; - size_t payloadSize = 1/* flag */ + 32/* Alice router hash */ + introDataLen; - if (payloadSize + 3 > len) return 0; - htobe16buf (buf + 1, payloadSize); // size - buf[3] = 0; // flag - memcpy (buf + 4, GetRemoteIdentity ()->GetIdentHash (), 32); // Alice router hash - memcpy (buf + 36, introData, introDataLen); - return payloadSize + 3; - } - - size_t SSU2Session::CreateRelayResponseBlock (uint8_t * buf, size_t len, - SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4) - { - buf[0] = eSSU2BlkRelayResponse; - buf[3] = 0; // flag - buf[4] = code; // code - htobe32buf (buf + 5, nonce); // nonce - htobe32buf (buf + 9, i2p::util::GetSecondsSinceEpoch ()); // timestamp - buf[13] = 2; // ver - size_t csz = 0; - if (code == eSSU2RelayResponseCodeAccept) - { - auto addr = i2p::context.GetRouterInfo ().GetSSU2Address (v4); - if (!addr) - { - LogPrint (eLogError, "SSU2: Can't find local address for RelayResponse"); - return 0; - } - csz = CreateEndpoint (buf + 15, len - 15, boost::asio::ip::udp::endpoint (addr->host, addr->port)); - if (!csz) - { - LogPrint (eLogError, "SSU2: Can't create local endpoint for RelayResponse"); - return 0; - } - } - buf[14] = csz; // csz - // signature - size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - if (15 + csz + signatureLen > len) - { - LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); - return 0; - } - SignedData<128> s; - s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue - if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie - s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash - else // Bob's reject - s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // bhash - s.Insert (buf + 5, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint - s.Sign (i2p::context.GetPrivateKeys (), buf + 15 + csz); - size_t payloadSize = 12 + csz + signatureLen; - if (!code) - { - if (payloadSize + 11 > len) - { - LogPrint (eLogError, "SSU2: Buffer for RelayResponse token is too small ", len); - return 0; - } - memcpy (buf + 3 + payloadSize, &token, 8); - payloadSize += 8; - } - htobe16buf (buf + 1, payloadSize); // size - return payloadSize + 3; - } - - size_t SSU2Session::CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, - const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen) - { - buf[0] = eSSU2BlkPeerTest; - size_t payloadSize = 3/* msg, code, flag */ + signedDataLen; - if (routerHash) payloadSize += 32; // router hash - if (payloadSize + 3 > len) return 0; - htobe16buf (buf + 1, payloadSize); // size - buf[3] = msg; // msg - buf[4] = (uint8_t)code; // code - buf[5] = 0; //flag - size_t offset = 6; - if (routerHash) - { - memcpy (buf + offset, routerHash, 32); // router hash - offset += 32; - } - memcpy (buf + offset, signedData, signedDataLen); - return payloadSize + 3; - } - - size_t SSU2Session::CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce) - { - auto localAddress = FindLocalAddress (); - if (!localAddress || !localAddress->port || localAddress->host.is_unspecified () || - localAddress->host.is_v4 () != m_RemoteEndpoint.address ().is_v4 ()) - { - LogPrint (eLogWarning, "SSU2: Can't find local address for peer test"); - return 0; - } - // signed data - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint8_t signedData[96]; - signedData[0] = 2; // ver - htobe32buf (signedData + 1, nonce); - htobe32buf (signedData + 5, ts); - size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); - signedData[9] = asz; - // signature - SignedData<128> s; - s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue - s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash - s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint - s.Sign (i2p::context.GetPrivateKeys (), signedData + 10 + asz); - return CreatePeerTestBlock (buf, len, 1, eSSU2PeerTestCodeAccept, nullptr, - signedData, 10 + asz + i2p::context.GetIdentity ()->GetSignatureLen ()); - } - - size_t SSU2Session::CreateTerminationBlock (uint8_t * buf, size_t len) - { - buf[0] = eSSU2BlkTermination; - htobe16buf (buf + 1, 9); - htobe64buf (buf + 3, m_ReceivePacketNum); - buf[11] = (uint8_t)m_TerminationReason; - return 12; - } - - std::shared_ptr SSU2Session::ExtractRouterInfo (const uint8_t * buf, size_t size) - { - if (size < 2) return nullptr; - // TODO: handle frag - std::shared_ptr ri; - if (buf[0] & SSU2_ROUTER_INFO_FLAG_GZIP) - { - 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) - ri = std::make_shared(uncompressed, uncompressedSize); - else - LogPrint (eLogInfo, "SSU2: RouterInfo decompression failed ", uncompressedSize); - } - 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; - } - - bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) - { - if (packetNum <= m_ReceivePacketNum) return false; // duplicate - if (packetNum == m_ReceivePacketNum + 1) - { - if (!m_OutOfSequencePackets.empty ()) - { - auto it = m_OutOfSequencePackets.begin (); - if (*it == packetNum + 1) - { - // first out of sequence packet is in sequence now - packetNum++; it++; - while (it != m_OutOfSequencePackets.end ()) - { - if (*it == packetNum + 1) - { - packetNum++; - it++; - } - else // next out of sequence - break; - } - m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it); - } - m_NumRanges = 0; // recalculate ranges when create next Ack - } - m_ReceivePacketNum = packetNum; - } - else - { - if (m_NumRanges && (m_OutOfSequencePackets.empty () || - packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1)) - m_NumRanges = 0; // reset ranges if received packet is not next - m_OutOfSequencePackets.insert (packetNum); - } - return true; - } - - void SSU2Session::SendQuickAck () - { - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 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); - } - - void SSU2Session::SendTermination () - { - uint8_t payload[32]; - size_t payloadSize = CreateTerminationBlock (payload, 32); - payloadSize += CreatePaddingBlock (payload + payloadSize, 32 - payloadSize); - SendData (payload, payloadSize); - } - - void SSU2Session::SendPathResponse (const uint8_t * data, size_t len) - { - 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; - } - payload[payloadSize] = eSSU2BlkPathResponse; - htobe16buf (payload + payloadSize + 1, len); - memcpy (payload + payloadSize + 3, data, len); - payloadSize += len + 3; - // ack block - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // padding - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); - } - - void SSU2Session::SendPathChallenge (const boost::asio::ip::udp::endpoint& to) - { - AdjustMaxPayloadSize (SSU2_MIN_PACKET_SIZE); // reduce to minimum - m_WindowSize = SSU2_MIN_WINDOW_SIZE; // reduce window to minimum - - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 0; - // datetime block - payload[0] = eSSU2BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); - payloadSize += 7; - // address block with new address - payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, to); - // path challenge block - payload[payloadSize] = eSSU2BlkPathChallenge; - uint64_t challenge; - RAND_bytes ((uint8_t *)&challenge, 8); - htobe16buf (payload + payloadSize + 1, 8); // always 8 bytes - htobuf64 (payload + payloadSize + 3, challenge); - payloadSize += 11; - m_PathChallenge = std::make_unique >(challenge, to); - // ack block - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // padding block - if (payloadSize < m_MaxPayloadSize) - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - // send to new endpoint - auto existing = m_RemoteEndpoint; - m_RemoteEndpoint = to; // send path challenge to new endpoint - SendData (payload, payloadSize); - m_RemoteEndpoint = existing; // restore endpoint back until path response received - } - - void SSU2Session::CleanUp (uint64_t ts) - { - for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) - { - if (ts > it->second->lastFragmentInsertTime + SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) - { - LogPrint (eLogWarning, "SSU2: message ", it->first, " was not completed in ", SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); - it = m_IncompleteMessages.erase (it); - } - else - ++it; - } - if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > GetLastActivityTimestamp () + SSU2_DECAY_INTERVAL) - // decay - m_ReceivedI2NPMsgIDs.clear (); - else - { - // delete old received msgIDs - for (auto it = m_ReceivedI2NPMsgIDs.begin (); it != m_ReceivedI2NPMsgIDs.end ();) - { - if (ts > it->second + SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT) - it = m_ReceivedI2NPMsgIDs.erase (it); - else - ++it; - } - } - if (!m_OutOfSequencePackets.empty ()) - { - int ranges = 0; - while (ranges < 8 && !m_OutOfSequencePackets.empty () && - (m_OutOfSequencePackets.size () > 2*SSU2_MAX_NUM_ACK_RANGES || - *m_OutOfSequencePackets.rbegin () > m_ReceivePacketNum + SSU2_MAX_NUM_ACK_PACKETS)) - { - uint32_t packet = *m_OutOfSequencePackets.begin (); - if (packet > m_ReceivePacketNum + 1) - { - // like we've just received all packets before first - packet--; - m_ReceivePacketNum = packet - 1; - UpdateReceivePacketNum (packet); - ranges++; - } - else - { - LogPrint (eLogError, "SSU2: Out of sequence packet ", packet, " is less than last received ", m_ReceivePacketNum); - break; - } - } - if (m_OutOfSequencePackets.size () > 255*4) - { - // seems we have a serious network issue - m_ReceivePacketNum = *m_OutOfSequencePackets.rbegin (); - m_OutOfSequencePackets.clear (); - } - } - - for (auto it = m_RelaySessions.begin (); it != m_RelaySessions.end ();) - { - if (ts > it->second.second + SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT) - { - LogPrint (eLogInfo, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted"); - it = m_RelaySessions.erase (it); - } - else - ++it; - } - if (m_PathChallenge) - RequestTermination (eSSU2TerminationReasonNormalClose); - } - - void SSU2Session::FlushData () - { - bool sent = SendQueue (); // if we have something to send - if (sent) - SetSendQueueSize (m_SendQueue.size ()); - if (m_IsDataReceived) - { - if (!sent) SendQuickAck (); - m_Handler.Flush (); - m_IsDataReceived = false; - } - else if (!sent && !m_SentPackets.empty ()) // if only acks received, nothing sent and we still have something to resend - Resend (i2p::util::GetMillisecondsSinceEpoch ()); // than right time to resend - } - - i2p::data::RouterInfo::SupportedTransports SSU2Session::GetTransportType () const - { - return m_RemoteEndpoint.address ().is_v4 () ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6; - } -} -} diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h deleted file mode 100644 index ee26255f..00000000 --- a/libi2pd/SSU2Session.h +++ /dev/null @@ -1,419 +0,0 @@ -/* -* Copyright (c) 2022-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef SSU2_SESSION_H__ -#define SSU2_SESSION_H__ - -#include -#include -#include -#include -#include -#include -#include "version.h" -#include "Crypto.h" -#include "RouterInfo.h" -#include "RouterContext.h" -#include "TransportSession.h" - -namespace i2p -{ -namespace transport -{ - const int SSU2_CONNECT_TIMEOUT = 5; // 5 seconds - 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 - const int SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT = 52*60; // for next token block, in seconds - const int SSU2_TOKEN_EXPIRATION_THRESHOLD = 2; // in seconds - const int SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT = 10; // in seconds - const int SSU2_PEER_TEST_EXPIRATION_TIMEOUT = 60; // 60 seconds - const size_t SSU2_MAX_PACKET_SIZE = 1500; - const size_t SSU2_MIN_PACKET_SIZE = 1280; - const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds - const int SSU2_MAX_NUM_RESENDS = 5; - const int SSU2_RESEND_ATTEMPT_MIN_INTERVAL = 3; // in milliseconds - const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds - const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check - const int SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT = 10; // in seconds - const int SSU2_DECAY_INTERVAL = 20; // in seconds - const size_t SSU2_MIN_WINDOW_SIZE = 16; // in packets - const size_t SSU2_MAX_WINDOW_SIZE = 256; // in packets - const size_t SSU2_MIN_RTO = 100; // in milliseconds - const size_t SSU2_INITIAL_RTO = 540; // in milliseconds - const size_t SSU2_MAX_RTO = 2500; // in milliseconds - const double SSU2_UNKNOWN_RTT = -1; - const double SSU2_RTT_EWMA_ALPHA = 0.125; - const float SSU2_kAPPA = 1.8; - const int SSU2_MAX_NUM_ACNT = 255; // acnt, acks or nacks - const int SSU2_MAX_NUM_ACK_PACKETS = 511; // ackthrough + acnt + 1 range - const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send - const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64; - const int SSU2_SEND_DATETIME_NUM_PACKETS = 256; - const int SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION = MAKE_VERSION_NUMBER(0, 9, 64); // 0.9.64 - - // flags - const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01; - - enum SSU2MessageType - { - eSSU2SessionRequest = 0, - eSSU2SessionCreated = 1, - eSSU2SessionConfirmed = 2, - eSSU2Data = 6, - eSSU2PeerTest = 7, - eSSU2Retry = 9, - eSSU2TokenRequest = 10, - eSSU2HolePunch = 11 - }; - - enum SSU2BlockType - { - eSSU2BlkDateTime = 0, - eSSU2BlkOptions, // 1 - eSSU2BlkRouterInfo, // 2 - eSSU2BlkI2NPMessage, // 3 - eSSU2BlkFirstFragment, // 4 - eSSU2BlkFollowOnFragment, // 5 - eSSU2BlkTermination, // 6 - eSSU2BlkRelayRequest, // 7 - eSSU2BlkRelayResponse, // 8 - eSSU2BlkRelayIntro, // 9 - eSSU2BlkPeerTest, // 10 - eSSU2BlkNextNonce, // 11 - eSSU2BlkAck, // 12 - eSSU2BlkAddress, // 13 - eSSU2BlkIntroKey, // 14 - eSSU2BlkRelayTagRequest, // 15 - eSSU2BlkRelayTag, // 16 - eSSU2BlkNewToken, // 17 - eSSU2BlkPathChallenge, // 18 - eSSU2BlkPathResponse, // 19 - eSSU2BlkFirstPacketNumber, // 20 - eSSU2BlkPadding = 254 - }; - - enum SSU2SessionState - { - eSSU2SessionStateUnknown, - eSSU2SessionStateTokenReceived, - eSSU2SessionStateSessionRequestSent, - eSSU2SessionStateSessionRequestReceived, - eSSU2SessionStateSessionCreatedSent, - eSSU2SessionStateSessionCreatedReceived, - eSSU2SessionStateSessionConfirmedSent, - eSSU2SessionStateEstablished, - eSSU2SessionStateClosing, - eSSU2SessionStateClosingConfirmed, - eSSU2SessionStateTerminated, - eSSU2SessionStateFailed, - eSSU2SessionStateIntroduced, - eSSU2SessionStateHolePunch, - eSSU2SessionStatePeerTest, - eSSU2SessionStateTokenRequestReceived - }; - - enum SSU2PeerTestCode - { - eSSU2PeerTestCodeAccept = 0, - eSSU2PeerTestCodeBobReasonUnspecified = 1, - eSSU2PeerTestCodeBobNoCharlieAvailable = 2, - eSSU2PeerTestCodeBobLimitExceeded = 3, - eSSU2PeerTestCodeBobSignatureFailure = 4, - eSSU2PeerTestCodeCharlieReasonUnspecified = 64, - eSSU2PeerTestCodeCharlieUnsupportedAddress = 65, - eSSU2PeerTestCodeCharlieLimitExceeded = 66, - eSSU2PeerTestCodeCharlieSignatureFailure = 67, - eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected = 68, - eSSU2PeerTestCodeCharlieAliceIsBanned = 69, - eSSU2PeerTestCodeCharlieAliceIsUnknown = 70, - eSSU2PeerTestCodeUnspecified = 128 - }; - - enum SSU2RelayResponseCode - { - eSSU2RelayResponseCodeAccept = 0, - eSSU2RelayResponseCodeBobRelayTagNotFound = 5, - eSSU2RelayResponseCodeCharlieUnsupportedAddress = 65, - eSSU2RelayResponseCodeCharlieSignatureFailure = 67, - eSSU2RelayResponseCodeCharlieAliceIsUnknown = 70 - }; - - enum SSU2TerminationReason - { - eSSU2TerminationReasonNormalClose = 0, - eSSU2TerminationReasonTerminationReceived = 1, - eSSU2TerminationReasonIdleTimeout = 2, - eSSU2TerminationReasonRouterShutdown = 3, - eSSU2TerminationReasonDataPhaseAEADFailure= 4, - eSSU2TerminationReasonIncompatibleOptions = 5, - eSSU2TerminationReasonTncompatibleSignatureType = 6, - eSSU2TerminationReasonClockSkew = 7, - eSSU2TerminationPaddingViolation = 8, - eSSU2TerminationReasonAEADFramingError = 9, - eSSU2TerminationReasonPayloadFormatError = 10, - eSSU2TerminationReasonSessionRequestError = 11, - eSSU2TerminationReasonSessionCreatedError = 12, - eSSU2TerminationReasonSessionConfirmedError = 13, - eSSU2TerminationReasonTimeout = 14, - eSSU2TerminationReasonRouterInfoSignatureVerificationFail = 15, - eSSU2TerminationReasonInvalidS = 16, - eSSU2TerminationReasonBanned = 17, - eSSU2TerminationReasonBadToken = 18, - eSSU2TerminationReasonConnectionLimits = 19, - eSSU2TerminationReasonIncompatibleVersion = 20, - eSSU2TerminationReasonWrongNetID = 21, - eSSU2TerminationReasonReplacedByNewSession = 22 - }; - - struct SSU2IncompleteMessage - { - struct Fragment - { - uint8_t buf[SSU2_MAX_PACKET_SIZE]; - size_t len; - int fragmentNum; - bool isLast; - std::shared_ptr next; - }; - - std::shared_ptr msg; - int nextFragmentNum; - uint32_t lastFragmentInsertTime; // in seconds - std::shared_ptr outOfSequenceFragments; // #1 and more - - void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); - bool ConcatOutOfSequenceFragments (); // true if message complete - void AddOutOfSequenceFragment (std::shared_ptr fragment); - }; - - struct SSU2SentPacket - { - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = 0; - uint64_t sendTime; // in milliseconds - int numResends = 0; - }; - - // RouterInfo flags - const uint8_t SSU2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; - const uint8_t SSU2_ROUTER_INFO_FLAG_GZIP = 0x02; - - class SSU2Server; - class SSU2Session: public TransportSession, public std::enable_shared_from_this - { - protected: - - union Header - { - uint64_t ll[2]; - uint8_t buf[16]; - struct - { - uint64_t connID; - uint32_t packetNum; - uint8_t type; - uint8_t flags[3]; - } h; - }; - - 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; - - public: - - SSU2Session (SSU2Server& server, std::shared_ptr in_RemoteRouter = nullptr, - 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; }; - - virtual void Connect (); - bool Introduce (std::shared_ptr session, uint32_t relayTag); - void WaitForIntroduction (); - void SendPeerTest (); // Alice, Data message - void SendKeepAlive (); - void RequestTermination (SSU2TerminationReason reason); - void CleanUp (uint64_t ts); - void FlushData (); - void Done () override; - void SendLocalRouterInfo (bool update) 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 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; }; - - 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); - 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 (); - bool SendQueue (); // returns true if ack block was sent - bool SendFragmentedMessage (std::shared_ptr msg); - void ResendHandshakePacket (); - void ConnectAfterIntroduction (); - - void ProcessSessionRequest (Header& header, uint8_t * buf, size_t len); - void ProcessTokenRequest (Header& header, uint8_t * buf, size_t len); - - void SendSessionRequest (uint64_t token = 0); - void SendSessionCreated (const uint8_t * X); - void SendSessionConfirmed (const uint8_t * Y); - void KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba); - void SendTokenRequest (); - void SendRetry (); - uint32_t SendData (const uint8_t * buf, size_t len, uint8_t flags = 0); // returns packet num - void SendQuickAck (); - void SendTermination (); - void SendPathResponse (const uint8_t * data, size_t len); - void SendPathChallenge (const boost::asio::ip::udp::endpoint& to); - - void HandleDateTime (const uint8_t * buf, size_t len); - void HandleRouterInfo (const uint8_t * buf, size_t len); - void HandleAck (const uint8_t * buf, size_t len); - void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts); - 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 (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); - 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); - virtual void HandlePeerTest (const uint8_t * buf, size_t len); - void HandleI2NPMsg (std::shared_ptr&& msg); - - 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 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, uint32_t nonce); // Alice - size_t CreateTerminationBlock (uint8_t * buf, size_t len); - - private: - - SSU2Server& m_Server; - std::shared_ptr m_EphemeralKeys; - std::unique_ptr m_NoiseState; - std::unique_ptr m_SessionConfirmedFragment; // for Bob if applicable or second fragment for Alice - 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, m_RemotePeerTestTransports; - int m_RemoteVersion; - uint64_t m_DestConnID, m_SourceConnID; - SSU2SessionState m_State; - uint8_t m_KeyDataSend[64], m_KeyDataReceive[64]; - uint32_t m_SendPacketNum, m_ReceivePacketNum, m_LastDatetimeSentPacketNum; - std::set m_OutOfSequencePackets; // packet nums > receive packet num - std::map > m_SentPackets; // packetNum -> packet - std::unordered_map > m_IncompleteMessages; // msgID -> I2NP - std::unordered_map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice - std::list > m_SendQueue; - i2p::I2NPMessagesHandler m_Handler; - std::list > m_IntermediateQueue; // from transports - mutable std::mutex m_IntermediateQueueMutex; - bool m_IsDataReceived; - double m_RTT; - int m_MsgLocalExpirationTimeout; - int m_MsgLocalSemiExpirationTimeout; - size_t m_WindowSize, m_RTO; - uint32_t m_RelayTag; // between Bob and Charlie - OnEstablished m_OnEstablished; // callback from Established - boost::asio::deadline_timer m_ConnectTimer; - SSU2TerminationReason m_TerminationReason; - size_t m_MaxPayloadSize; - std::unique_ptr > m_PathChallenge; - std::unordered_map m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds - uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds - int m_NumRanges; - uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any - }; - - inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) - { - uint64_t data = 0; - i2p::crypto::ChaCha20 ((uint8_t *)&data, 8, kh, nonce, (uint8_t *)&data); - return data; - } - - inline void CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); - } -} -} - -#endif diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp new file mode 100644 index 00000000..6365381e --- /dev/null +++ b/libi2pd/SSUData.cpp @@ -0,0 +1,516 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include "Log.h" +#include "Timestamp.h" +#include "NetDb.hpp" +#include "SSU.h" +#include "SSUData.h" + +namespace i2p +{ +namespace transport +{ + void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) + { + if (msg->len + fragmentSize > msg->maxLen) + { + LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); + auto newMsg = NewI2NPMessage (); + *newMsg = *msg; + msg = newMsg; + } + if (msg->Concat (fragment, fragmentSize) < fragmentSize) + LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); + nextFragmentNum++; + } + + SSUData::SSUData (SSUSession& session): + m_Session (session), m_ResendTimer (session.GetService ()), + m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), + m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) + { + } + + SSUData::~SSUData () + { + } + + void SSUData::Start () + { + } + + void SSUData::Stop () + { + m_ResendTimer.cancel (); + m_IncompleteMessages.clear (); + m_SentMessages.clear (); + m_ReceivedMessages.clear (); + } + + void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) + { + if (!remoteRouter) return; + auto ssuAddress = remoteRouter->GetSSUAddress (); + if (ssuAddress && ssuAddress->ssu->mtu) + { + if (m_Session.IsV6 ()) + m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; + else + m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; + if (m_PacketSize > 0) + { + // make sure packet size multiple of 16 + m_PacketSize >>= 4; + m_PacketSize <<= 4; + if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; + LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize); + } + else + { + LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu); + m_PacketSize = m_MaxPacketSize; + } + } + } + + void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent) + { + auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); + if (routerInfo) + AdjustPacketSize (routerInfo); + } + + void SSUData::ProcessSentMessageAck (uint32_t msgID) + { + auto it = m_SentMessages.find (msgID); + if (it != m_SentMessages.end ()) + { + m_SentMessages.erase (it); + if (m_SentMessages.empty ()) + m_ResendTimer.cancel (); + } + } + + void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag) + { + if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) + { + // explicit ACKs + uint8_t numAcks =*buf; + buf++; + for (int i = 0; i < numAcks; i++) + ProcessSentMessageAck (bufbe32toh (buf+i*4)); + buf += numAcks*4; + } + if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) + { + // explicit ACK bitfields + uint8_t numBitfields =*buf; + buf++; + for (int i = 0; i < numBitfields; i++) + { + uint32_t msgID = bufbe32toh (buf); + buf += 4; // msgID + auto it = m_SentMessages.find (msgID); + // process individual Ack bitfields + bool isNonLast = false; + int fragment = 0; + do + { + uint8_t bitfield = *buf; + isNonLast = bitfield & 0x80; + bitfield &= 0x7F; // clear MSB + if (bitfield && it != m_SentMessages.end ()) + { + int numSentFragments = it->second->fragments.size (); + // process bits + uint8_t mask = 0x01; + for (int j = 0; j < 7; j++) + { + if (bitfield & mask) + { + if (fragment < numSentFragments) + it->second->fragments[fragment] = nullptr; + } + fragment++; + mask <<= 1; + } + } + buf++; + } + while (isNonLast); + } + } + } + + void SSUData::ProcessFragments (uint8_t * buf) + { + uint8_t numFragments = *buf; // number of fragments + buf++; + for (int i = 0; i < numFragments; i++) + { + uint32_t msgID = bufbe32toh (buf); // message ID + buf += 4; + uint8_t frag[4] = {0}; + memcpy (frag + 1, buf, 3); + buf += 3; + uint32_t fragmentInfo = bufbe32toh (frag); // fragment info + uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 + bool isLast = fragmentInfo & 0x010000; // bit 16 + uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 + if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) + { + LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); + return; + } + + // find message with msgID + auto it = m_IncompleteMessages.find (msgID); + if (it == m_IncompleteMessages.end ()) + { + // create new message + auto msg = NewI2NPShortMessage (); + msg->len -= I2NP_SHORT_HEADER_SIZE; + it = m_IncompleteMessages.insert (std::make_pair (msgID, + m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (std::move (msg)))).first; + } + auto& incompleteMessage = it->second; + // mark fragment as received + if (fragmentNum < 64) + incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum); + else + LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); + + // handle current fragment + if (fragmentNum == incompleteMessage->nextFragmentNum) + { + // expected fragment + incompleteMessage->AttachNextFragment (buf, fragmentSize); + if (!isLast && !incompleteMessage->savedFragments.empty ()) + { + // try saved fragments + for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) + { + auto& savedFragment = *it1; + if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) + { + incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); + isLast = savedFragment->isLast; + incompleteMessage->savedFragments.erase (it1++); + } + else + break; + } + if (isLast) + LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); + } + } + else + { + if (fragmentNum < incompleteMessage->nextFragmentNum) + // duplicate fragment + LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); + else + { + // missing fragment + LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); + auto savedFragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (fragmentNum, buf, fragmentSize, isLast); + if (incompleteMessage->savedFragments.insert (savedFragment).second) + incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + else + LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); + } + isLast = false; + } + + if (isLast) + { + // delete incomplete message + auto msg = incompleteMessage->msg; + incompleteMessage->msg = nullptr; + m_IncompleteMessages.erase (msgID); + // process message + SendMsgAck (msgID); + msg->FromSSU (msgID); + if (m_Session.GetState () == eSessionStateEstablished) + { + if (!m_ReceivedMessages.count (msgID)) + { + m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); + m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime); + if (!msg->IsExpired ()) + { + m_Handler.PutNextMessage (std::move (msg)); + } + else + LogPrint (eLogDebug, "SSU: message expired"); + } + else + LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); + } + else + { + // we expect DeliveryStatus + if (msg->GetTypeID () == eI2NPDeliveryStatus) + { + LogPrint (eLogDebug, "SSU: session established"); + m_Session.Established (); + } + else + LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); + } + } + else + SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits); + buf += fragmentSize; + } + } + + void SSUData::FlushReceivedMessage () + { + m_Handler.Flush (); + } + + void SSUData::ProcessMessage (uint8_t * buf, size_t len) + { + //uint8_t * start = buf; + uint8_t flag = *buf; + buf++; + LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); + // process acks if presented + if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) + ProcessAcks (buf, flag); + // extended data if presented + if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) + { + uint8_t extendedDataSize = *buf; + buf++; // size + LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); + buf += extendedDataSize; + } + // process data + ProcessFragments (buf); + } + + void SSUData::Send (std::shared_ptr msg) + { + uint32_t msgID = msg->ToSSU (); + if (m_SentMessages.find (msgID) != m_SentMessages.end()) + { + LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); + return; + } + if (m_SentMessages.empty ()) // schedule resend at first message only + ScheduleResend (); + + auto ret = m_SentMessages.emplace (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ()); + auto& sentMessage = ret.first->second; + if (ret.second) + { + sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; + sentMessage->numResends = 0; + } + auto& fragments = sentMessage->fragments; + size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) + size_t len = msg->GetLength (); + uint8_t * msgBuf = msg->GetSSUHeader (); + + uint32_t fragmentNum = 0; + while (len > 0 && fragmentNum <= 127) + { + auto fragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (); + fragment->fragmentNum = fragmentNum; + uint8_t * payload = fragment->buf + sizeof (SSUHeader); + *payload = DATA_FLAG_WANT_REPLY; // for compatibility + payload++; + *payload = 1; // always 1 message fragment per message + payload++; + htobe32buf (payload, msgID); + payload += 4; + bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max + size_t size = isLast ? len : payloadSize; + uint32_t fragmentInfo = (fragmentNum << 17); + if (isLast) + fragmentInfo |= 0x010000; + + fragmentInfo |= size; + fragmentInfo = htobe32 (fragmentInfo); + memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3); + payload += 3; + memcpy (payload, msgBuf, size); + + size += payload - fragment->buf; + uint8_t rem = size & 0x0F; + if (rem) // make sure 16 bytes boundary + { + auto padding = 16 - rem; + memset (fragment->buf + size, 0, padding); + size += padding; + } + fragment->len = size; + fragments.push_back (fragment); + + // encrypt message with session key + uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf); + try + { + m_Session.Send (buf, size); + } + catch (boost::system::system_error& ec) + { + LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); + } + if (!isLast) + { + len -= payloadSize; + msgBuf += payloadSize; + } + else + len = 0; + fragmentNum++; + } + } + + void SSUData::SendMsgAck (uint32_t msgID) + { + uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16 + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag + payload++; + *payload = 1; // number of ACKs + payload++; + htobe32buf (payload, msgID); // msgID + payload += 4; + *payload = 0; // number of fragments + + // encrypt message with session key + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); + m_Session.Send (buf, 48); + } + + void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits) + { + if (!bits) return; + uint8_t buf[64 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag + payload++; + *payload = 1; // number of ACK bitfields + payload++; + // one ack + *(uint32_t *)(payload) = htobe32 (msgID); // msgID + payload += 4; + size_t len = 0; + while (bits) + { + *payload = (bits & 0x7F); // next 7 bits + bits >>= 7; + if (bits) *payload &= 0x80; // 0x80 means non-last + payload++; len++; + } + *payload = 0; // number of fragments + len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4 + // encrypt message with session key + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); + m_Session.Send (buf, len); + } + + void SSUData::ScheduleResend() + { + m_ResendTimer.cancel (); + m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL)); + auto s = m_Session.shared_from_this(); + m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) + { s->m_Data.HandleResendTimer (ecode); }); + } + + void SSUData::HandleResendTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + int numResent = 0; + for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) + { + if (ts >= it->second->nextResendTime) + { + if (it->second->numResends < MAX_NUM_RESENDS) + { + for (auto& f: it->second->fragments) + if (f) + { + try + { + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf); + m_Session.Send (buf, f->len); // resend + numResent++; + } + catch (boost::system::system_error& ec) + { + LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ()); + } + } + + it->second->numResends++; + it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; + ++it; + } + else + { + LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); + it = m_SentMessages.erase (it); + } + } + else + ++it; + } + if (m_SentMessages.empty ()) return; // nothing to resend + if (numResent < MAX_OUTGOING_WINDOW_SIZE) + ScheduleResend (); + else + { + LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); + m_Session.Close (); + } + } + } + + void SSUData::CleanUp (uint64_t ts) + { + for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) + { + if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) + { + LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); + it = m_IncompleteMessages.erase (it); + } + else + ++it; + } + + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) + // decay + m_ReceivedMessages.clear (); + else + { + // delete old received messages + for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) + { + if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) + it = m_ReceivedMessages.erase (it); + else + ++it; + } + } + } +} +} diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h new file mode 100644 index 00000000..1746dcd8 --- /dev/null +++ b/libi2pd/SSUData.h @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef SSU_DATA_H__ +#define SSU_DATA_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "I2NPProtocol.h" +#include "Identity.h" +#include "RouterInfo.h" + +namespace i2p +{ +namespace transport +{ + + const size_t SSU_MTU_V4 = 1484; + const size_t SSU_MTU_V6 = 1488; + const size_t IPV4_HEADER_SIZE = 20; + const size_t IPV6_HEADER_SIZE = 40; + const size_t UDP_HEADER_SIZE = 8; + const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 + const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 + const int RESEND_INTERVAL = 3; // in seconds + const int MAX_NUM_RESENDS = 5; + const int DECAY_INTERVAL = 20; // in seconds + const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds + const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check + const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store + // data flags + const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; + const uint8_t DATA_FLAG_WANT_REPLY = 0x04; + const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; + const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; + const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; + const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; + + struct Fragment + { + int fragmentNum; + size_t len; + bool isLast; + uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest + + Fragment () = default; + Fragment (int n, const uint8_t * b, int l, bool last): + fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; + }; + + struct FragmentCmp + { + bool operator() (const std::shared_ptr& f1, const std::shared_ptr& f2) const + { + return f1->fragmentNum < f2->fragmentNum; + }; + }; + + struct IncompleteMessage + { + std::shared_ptr msg; + int nextFragmentNum; + uint32_t lastFragmentInsertTime; // in seconds + uint64_t receivedFragmentsBits; + std::set, FragmentCmp> savedFragments; + + IncompleteMessage (std::shared_ptr&& m): msg (m), nextFragmentNum (0), + lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; + void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); + }; + + struct SentMessage + { + std::vector > fragments; + uint32_t nextResendTime; // in seconds + int numResends; + }; + + class SSUSession; + class SSUData + { + public: + + SSUData (SSUSession& session); + ~SSUData (); + + void Start (); + void Stop (); + void CleanUp (uint64_t ts); + + void ProcessMessage (uint8_t * buf, size_t len); + void FlushReceivedMessage (); + void Send (std::shared_ptr msg); + + void AdjustPacketSize (std::shared_ptr remoteRouter); + void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); + + private: + + void SendMsgAck (uint32_t msgID); + void SendFragmentAck (uint32_t msgID, uint64_t bits); + void ProcessAcks (uint8_t *& buf, uint8_t flag); + void ProcessFragments (uint8_t * buf); + void ProcessSentMessageAck (uint32_t msgID); + + void ScheduleResend (); + void HandleResendTimer (const boost::system::error_code& ecode); + + private: + + SSUSession& m_Session; + std::map > m_IncompleteMessages; + std::map > m_SentMessages; + std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds + boost::asio::deadline_timer m_ResendTimer; + int m_MaxPacketSize, m_PacketSize; + i2p::I2NPMessagesHandler m_Handler; + uint32_t m_LastMessageReceivedTime; // in second + }; +} +} + +#endif diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp new file mode 100644 index 00000000..817133e8 --- /dev/null +++ b/libi2pd/SSUSession.cpp @@ -0,0 +1,1319 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "version.h" +#include "Crypto.h" +#include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "Transports.h" +#include "NetDb.hpp" +#include "SSU.h" +#include "SSUSession.h" + +namespace i2p +{ +namespace transport +{ + SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, + std::shared_ptr router, bool peerTest ): + TransportSession (router, SSU_TERMINATION_TIMEOUT), + m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), + m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), + m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) + { + if (router) + { + // we are client + auto address = IsV6 () ? router->GetSSUV6Address () : router->GetSSUAddress (true); + if (address) m_IntroKey = address->i; + m_Data.AdjustPacketSize (router); // mtu + } + else + { + // we are server + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); + if (address) m_IntroKey = address->i; + } + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + } + + SSUSession::~SSUSession () + { + } + + boost::asio::io_service& SSUSession::GetService () + { + return m_Server.GetService (); + } + + void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) + { + uint8_t sharedKey[256]; + m_DHKeysPair->Agree (pubKey, sharedKey); + + uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; + if (sharedKey[0] & 0x80) + { + sessionKey[0] = 0; + memcpy (sessionKey + 1, sharedKey, 31); + memcpy (macKey, sharedKey + 31, 32); + } + else if (sharedKey[0]) + { + memcpy (sessionKey, sharedKey, 32); + memcpy (macKey, sharedKey + 32, 32); + } + else + { + // find first non-zero byte + uint8_t * nonZero = sharedKey + 1; + while (!*nonZero) + { + nonZero++; + if (nonZero - sharedKey > 32) + { + LogPrint (eLogWarning, "SSU: First 32 bytes of shared key is all zeros. Ignored"); + return; + } + } + + memcpy (sessionKey, nonZero, 32); + SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); + } + m_IsSessionKey = true; + m_SessionKeyEncryption.SetKey (m_SessionKey); + m_SessionKeyDecryption.SetKey (m_SessionKey); + } + + void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + m_NumReceivedBytes += len; + i2p::transport::transports.UpdateReceivedBytes (len); + if (m_State == eSessionStateIntroduced) + { + // HolePunch received + LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); + m_State = eSessionStateUnknown; + Connect (); + } + else + { + if (!len) return; // ignore zero-length packets + if (m_State == eSessionStateEstablished) + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + + if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first + DecryptSessionKey (buf, len); + else + { + if (m_State == eSessionStateEstablished) Reset (); // new session key required + // try intro key depending on side + if (Validate (buf, len, m_IntroKey)) + Decrypt (buf, len, m_IntroKey); + else + { + // try own intro key + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); + if (!address) + { + LogPrint (eLogInfo, "SSU: SSU is not supported"); + return; + } + if (Validate (buf, len, address->i)) + Decrypt (buf, len, address->i); + else + { + LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); + m_Server.DeleteSession (shared_from_this ()); + return; + } + } + } + // successfully decrypted + ProcessMessage (buf, len, senderEndpoint); + } + } + + size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const + { + size_t s = sizeof (SSUHeader); + if (((const SSUHeader *)buf)->IsExtendedOptions ()) + s += buf[s] + 1; // byte right after header is extended options length + return s; + } + + void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + len -= (len & 0x0F); // %16, delete extra padding + if (len <= sizeof (SSUHeader)) return; // drop empty message + //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "SSU: SSU header size ", headerSize, " exceeds packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + switch (header->GetPayloadType ()) + { + case PAYLOAD_TYPE_DATA: + ProcessData (buf + headerSize, len - headerSize); + break; + case PAYLOAD_TYPE_SESSION_REQUEST: + ProcessSessionRequest (buf, len); // buf with header + break; + case PAYLOAD_TYPE_SESSION_CREATED: + ProcessSessionCreated (buf, len); // buf with header + break; + case PAYLOAD_TYPE_SESSION_CONFIRMED: + ProcessSessionConfirmed (buf, len); // buf with header + break; + case PAYLOAD_TYPE_PEER_TEST: + LogPrint (eLogDebug, "SSU: Peer test received"); + ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); + break; + case PAYLOAD_TYPE_SESSION_DESTROYED: + { + LogPrint (eLogDebug, "SSU: Session destroy received"); + m_Server.DeleteSession (shared_from_this ()); + break; + } + case PAYLOAD_TYPE_RELAY_RESPONSE: + ProcessRelayResponse (buf + headerSize, len - headerSize); + if (m_State != eSessionStateEstablished) + m_Server.DeleteSession (shared_from_this ()); + break; + case PAYLOAD_TYPE_RELAY_REQUEST: + LogPrint (eLogDebug, "SSU: Relay request received"); + ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); + break; + case PAYLOAD_TYPE_RELAY_INTRO: + LogPrint (eLogDebug, "SSU: Relay intro received"); + ProcessRelayIntro (buf + headerSize, len - headerSize); + break; + default: + LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); + } + } + + void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len) + { + LogPrint (eLogDebug, "SSU message: Session request"); + bool sendRelayTag = true; + auto headerSize = sizeof (SSUHeader); + if (((SSUHeader *)buf)->IsExtendedOptions ()) + { + uint8_t extendedOptionsLen = buf[headerSize]; + headerSize++; + if (extendedOptionsLen >= 2) // options are presented + { + uint16_t flags = bufbe16toh (buf + headerSize); + sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; + } + headerSize += extendedOptionsLen; + } + if (headerSize >= len) + { + LogPrint (eLogError, "SSU message: Session request header size ", headerSize, " exceeds packet length ", len); + return; + } + if (!m_DHKeysPair) + { + auto pair = std::make_shared (); + pair->GenerateKeys (); + m_DHKeysPair = pair; + } + CreateAESandMacKey (buf + headerSize); + SendSessionCreated (buf + headerSize, sendRelayTag); + } + + void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) + { + if (!IsOutgoing () || !m_DHKeysPair) + { + LogPrint (eLogWarning, "SSU: Unsolicited session created message"); + return; + } + + LogPrint (eLogDebug, "SSU message: session created"); + m_ConnectTimer.cancel (); // connect timer + SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "SSU message: Session created header size ", headerSize, " exceeds packet length ", len); + return; + } + uint8_t * payload = buf + headerSize; + uint8_t * y = payload; + CreateAESandMacKey (y); + s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x + s.Insert (y, 256); // y + payload += 256; + boost::asio::ip::address ourIP; + uint16_t ourPort = 0; + auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort); + if (!addressAndPortLen) return; + uint8_t * ourAddressAndPort = payload + 1; + payload += addressAndPortLen; + addressAndPortLen--; // -1 byte address size + s.Insert (ourAddressAndPort, addressAndPortLen); // address + port + if (m_RemoteEndpoint.address ().is_v4 ()) + s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 + else + s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (payload, 8); // relayTag and signed on time + m_RelayTag = bufbe32toh (payload); + payload += 4; // relayTag + uint32_t signedOnTime = bufbe32toh(payload); + payload += 4; // signed on time + // decrypt signature + size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) signatureLen += (16 - paddingSize); + //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); + m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload + // verify signature + if (s.Verify (m_RemoteIdentity, payload)) + { + if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + int offset = (int)ts - signedOnTime; + if (m_Server.IsSyncClockFromPeers ()) + { + if (std::abs (offset) > SSU_CLOCK_THRESHOLD) + { + LogPrint (eLogWarning, "SSU: Clock adjusted by ", -offset, " seconds"); + i2p::util::AdjustTimeOffset (-offset); + } + } + else if (std::abs (offset) > SSU_CLOCK_SKEW) + { + LogPrint (eLogError, "SSU: Clock skew detected ", offset, ". Check your clock"); + i2p::context.SetError (eRouterErrorClockSkew); + } + } + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + if (!i2p::util::net::IsInReservedRange (ourIP)) + { + i2p::context.UpdateAddress (ourIP); + SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen); + } + else + { + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); + Failed (); + } + } + else + { + LogPrint (eLogError, "SSU: Message 'created' signature verification failed"); + Failed (); + } + } + + void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) + { + LogPrint (eLogDebug, "SSU: Session confirmed received"); + m_ConnectTimer.cancel (); + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "SSU: Session confirmed header size ", headerSize, " exceeds packet length ", len); + return; + } + const uint8_t * payload = buf + headerSize; + payload++; // identity fragment info + uint16_t identitySize = bufbe16toh (payload); + if (identitySize + headerSize + 7 > len) // 7 = fragment info + fragment size + signed on time + { + LogPrint (eLogError, "SSU: Session confirmed identity size ", identitySize, " exceeds packet length ", len); + return; + } + payload += 2; // size of identity fragment + auto identity = std::make_shared (payload, identitySize); + auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already + SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); + m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); + payload += identitySize; // identity + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t signedOnTime = bufbe32toh(payload); + if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) + { + LogPrint (eLogError, "SSU: Message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); + Failed (); + return; + } + if (m_SignedData) + m_SignedData->Insert (payload, 4); // insert Alice's signed on time + payload += 4; // signed-on time + size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); + size_t paddingSize = fullSize & 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + payload += paddingSize; + if (fullSize + paddingSize > len) + { + LogPrint (eLogError, "SSU: Session confirmed message is too short ", len); + return; + } + // verify signature + if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) + { + m_Data.Send (CreateDeliveryStatusMsg (0)); + Established (); + } + else + { + LogPrint (eLogError, "SSU: Message 'confirmed' signature verification failed"); + Failed (); + } + } + + void SSUSession::SendSessionRequest () + { + uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t flag = 0; + // fill extended options, 3 bytes extended options don't change message size + bool isV4 = m_RemoteEndpoint.address ().is_v4 (); + if ((isV4 && i2p::context.GetStatus () == eRouterStatusOK) || + (!isV4 && i2p::context.GetStatusV6 () == eRouterStatusOK)) // we don't need relays + { + // tell out peer to now assign relay tag + flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; + *payload = 2; payload++; // 1 byte length + uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG + htobe16buf (payload, flags); + payload += 2; + } + // fill payload + memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x + if (isV4) + { + payload[256] = 4; + memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); + } + else + { + payload[256] = 16; + memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); + } + // encrypt and send + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); + m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); + } + + void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) + { + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); + if (!address) + { + LogPrint (eLogInfo, "SSU: SSU is not supported"); + return; + } + + uint8_t buf[96 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + htobe32buf (payload, introducer.iTag); + payload += 4; + *payload = 0; // no address + payload++; + htobuf16(payload, 0); // port = 0 + payload += 2; + *payload = 0; // challenge + payload++; + memcpy (payload, (const uint8_t *)address->i, 32); + payload += 32; + htobe32buf (payload, nonce); // nonce + + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + if (m_State == eSessionStateEstablished) + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); + else + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); + m_Server.Send (buf, 96, m_RemoteEndpoint); + LogPrint (eLogDebug, "SSU: Relay request sent"); + } + + void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) + { + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only + if (!address) + { + LogPrint (eLogInfo, "SSU: SSU is not supported"); + return; + } + SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time + s.Insert (x, 256); // x + + uint8_t buf[384 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); + s.Insert (payload, 256); // y + payload += 256; + if (m_RemoteEndpoint.address ().is_v4 ()) + { + // ipv4 + *payload = 4; + payload++; + memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); + s.Insert (payload, 4); // remote endpoint IP V4 + payload += 4; + } + else + { + // ipv6 + *payload = 16; + payload++; + memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); + s.Insert (payload, 16); // remote endpoint IP V6 + payload += 16; + } + htobe16buf (payload, m_RemoteEndpoint.port ()); + s.Insert (payload, 2); // remote port + payload += 2; + if (address->host.is_v4 ()) + s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 + else + s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 + s.Insert (htobe16 (address->port)); // our port + if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer (!IsV6 ())) + { + RAND_bytes((uint8_t *)&m_SentRelayTag, 4); + if (!m_SentRelayTag) m_SentRelayTag = 1; + } + htobe32buf (payload, m_SentRelayTag); + payload += 4; // relay tag + htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time + payload += 4; + s.Insert (payload - 8, 4); // relayTag + // we have to store this signed data for session confirmed + // same data but signed on time, it will Alice's there + m_SignedData = std::unique_ptr(new SignedData (s)); + s.Insert (payload - 4, 4); // BOB's signed on time + s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature + + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + // encrypt signature and padding with newly created session key + size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) + { + // fill random padding + RAND_bytes(payload + signatureLen, (16 - paddingSize)); + signatureLen += (16 - paddingSize); + } + m_SessionKeyEncryption.SetIV (iv); + m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); + payload += signatureLen; + size_t msgLen = payload - buf; + + // encrypt message with intro key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); + Send (buf, msgLen); + } + + void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) + { + uint8_t buf[512 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 1; // 1 fragment + payload++; // info + size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes + htobe16buf (payload, identLen); + payload += 2; // cursize + i2p::context.GetIdentity ()->ToBuffer (payload, identLen); + payload += identLen; + uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); + htobe32buf (payload, signedOnTime); // signed on time + payload += 4; + auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); + size_t paddingSize = ((payload - buf) + signatureLen)%16; + if (paddingSize > 0) paddingSize = 16 - paddingSize; + RAND_bytes(payload, paddingSize); // fill padding with random + payload += paddingSize; // padding size + // signature + SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time + s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x + s.Insert (y, 256); // y + s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party + if (m_RemoteEndpoint.address ().is_v4 ()) + s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 + else + s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (htobe32 (m_RelayTag)); // relay tag + s.Insert (htobe32 (signedOnTime)); // signed on time + s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature + payload += signatureLen; + + size_t msgLen = payload - buf; + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); + Send (buf, msgLen); + } + + void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) + { + uint32_t relayTag = bufbe32toh (buf); + auto session = m_Server.FindRelaySession (relayTag); + if (session) + { + buf += 4; // relay tag + uint8_t size = *buf; + buf++; // size + buf += size; // address + buf += 2; // port + uint8_t challengeSize = *buf; + buf++; // challenge size + buf += challengeSize; + const uint8_t * introKey = buf; + buf += 32; // introkey + uint32_t nonce = bufbe32toh (buf); + SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); + SendRelayIntro (session, from); + } + } + + void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, + const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) + { + bool isV4 = to.address ().is_v4 (); // Charle's + bool isV4A = from.address ().is_v4 (); // Alice's + if ((isV4 && !isV4A) || (!isV4 && isV4A)) + { + LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay response"); + return; + } + uint8_t buf[80 + 18] = {0}; // 64 for ipv4 and 80 for ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + // Charlie + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4 + payload += 4; // address + } + else + { + *payload = 16; + payload++; // size + memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Charlie's IP V6 + payload += 16; // address + } + htobe16buf (payload, to.port ()); // Charlie's port + payload += 2; // port + // Alice + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 + payload += 4; // address + } + else + { + *payload = 16; + payload++; // size + memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 + payload += 16; // address + } + htobe16buf (payload, from.port ()); // Alice's port + payload += 2; // port + htobe32buf (payload, nonce); + + if (m_State == eSessionStateEstablished) + { + // encrypt with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); + Send (buf, isV4 ? 64 : 80); + } + else + { + // ecrypt with Alice's intro key + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); + m_Server.Send (buf, isV4 ? 64 : 80, from); + } + LogPrint (eLogDebug, "SSU: Relay response sent"); + } + + void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) + { + if (!session) return; + bool isV4 = from.address ().is_v4 (); // Alice's + bool isV4C = session->m_RemoteEndpoint.address ().is_v4 (); // Charlie's + if ((isV4 && !isV4C) || (!isV4 && isV4C)) + { + LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay intro"); + return; + } + uint8_t buf[64 + 18] = {0}; // 48 for ipv4 and 64 for ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + if (isV4) + { + *payload = 4; + payload++; // size + memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 + payload += 4; // address + } + else + { + *payload = 16; + payload++; // size + memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 + payload += 16; // address + } + htobe16buf (payload, from.port ()); // Alice's port + payload += 2; // port + *payload = 0; // challenge size + uint8_t iv[16]; + RAND_bytes (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey); + m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint); + LogPrint (eLogDebug, "SSU: Relay intro sent"); + } + + void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) + { + LogPrint (eLogDebug, "SSU message: Relay response received"); + boost::asio::ip::address remoteIP; + uint16_t remotePort = 0; + auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort); + if (!remoteSize) return; + buf += remoteSize; len -= remoteSize; + boost::asio::ip::address ourIP; + uint16_t ourPort = 0; + auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort); + if (!ourSize) return; + buf += ourSize; len -= ourSize; + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + if (!i2p::util::net::IsInReservedRange (ourIP)) + i2p::context.UpdateAddress (ourIP); + else + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); + if (ourIP.is_v4 ()) + { + if (ourPort != m_Server.GetPort ()) + { + if (i2p::context.GetStatus () == eRouterStatusTesting) + i2p::context.SetError (eRouterErrorSymmetricNAT); + } + else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) + i2p::context.SetStatus (eRouterStatusTesting); + } + uint32_t nonce = bufbe32toh (buf); + buf += 4; // nonce + auto it = m_RelayRequests.find (nonce); + if (it != m_RelayRequests.end ()) + { + // check if we are waiting for introduction + boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); + if (!m_Server.FindSession (remoteEndpoint)) + { + // we didn't have correct endpoint when sent relay request + // now we do + LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); + if ((remoteIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || + (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) + m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch + // we assume that HolePunch has been sent by this time and our SessionRequest will go through + m_Server.CreateDirectSession (it->second.first, remoteEndpoint, false); + } + // delete request + m_RelayRequests.erase (it); + // cancel connect timer + m_ConnectTimer.cancel (); + } + else + LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); + } + + void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) + { + boost::asio::ip::address ip; + uint16_t port = 0; + ExtractIPAddressAndPort (buf, len, ip, port); + if (!ip.is_unspecified () && port) + // send hole punch of 0 bytes + m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port)); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, + const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + memcpy (header->iv, iv, 16); + header->flag = flag | (payloadType << 4); // MSB is 0 + htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + i2p::crypto::CBCEncryption encryption; + encryption.SetKey (aesKey); + encryption.SetIV (iv); + encryption.Encrypt (encrypted, encryptedLen, encrypted); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, iv, 16); + uint16_t netid = i2p::context.GetNetID (); + htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) + { + FillHeaderAndEncrypt (payloadType, buf, len, buf); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)out; + RAND_bytes (header->iv, 16); // random iv + m_SessionKeyEncryption.SetIV (header->iv); + SSUHeader * inHeader = (SSUHeader *)in; + inHeader->flag = payloadType << 4; // MSB is 0 + htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; + uint16_t encryptedLen = len - (encrypted - out); + m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); + // assume actual out buffer size is 18 (16 + 2) bytes more + memcpy (out + len, header->iv, 16); + uint16_t netid = i2p::context.GetNetID (); + htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); + } + + void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (aesKey); + decryption.SetIV (header->iv); + decryption.Decrypt (encrypted, encryptedLen, encrypted); + } + + void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + if (encryptedLen > 0) + { + m_SessionKeyDecryption.SetIV (header->iv); + m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); + } + } + + bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return false; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, header->iv, 16); + uint16_t netid = i2p::context.GetNetID (); + htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); + uint8_t digest[16]; + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); + return !memcmp (header->mac, digest, 16); + } + + void SSUSession::Connect () + { + if (m_State == eSessionStateUnknown) + { + ScheduleConnectTimer (); // set connect timer + m_DHKeysPair = std::make_shared (); + m_DHKeysPair->GenerateKeys (); + SendSessionRequest (); + } + } + + void SSUSession::WaitForConnect () + { + if (!IsOutgoing ()) // incoming session + ScheduleConnectTimer (); + else + LogPrint (eLogError, "SSU: Wait for connect for outgoing session"); + } + + void SSUSession::ScheduleConnectTimer () + { + m_ConnectTimer.cancel (); + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + shared_from_this (), std::placeholders::_1)); +} + + void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) + { + if (!ecode) + { + // timeout expired + LogPrint (eLogWarning, "SSU: Session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); + Failed (); + } + } + + void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, + std::shared_ptr to) + { + if (m_State == eSessionStateUnknown) + { + // set connect timer + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + shared_from_this (), std::placeholders::_1)); + } + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + auto ts = i2p::util::GetSecondsSinceEpoch (); + m_RelayRequests.emplace (nonce, std::make_pair (to, ts)); + SendRelayRequest (introducer, nonce); + } + + void SSUSession::WaitForIntroduction () + { + m_State = eSessionStateIntroduced; + // set connect timer + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + shared_from_this (), std::placeholders::_1)); + } + + void SSUSession::Close () + { + SendSessionDestroyed (); + Reset (); + m_State = eSessionStateClosed; + } + + void SSUSession::Reset () + { + m_State = eSessionStateUnknown; + transports.PeerDisconnected (shared_from_this ()); + m_Data.Stop (); + m_ConnectTimer.cancel (); + if (m_SentRelayTag) + { + m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore + m_SentRelayTag = 0; + } + m_DHKeysPair = nullptr; + m_SignedData = nullptr; + m_IsSessionKey = false; + } + + void SSUSession::Done () + { + GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); + } + + void SSUSession::Established () + { + m_State = eSessionStateEstablished; + m_DHKeysPair = nullptr; + m_SignedData = nullptr; + m_Data.Start (); + transports.PeerConnected (shared_from_this ()); + if (m_IsPeerTest) + SendPeerTest (); + if (m_SentRelayTag) + m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + } + + void SSUSession::Failed () + { + if (m_State != eSessionStateFailed) + { + m_State = eSessionStateFailed; + m_Server.DeleteSession (shared_from_this ()); + } + } + + void SSUSession::SendI2NPMessages (const std::vector >& msgs) + { + GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); + } + + void SSUSession::PostI2NPMessages (std::vector > msgs) + { + if (m_State == eSessionStateEstablished) + { + for (const auto& it: msgs) + if (it) + { + if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) + m_Data.Send (it); + else + LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); + } + } + } + + void SSUSession::ProcessData (uint8_t * buf, size_t len) + { + m_Data.ProcessMessage (buf, len); + m_IsDataReceived = true; + } + + void SSUSession::FlushData () + { + if (m_IsDataReceived) + { + m_Data.FlushReceivedMessage (); + m_IsDataReceived = false; + } + } + + void SSUSession::CleanUp (uint64_t ts) + { + m_Data.CleanUp (ts); + for (auto it = m_RelayRequests.begin (); it != m_RelayRequests.end ();) + { + if (ts > it->second.second + SSU_CONNECT_TIMEOUT) + it = m_RelayRequests.erase (it); + else + ++it; + } + } + + void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + uint32_t nonce = bufbe32toh (buf); // 4 bytes + boost::asio::ip::address addr; // Alice's address + uint16_t port = 0; // and port + auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port); + if (port && (size != 7) && (size != 19)) + { + LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported"); + return; + } + const uint8_t * introKey = buf + 4 + size; + switch (m_Server.GetPeerTestParticipant (nonce)) + { + // existing test + case ePeerTestParticipantAlice1: + { + if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob + { + LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); + if (IsV6 ()) + { + if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + { + i2p::context.SetStatusV6 (eRouterStatusFirewalled); + m_Server.RescheduleIntroducersUpdateTimerV6 (); + } + } + else if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK + { + i2p::context.SetStatus (eRouterStatusFirewalled); + m_Server.RescheduleIntroducersUpdateTimer (); + } + } + else + { + LogPrint (eLogDebug, "SSU: First peer test from Charlie. We are Alice"); + if (m_State == eSessionStateEstablished) + LogPrint (eLogWarning, "SSU: First peer test from Charlie through established session. We are Alice"); + if (IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); + else + i2p::context.SetStatus (eRouterStatusOK); + m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); + SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie + } + break; + } + case ePeerTestParticipantAlice2: + { + if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob + LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); + else + { + // peer test successive + LogPrint (eLogDebug, "SSU: Second peer test from Charlie. We are Alice"); + if (IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); + else + i2p::context.SetStatus (eRouterStatusOK); + m_Server.RemovePeerTest (nonce); + } + break; + } + case ePeerTestParticipantBob: + { + LogPrint (eLogDebug, "SSU: Peer test from Charlie. We are Bob"); + auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest + if (session && session->m_State == eSessionStateEstablished) + { + const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob + session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice + } + m_Server.RemovePeerTest (nonce); // nonce has been used + break; + } + case ePeerTestParticipantCharlie: + { + LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Charlie"); + SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address + m_Server.RemovePeerTest (nonce); // nonce has been used + break; + } + // test not found + case ePeerTestParticipantUnknown: + { + if (m_State == eSessionStateEstablished) + { + // new test + if (port) + { + LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Charlie"); + Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob + if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) + { + m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); + SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob + } + } + else + { + LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Bob"); + auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie + if (session) + { + m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); + session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address + } + } + } + else + LogPrint (eLogError, "SSU: Unexpected peer test"); + } + } + } + + void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, + const uint8_t * introKey, bool toAddress, bool sendAddress) + // toAddress is true for Alice<->Chalie communications only + // sendAddress is false if message comes from Alice + { + uint8_t buf[80 + 18] = {0}; + uint8_t iv[16]; + uint8_t * payload = buf + sizeof (SSUHeader); + htobe32buf (payload, nonce); + payload += 4; // nonce + // address and port + if (sendAddress) + { + if (address.is_v4 ()) + { + *payload = 4; + memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 + } + else if (address.is_v6 ()) + { + *payload = 16; + memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 + } + else + *payload = 0; + payload += (payload[0] + 1); + } + else + { + *payload = 0; + payload++; //size + } + htobe16buf (payload, port); + payload += 2; // port + // intro key + if (toAddress) + { + // send our intro key to address instead of its own + auto addr = address.is_v4 () ? i2p::context.GetRouterInfo ().GetSSUAddress (true) : // ipv4 + i2p::context.GetRouterInfo ().GetSSUV6Address (); + if (addr) + memcpy (payload, addr->i, 32); // intro key + else + LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); + } + else + memcpy (payload, introKey, 32); // intro key + + // send + RAND_bytes (iv, 16); // random iv + if (toAddress) + { + // encrypt message with specified intro key + FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); + boost::asio::ip::udp::endpoint e (address, port); + m_Server.Send (buf, 80, e); + } + else + { + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); + Send (buf, 80); + } + } + + void SSUSession::SendPeerTest () + { + // we are Alice + LogPrint (eLogDebug, "SSU: Sending peer test"); + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); + if (!address) + { + LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); + return; + } + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + if (!nonce) nonce = 1; + m_IsPeerTest = false; + m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); + SendPeerTest (nonce, boost::asio::ip::address(), 0, address->i, false, false); // address and port always zero for Alice + } + + void SSUSession::SendKeepAlive () + { + if (m_State == eSessionStateEstablished) + { + uint8_t buf[48 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 0; // flags + payload++; + *payload = 0; // num fragments + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); + Send (buf, 48); + LogPrint (eLogDebug, "SSU: keep-alive sent"); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + } + } + + void SSUSession::SendSessionDestroyed () + { + if (m_IsSessionKey) + { + uint8_t buf[48 + 18] = {0}; + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); + try + { + Send (buf, 48); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "SSU: Exception while sending session destoroyed: ", ex.what ()); + } + LogPrint (eLogDebug, "SSU: Session destroyed sent"); + } + } + + void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) + { + uint8_t buf[SSU_MTU_V4 + 18] = {0}; + size_t msgSize = len + sizeof (SSUHeader); + size_t paddingSize = msgSize & 0x0F; // %16 + if (paddingSize > 0) msgSize += (16 - paddingSize); + if (msgSize > SSU_MTU_V4) + { + LogPrint (eLogWarning, "SSU: Payload size ", msgSize, " exceeds MTU"); + return; + } + memcpy (buf + sizeof (SSUHeader), payload, len); + // encrypt message with session key + FillHeaderAndEncrypt (type, buf, msgSize); + Send (buf, msgSize); + } + + void SSUSession::Send (const uint8_t * buf, size_t size) + { + m_NumSentBytes += size; + i2p::transport::transports.UpdateSentBytes (size); + m_Server.Send (buf, size, m_RemoteEndpoint); + } + + size_t SSUSession::ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port) + { + if (!len) return 0; + uint8_t size = *buf; + size_t s = 1 + size + 2; // size + address + port + if (len < s) + { + LogPrint (eLogWarning, "SSU: Address is too short ", len); + port = 0; + return len; + } + buf++; // size + if (size == 4) + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), buf, 4); + ip = boost::asio::ip::address_v4 (bytes); + } + else if (size == 16) + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), buf, 16); + ip = boost::asio::ip::address_v6 (bytes); + } + else + LogPrint (eLogWarning, "SSU: Address size ", int(size), " is not supported"); + buf += size; + port = bufbe16toh (buf); + return s; + } +} +} diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h new file mode 100644 index 00000000..535de328 --- /dev/null +++ b/libi2pd/SSUSession.h @@ -0,0 +1,180 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef SSU_SESSION_H__ +#define SSU_SESSION_H__ + +#include +#include +#include +#include "Crypto.h" +#include "I2NPProtocol.h" +#include "TransportSession.h" +#include "SSUData.h" + +namespace i2p +{ +namespace transport +{ + const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; + struct SSUHeader + { + uint8_t mac[16]; + uint8_t iv[16]; + uint8_t flag; + uint8_t time[4]; + + uint8_t GetPayloadType () const { return flag >> 4; }; + bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; + }; + + const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds + const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes + const int SSU_CLOCK_SKEW = 60; // in seconds + const int SSU_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust + const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; + + // payload types (4 bits) + const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; + const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; + const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; + const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; + const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; + const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; + const uint8_t PAYLOAD_TYPE_DATA = 6; + const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; + const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; + + // extended options + const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; + + enum SessionState + { + eSessionStateUnknown, + eSessionStateIntroduced, + eSessionStateEstablished, + eSessionStateClosed, + eSessionStateFailed + }; + + enum PeerTestParticipant + { + ePeerTestParticipantUnknown = 0, + ePeerTestParticipantAlice1, + ePeerTestParticipantAlice2, + ePeerTestParticipantBob, + ePeerTestParticipantCharlie + }; + + class SSUServer; + class SSUSession: public TransportSession, public std::enable_shared_from_this + { + public: + + SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, + std::shared_ptr router = nullptr, bool peerTest = false); + void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + ~SSUSession (); + + void Connect (); + void WaitForConnect (); + void Introduce (const i2p::data::RouterInfo::Introducer& introducer, + std::shared_ptr to); // Alice to Charlie + void WaitForIntroduction (); + void Close (); + void Done (); + void Failed (); + const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; + SSUServer& GetServer () { return m_Server; }; + + bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; + void SendI2NPMessages (const std::vector >& msgs); + void SendPeerTest (); // Alice + + SessionState GetState () const { return m_State; }; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + + void SendKeepAlive (); + uint32_t GetRelayTag () const { return m_RelayTag; }; + const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; + uint32_t GetCreationTime () const { return m_CreationTime; }; + void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers + + void FlushData (); + void CleanUp (uint64_t ts); + + private: + + boost::asio::io_service& GetService (); + void CreateAESandMacKey (const uint8_t * pubKey); + size_t GetSSUHeaderSize (const uint8_t * buf) const; + void PostI2NPMessages (std::vector > msgs); + void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session + void ProcessSessionRequest (const uint8_t * buf, size_t len); + void SendSessionRequest (); + void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); + void ProcessSessionCreated (uint8_t * buf, size_t len); + void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); + void ProcessSessionConfirmed (const uint8_t * buf, size_t len); + void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); + void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, + const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); + void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); + void ProcessRelayResponse (const uint8_t * buf, size_t len); + void ProcessRelayIntro (const uint8_t * buf, size_t len); + void Established (); + void ScheduleConnectTimer (); + void HandleConnectTimer (const boost::system::error_code& ecode); + void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); + void ProcessData (uint8_t * buf, size_t len); + void SendSessionDestroyed (); + void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key + void Send (const uint8_t * buf, size_t size); + + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, + const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key + void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); + void DecryptSessionKey (uint8_t * buf, size_t len); + bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); + + void Reset (); + + static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size + + private: + + friend class SSUData; // TODO: change in later + SSUServer& m_Server; + const boost::asio::ip::udp::endpoint m_RemoteEndpoint; + boost::asio::deadline_timer m_ConnectTimer; + bool m_IsPeerTest; + SessionState m_State; + bool m_IsSessionKey; + uint32_t m_RelayTag; // received from peer + uint32_t m_SentRelayTag; // sent by us + i2p::crypto::CBCEncryption m_SessionKeyEncryption; + i2p::crypto::CBCDecryption m_SessionKeyDecryption; + i2p::crypto::AESKey m_SessionKey; + i2p::crypto::MACKey m_MacKey; + i2p::data::RouterInfo::IntroKey m_IntroKey; + uint32_t m_CreationTime; // seconds since epoch + SSUData m_Data; + bool m_IsDataReceived; + std::unique_ptr m_SignedData; // we need it for SessionConfirmed only + std::map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) + std::shared_ptr m_DHKeysPair; // X - for client and Y - for server + }; +} +} + +#endif diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index cad7d484..ebc188a9 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,11 +7,6 @@ */ #include -#include -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#include -#endif #include "Log.h" #include "Signature.h" @@ -19,320 +14,59 @@ namespace i2p { namespace crypto { -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - DSAVerifier::DSAVerifier (): - m_PublicKey (nullptr) +#if OPENSSL_EDDSA + EDDSA25519Verifier::EDDSA25519Verifier () { + m_MDCtx = EVP_MD_CTX_create (); } - DSAVerifier::~DSAVerifier () + EDDSA25519Verifier::~EDDSA25519Verifier () { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); + EVP_MD_CTX_destroy (m_MDCtx); } - void DSAVerifier::SetPublicKey (const uint8_t * signingKey) + void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); - BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); - m_PublicKey = CreateDSA (pub); - BN_free (pub); - } - - bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // 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 * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); + EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey); EVP_PKEY_free (pkey); - EVP_PKEY_free (paramskey); - EVP_PKEY_CTX_free (ctx); - } -#else - - DSAVerifier::DSAVerifier () - { - m_PublicKey = CreateDSA (); } - DSAVerifier::~DSAVerifier () + bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - DSA_free (m_PublicKey); + return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); } - 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) +#else + EDDSA25519Verifier::EDDSA25519Verifier () { } EDDSA25519Verifier::~EDDSA25519Verifier () { - EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - if (m_Pkey) EVP_PKEY_free (m_Pkey); - m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); + 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); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - 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; + 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); } +#endif EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { @@ -363,219 +97,44 @@ 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_Pkey (nullptr), m_Fallback (nullptr) + m_MDCtx (nullptr), m_Fallback (nullptr) { - m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); + EVP_PKEY * 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 (m_Pkey, publicKey, &len); + EVP_PKEY_get_raw_public_key (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; - if (m_Pkey) EVP_PKEY_free (m_Pkey); + EVP_MD_CTX_destroy (m_MDCtx); } 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_Pkey) + if (m_Fallback) return m_Fallback->Sign (buf, len, signature); + else { - - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); size_t l = 64; uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 - EVP_DigestSignInit (ctx, NULL, NULL, NULL, m_Pkey); - if (!EVP_DigestSign (ctx, sig, &l, buf, len)) - LogPrint (eLogError, "EdDSA signing failed"); + EVP_DigestSign (m_MDCtx, sig, &l, buf, len); memcpy (signature, sig, 64); - EVP_MD_CTX_destroy (ctx); } - else - LogPrint (eLogError, "EdDSA signing key is not set"); } - -#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 +#endif } } diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 43f706bd..e153e66d 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -43,164 +43,94 @@ 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 (); - // 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; }; - + 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; }; + 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 - ~DSASigner (); + { + m_PrivateKey = CreateDSA (); + DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); + } - // implements Signer - void Sign (const uint8_t * buf, int len, uint8_t * signature) const override; + ~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); + } private: -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - EVP_PKEY * m_PrivateKey; -#else DSA * m_PrivateKey; -#endif }; - 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 + inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - 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); + 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); } -// 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) @@ -231,6 +161,7 @@ namespace crypto enum { hashLen = 64 }; }; + // EcDSA template class ECDSAVerifier: public Verifier { @@ -263,7 +194,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) == 1; + int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); ECDSA_SIG_free(sig); return ret; } @@ -326,6 +257,7 @@ namespace crypto } // ECDSA_SHA256_P256 + const size_t ECDSAP256_KEY_LENGTH = 64; typedef ECDSAVerifier ECDSAP256Verifier; typedef ECDSASigner ECDSAP256Signer; @@ -335,6 +267,7 @@ namespace crypto } // ECDSA_SHA384_P384 + const size_t ECDSAP384_KEY_LENGTH = 96; typedef ECDSAVerifier ECDSAP384Verifier; typedef ECDSASigner ECDSAP384Signer; @@ -344,6 +277,7 @@ namespace crypto } // ECDSA_SHA512_P521 + const size_t ECDSAP521_KEY_LENGTH = 132; typedef ECDSAVerifier ECDSAP521Verifier; typedef ECDSASigner ECDSAP521Signer; @@ -351,8 +285,7 @@ namespace crypto { CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } - -#endif + // EdDSA class EDDSA25519Verifier: public Verifier @@ -369,23 +302,15 @@ namespace crypto size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: - - EVP_PKEY * m_Pkey; - - protected: - EVP_PKEY * GetPkey () const { return m_Pkey; }; +#if OPENSSL_EDDSA + EVP_MD_CTX * m_MDCtx; +#else + EDDSAPoint m_PublicKey; + uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; +#endif }; -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - class EDDSA25519phVerifier: public EDDSA25519Verifier - { - public: - - bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; - }; -#endif - class EDDSA25519SignerCompat: public Signer { public: @@ -403,6 +328,7 @@ namespace crypto uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; +#if OPENSSL_EDDSA class EDDSA25519Signer: public Signer { public: @@ -413,30 +339,20 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; - protected: - - EVP_PKEY * GetPkey () const { return m_Pkey; }; - private: - EVP_PKEY * m_Pkey; + EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; +#else -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - class EDDSA25519phSigner: public EDDSA25519Signer - { - public: + typedef EDDSA25519SignerCompat EDDSA25519Signer; + +#endif - 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); @@ -447,6 +363,11 @@ 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 } @@ -609,57 +530,6 @@ 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 deleted file mode 100644 index 8db8939b..00000000 --- a/libi2pd/Socks5.h +++ /dev/null @@ -1,210 +0,0 @@ -/* -* 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 25fb7b5d..af0a359f 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,55 +19,44 @@ namespace i2p { namespace stream { - void SendBufferQueue::Add (std::shared_ptr&& buf) + 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) { 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; - if (len >= m_Size) + while (!m_Buffers.empty () && offset < len) { - for (auto& it: m_Buffers) + auto nextBuffer = m_Buffers.front (); + auto rem = nextBuffer->GetRemainingSize (); + if (offset + rem <= len) { - auto rem = it->GetRemainingSize (); - memcpy (buf + offset, it->GetRemaningBuffer (), rem); + // whole buffer + memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); offset += rem; + m_Buffers.pop_front (); // delete it } - m_Buffers.clear (); - m_Size = 0; - return offset; - } - else - { - while (!m_Buffers.empty () && offset < len) + else { - 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 - } + // partially + rem = len - offset; + memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); + nextBuffer->offset += (len - offset); + offset = len; // break } - m_Size -= offset; - } + } + m_Size -= offset; return offset; } @@ -75,76 +64,36 @@ 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_context& service, StreamingDestination& local, + Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), - m_SendStreamID (0), m_SequenceNumber (0), m_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) + 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) { 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_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) + 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) { 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 () @@ -159,32 +108,23 @@ 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 + { { - int numPackets = m_SentPackets.size (); - int numSentPackets = m_RoutingSession->NumSentPackets (); - numSentPackets -= numPackets; - if (numSentPackets < 0) numSentPackets = 0; - m_RoutingSession->SetNumSentPackets (numSentPackets); + std::unique_lock l(m_SendBufferMutex); + m_SendBuffer.CleanUp (); } - - m_SendBuffer.CleanUp (); while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } - - m_NACKedPackets.clear (); for (auto it: m_SentPackets) m_LocalDestination.DeletePacket (it); @@ -197,51 +137,19 @@ 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 (); - if (!receivedSeqn && m_LastReceivedSequenceNumber >= 0) + bool isSyn = packet->IsSYN (); + if (!receivedSeqn && !isSyn) { - 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; - } - } + // plain ack + LogPrint (eLogDebug, "Streaming: Plain ACK received"); m_LocalDestination.DeletePacket (packet); return; } @@ -251,8 +159,7 @@ 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 ();) { @@ -262,7 +169,6 @@ namespace stream m_SavedPackets.erase (it++); ProcessPacket (savedPacket); - if (m_Status == eStreamStatusTerminated) return; } else break; @@ -273,12 +179,16 @@ namespace stream { if (!m_IsAckSendScheduled) { + m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; - ScheduleAck (ackTimeout); + 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)); } } - else if (packet->IsSYN ()) + else if (isSyn) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } @@ -287,75 +197,33 @@ namespace stream if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate - 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; + LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); + SendQuickAck (); // resend ack for previous message again 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 (eLogInfo, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); - if ((receivedSeqn - m_LastReceivedSequenceNumber) >= m_MaxWindowSize*3) - { - 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) - { - auto seqn = it->GetSeqn (); - if ((int)seqn > lastSavedSeq) lastSavedSeq = seqn; - for (uint32_t i = nextSeqn; i < seqn; i++) numNacks++; - nextSeqn = seqn + 1; - } - - 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); + 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) { - if (!m_IsAckSendScheduled) - { - // send NACKs for missing messages - SendQuickAck (); - auto ackTimeout = m_RTT/10; - if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; - ScheduleAck (ackTimeout); - } - } + // send NACKs for missing messages ASAP + if (m_IsAckSendScheduled) + { + m_IsAckSendScheduled = false; + m_AckSendTimer.cancel (); + } + SendQuickAck (); + } else + { // wait for SYN - ScheduleAck (SYN_TIMEOUT); + 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)); + } } } } @@ -409,62 +277,24 @@ 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) { - uint16_t delayRequested = bufbe16toh (optionData); - if (!delayRequested) // 0 requests an immediate ack - immediateAckRequested = true; - else if (!m_IsAckSendScheduled) + if (!m_IsAckSendScheduled) { - if (delayRequested < m_RTT) + uint16_t delayRequested = bufbe16toh (optionData); + if (delayRequested > 0 && 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); @@ -475,26 +305,7 @@ namespace stream } optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) - { - 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 - } + LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) @@ -504,30 +315,6 @@ 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) @@ -535,95 +322,55 @@ namespace stream LogPrint (eLogInfo, "Streaming: offline signature without identity"); return false; } - if (verified) + // 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) { - // 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 + // skip option data + optionData += 6; // timestamp and key type + optionData += m_TransientVerifier->GetPublicKeyLen (); // public key optionData += m_RemoteIdentity->GetSignatureLen (); // signature } else - { - // 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) + { + // transient key + size_t offset = 0; + m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); + optionData += offset; + if (!m_TransientVerifier) { - // skip option data - optionData += 6; // timestamp and key type - optionData += m_TransientVerifier->GetPublicKeyLen (); // public key - optionData += m_RemoteIdentity->GetSignatureLen (); // signature + LogPrint (eLogError, "Streaming: offline signature failed"); + return false; } - 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 > packet->GetLength ()) + 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 { 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; } @@ -637,7 +384,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 - auto payloadLen = int(packet->len) - (packet->GetPayload () - packet->buf); + ssize_t payloadLen = packet->len - (packet->GetPayload () - packet->buf); if (payloadLen > 0) memcpy (p.buf + 22, packet->GetPayload (), payloadLen); else @@ -654,17 +401,11 @@ 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 ();) { @@ -677,8 +418,6 @@ namespace stream for (int i = 0; i < nackCount; i++) if (seqn == packet->GetNACK (i)) { - m_NACKedPackets.insert (*it); - m_IsNAcked = true; nacked = true; break; } @@ -690,150 +429,43 @@ namespace stream } } auto sentPacket = *it; - 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) + uint64_t rtt = ts - sentPacket->sendTime; + if(ts < sentPacket->sendTime) { - m_IsFirstRttSample = true; - rttSample = rtt < 0 ? 1 : rtt; + LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); + rtt = 1; } - else if (!sentPacket->resent && seqn > m_TunnelsChangeSequenceNumber && rtt >= 0) - rttSample = std::min (rttSample, (int)rtt); + m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); + m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; - ackPacketsCounter++; - if (m_WindowIncCounter < m_MaxWindowSize && !m_IsFirstACK && !m_IsWinDropped) - incCounter++; + 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})); } else break; } - if (m_LastACKRecieveTime) - { - uint64_t interval = ts - m_LastACKRecieveTime; - if (m_ACKRecieveInterval) - m_ACKRecieveInterval = (m_ACKRecieveInterval + interval) / 2; - else - m_ACKRecieveInterval = interval; - } - m_LastACKRecieveTime = ts; - if (rttSample != INT_MAX) - { - if (m_IsFirstRttSample && !m_IsFirstACK) - { - 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 ()) - { + if (m_SentPackets.empty ()) 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) @@ -842,48 +474,6 @@ namespace stream Close (); // check is all outgoing messages have been sent and we can send close } - size_t Stream::Receive (uint8_t * buf, size_t len, int timeout) - { - if (!len) return 0; - size_t ret = 0; - volatile bool done = false; - std::condition_variable newDataReceived; - std::mutex newDataReceivedMutex; - AsyncReceive (boost::asio::buffer (buf, len), - [&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode == boost::asio::error::timed_out) - ret = 0; - else - ret = bytes_transferred; - std::unique_lock l(newDataReceivedMutex); - newDataReceived.notify_all (); - done = true; - }, - timeout); - if (!done) - { std::unique_lock l(newDataReceivedMutex); - if (!done && newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) - ret = 0; - } - if (!done) - { - // make sure that AsycReceive complete - auto s = shared_from_this(); - boost::asio::post (m_Service, [s]() - { - s->m_ReceiveTimer.cancel (); - }); - int i = 0; - while (!done && i < 100) // 1 sec - { - std::this_thread::sleep_for (std::chrono::milliseconds(10)); - i++; - } - } - return ret; - } - size_t Stream::Send (const uint8_t * buf, size_t len) { AsyncSend (buf, len, nullptr); @@ -892,121 +482,71 @@ namespace stream void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) { - std::shared_ptr buffer; if (len > 0 && buf) - buffer = std::make_shared(buf, len, handler); + { + std::unique_lock l(m_SendBufferMutex); + m_SendBuffer.Add (buf, len, handler); + } else if (handler) handler(boost::system::error_code ()); - 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 (); - }); + m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } 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 || !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; - } + if (numMsgs <= 0) return; // window is full + bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; - 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) + std::unique_lock l(m_SendBufferMutex); + while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) { - // first SYN packet - if (m_DontSign) - { - // remote ident is useless without signature, don't include it - packet[size] = 0; size++; // NACK count - } + 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 - { - packet[size] = 8; - size++; // NACK count - memcpy (packet + size, m_RemoteIdentity->GetIdentHash (), 32); - size += 32; - } - } - else - { + htobe32buf (packet + size, m_LastReceivedSequenceNumber); + size += 4; // ack Through packet[size] = 0; size++; // NACK count - } - 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) + packet[size] = m_RTO/1000; + size++; // resend delay + if (m_Status == eStreamStatusNew) { - 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 - { + // initial packet + m_Status = eStreamStatusOpen; + if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; + if (m_RemoteLeaseSet) + { + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); + m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; + } + uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | + PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; + if (isNoAck) flags |= PACKET_FLAG_NO_ACK; + bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); + if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + uint8_t * optionsSize = packet + size; // set options size later + size += 2; // options size + m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); + size += identityLen; // from + htobe16buf (packet + size, m_MTU); + size += 2; // max packet size if (isOfflineSignature) { const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); @@ -1019,36 +559,21 @@ 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 } - size += 2; // options size - size += m_SendBuffer.Get(packet + size, m_MTU); // payload + p->len = size; + packets.push_back (p); + numMsgs--; } - 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 @@ -1057,20 +582,13 @@ 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) @@ -1081,53 +599,10 @@ 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 ()) { - 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 - } - } + int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn (); + if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn; } if (lastReceivedSeqn < 0) { @@ -1147,7 +622,6 @@ namespace stream htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; - bool choking = m_IsChoking2; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs @@ -1156,16 +630,10 @@ 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 - 1); // change ack Through back - choking = true; + htobe32buf (packet + 12, nextSeqn); // change ack Through break; } for (uint32_t i = nextSeqn; i < seqn; i++) @@ -1187,36 +655,14 @@ namespace stream size++; // NACK count } packet[size] = 0; - 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++; // resend delay + htobuf16 (packet + size, 0); // no flags set size += 2; // flags - 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 + 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"); } @@ -1229,34 +675,28 @@ namespace stream size += 4; // sendStreamID memset (packet + size, 0, 14); size += 14; // all zeroes - uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_FROM_INCLUDED; - if (!m_DontSign) flags |= PACKET_FLAG_SIGNATURE_INCLUDED; + uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_FROM_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 (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); - } + 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); p.len = size; SendPackets (std::vector { &p }); LogPrint (eLogDebug, "Streaming: Ping of ", p.len, " bytes sent"); @@ -1289,7 +729,7 @@ namespace stream Terminate (); break; default: - LogPrint (eLogWarning, "Streaming: Unexpected stream status=", (int)m_Status, " for sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); }; } @@ -1310,41 +750,18 @@ namespace stream size++; // NACK count packet[size] = 0; size++; // resend delay - 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); + htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - 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); - } + 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; - 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); - } + m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } @@ -1376,7 +793,6 @@ 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); @@ -1392,7 +808,6 @@ namespace stream { if (!m_RemoteLeaseSet) { - CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { @@ -1401,15 +816,7 @@ 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_IsIncoming || m_SequenceNumber > 1); - if (!m_RoutingSession) - { - LogPrint (eLogError, "Streaming: Can't obtain routing session, sSID=", m_SendStreamID); - Terminate (); - return; - } - } + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first @@ -1419,65 +826,36 @@ 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 - { - CancelRemoteLeaseChange (); + if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet + ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) 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 ()) - std::tie(m_CurrentOutboundTunnel, freshTunnel) = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); m_CurrentRemoteLease = nullptr; return; } - if (freshTunnel) - { - LogPrint (eLogDebug, "Streaming: OutboundTunnel changed, set initial window size"); - ResetWindowSize (); -// m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely - } std::vector msgs; for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( - it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets (), it->IsSYN ())); + it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -1485,13 +863,8 @@ 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->SendTunnelDataMsgs (msgs); + m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else { @@ -1508,10 +881,10 @@ namespace stream if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT) + if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels - LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); + LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; @@ -1528,98 +901,6 @@ 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) @@ -1637,149 +918,63 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { - 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; - if (m_IsNAcked && !m_IsClientChoked && !m_IsClientChoked2) - { - for (auto it : m_NACKedPackets) + // check for resend attempts + if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { - 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; - } + 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; } - } - else - { + + // collect packets to resend + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + std::vector packets; 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_IsSendTime) - { - if (m_IsNAcked) m_NumResendAttempts = 1; - else if (m_IsTimeOutResend) m_NumResendAttempts++; - if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) + + // select tunnels if necessary and send + if (packets.size () > 0) { - // loss-based CC - if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED && !m_IsClientChoked) + m_NumResendAttempts++; + m_RTO *= 2; + switch (m_NumResendAttempts) { - 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 (); + 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); } - 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; + ScheduleResend (); } - 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) @@ -1795,13 +990,9 @@ namespace stream { if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ()) { - 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; - } + // seems something went wrong and we should re-select tunnels + m_CurrentOutboundTunnel = nullptr; + m_CurrentRemoteLease = nullptr; } SendQuickAck (); } @@ -1811,43 +1002,23 @@ 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 ().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 // incoming + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); + if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) { - // just close the socket without sending FIN or RST - m_Status = eStreamStatusClosed; - AsyncClose (); - } + m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( + std::make_shared(m_RemoteIdentity)); + return; // we keep m_RemoteLeaseSet for possible next request + } + else + { + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + } } else { @@ -1888,15 +1059,10 @@ namespace stream } if (!updated) { - uint32_t i = m_LocalDestination.GetRandom () % leases.size (); + uint32_t i = rand () % leases.size (); if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) - { // make sure we don't select previous - if (leases.size () > 1) - i = (i + 1) % leases.size (); // if so, pick next - else - isLeaseChanged = false; - } + i = (i + 1) % leases.size (); // if so, pick next m_CurrentRemoteLease = leases[i]; } } @@ -1913,83 +1079,14 @@ 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_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ()) + m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), + m_PendingIncomingTimer (m_Owner->GetService ()) { + if (m_Gzip) + m_Deflator.reset (new i2p::data::GzipDeflator); } StreamingDestination::~StreamingDestination () @@ -2009,7 +1106,6 @@ namespace stream void StreamingDestination::Stop () { ResetAcceptor (); - ResetPongHandler (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); { @@ -2056,8 +1152,6 @@ 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; } @@ -2068,25 +1162,13 @@ namespace stream if (it1 != m_IncomingStreams.end ()) { // already pending - LogPrint(eLogInfo, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); - it1->second->ResetRoutingPath (); // Ack was not delivered, changing path + LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); 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 - if (!incomingStream->GetRemoteLeaseSet ()) - { - LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated"); - incomingStream->Terminate (); // can't send FIN anyway - return; - } + auto ident = incomingStream->GetRemoteIdentity(); // handle saved packets if any { @@ -2188,16 +1270,13 @@ namespace stream { std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); - if (stream->IsIncoming ()) - m_IncomingStreams.erase (stream->GetSendStreamID ()); + m_IncomingStreams.erase (stream->GetSendStreamID ()); if (m_LastStream == stream) m_LastStream = nullptr; } - auto ts = i2p::util::GetSecondsSinceEpoch (); - if (m_Streams.empty () || ts > m_LastCleanupTime + STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL) + if (m_Streams.empty ()) { m_PacketsPool.CleanUp (); m_I2NPMsgsPool.CleanUp (); - m_LastCleanupTime = ts; } } @@ -2207,7 +1286,7 @@ namespace stream if (it == m_Streams.end ()) return false; auto s = it->second; - boost::asio::post (m_Owner->GetService (), [this, s] () + m_Owner->GetService ().post ([this, s] () { s->Close (); // try to send FIN s->Terminate (false); @@ -2220,7 +1299,7 @@ namespace stream { m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet auto s = shared_from_this (); - boost::asio::post (m_Owner->GetService (), [s](void) + m_Owner->GetService ().post([s](void) { // take care about incoming queue for (auto& it: s->m_PendingIncomingStreams) @@ -2237,19 +1316,9 @@ 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) { - boost::asio::post (m_Owner->GetService (), [acceptor, this](void) + m_Owner->GetService ().post([acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) { @@ -2272,26 +1341,6 @@ namespace stream acceptor (stream); } - std::shared_ptr StreamingDestination::AcceptStream (int timeout) - { - std::shared_ptr stream; - std::condition_variable streamAccept; - std::mutex streamAcceptMutex; - std::unique_lock l(streamAcceptMutex); - AcceptOnce ( - [&streamAccept, &streamAcceptMutex, &stream](std::shared_ptr s) - { - stream = s; - std::unique_lock l(streamAcceptMutex); - streamAccept.notify_all (); - }); - if (timeout) - streamAccept.wait_for (l, std::chrono::seconds (timeout)); - else - streamAccept.wait (l); - return stream; - } - void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) @@ -2303,33 +1352,29 @@ namespace stream } } - void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len, - i2p::garlic::ECIESX25519AEADRatchetSession * from) + void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // 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); } std::shared_ptr StreamingDestination::CreateDataMessage ( - const uint8_t * payload, size_t len, uint16_t toPort, bool checksum, bool gzip) + const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { size_t size; - auto msg = (len <= STREAMING_MTU_RATCHETS) ? m_I2NPMsgsPool.AcquireShared () : NewI2NPMessage (); + auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; - if (m_Gzip || gzip) - size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payload, len, buf, msg->maxLen - msg->len); else size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); @@ -2347,15 +1392,5 @@ 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 d3bd314e..088c2e11 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,7 +19,6 @@ #include #include #include "Base.h" -#include "Gzip.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" @@ -27,7 +26,6 @@ #include "Garlic.h" #include "Tunnel.h" #include "util.h" // MemoryPool -#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -52,50 +50,29 @@ 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 = 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 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 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 = 1024; + const size_t MAX_PENDING_INCOMING_BACKLOG = 128; 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), resent (false), from (nullptr) {}; + Packet (): len (0), offset (0), sendTime (0) {}; uint8_t * GetBuffer () { return buf + offset; }; - size_t GetLength () const { return len > offset ? len - offset : 0; }; + size_t GetLength () const { return len - offset; }; uint32_t GetSendStreamID () const { return bufbe32toh (buf); }; uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); }; @@ -103,7 +80,6 @@ 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 ()); }; @@ -158,7 +134,8 @@ namespace stream SendBufferQueue (): m_Size (0) {}; ~SendBufferQueue () { CleanUp (); }; - void Add (std::shared_ptr&& buf); + void Add (const uint8_t * buf, size_t len, SendHandler handler); + 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 (); }; @@ -185,9 +162,9 @@ namespace stream { public: - Stream (boost::asio::io_context& service, StreamingDestination& local, + Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing - Stream (boost::asio::io_context& service, StreamingDestination& local); // incoming + Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; @@ -196,10 +173,8 @@ 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); @@ -210,9 +185,8 @@ namespace stream template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; - size_t Receive (uint8_t * buf, size_t len, int timeout); - void AsyncClose() { boost::asio::post(m_Service, std::bind(&Stream::Close, shared_from_this())); }; + void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); }; /** only call close from destination thread, use Stream::AsyncClose for other threads */ void Close (); @@ -248,62 +222,38 @@ namespace stream void UpdateCurrentRemoteLease (bool expired = false); template - void HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout); + void HandleReceiveTimer (const boost::system::error_code& ecode, const 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_context& m_Service; + boost::asio::io_service& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; - uint32_t m_DropWindowDelaySequenceNumber; - uint32_t m_TunnelsChangeSequenceNumber; int32_t m_LastReceivedSequenceNumber; - int32_t m_PreviousReceivedSequenceNumber; - int32_t m_LastConfirmedReceivedSequenceNumber; // for limit inbound speed StreamStatus m_Status; - 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; + bool m_IsAckSendScheduled; 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; - std::set m_NACKedPackets; - boost::asio::deadline_timer m_ReceiveTimer, m_SendTimer, m_ResendTimer, m_AckSendTimer; + boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; + std::mutex m_SendBufferMutex; SendBufferQueue m_SendBuffer; - 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; + int m_WindowSize, m_RTT, m_RTO, m_AckDelay; + uint64_t m_LastWindowSizeIncreaseTime; + int m_NumResendAttempts; size_t m_MTU; }; @@ -312,7 +262,6 @@ namespace stream public: typedef std::function)> Acceptor; - typedef std::function PongHandler; StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); ~StreamingDestination (); @@ -324,26 +273,21 @@ 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, i2p::garlic::ECIESX25519AEADRatchetSession * from); - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true, bool gzip = false); + void HandleDataMessagePayload (const uint8_t * buf, size_t len); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } - uint32_t GetRandom (); private: @@ -361,19 +305,17 @@ 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; - uint64_t m_LastCleanupTime; // in seconds - + i2p::util::MemoryPool > m_I2NPMsgsPool; + public: i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; @@ -385,7 +327,7 @@ namespace stream void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout) { auto s = shared_from_this(); - boost::asio::post (m_Service, [s, buffer, handler, timeout](void) + m_Service.post ([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); @@ -394,19 +336,20 @@ namespace stream int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); int left = timeout - t; - s->m_ReceiveTimer.async_wait ( - [s, buffer, handler, left](const boost::system::error_code & ec) + auto self = s->shared_from_this(); + self->m_ReceiveTimer.async_wait ( + [self, buffer, handler, left](const boost::system::error_code & ec) { - s->HandleReceiveTimer(ec, buffer, handler, left); + self->HandleReceiveTimer(ec, buffer, handler, left); }); } }); } template - void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout) + void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { - size_t received = ConcatenatePackets ((uint8_t *)buffer.data (), buffer.size ()); + size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); 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 30b7708d..d898395f 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,14 +12,10 @@ #include #include #include -#include -#include #include "Base.h" -namespace i2p -{ -namespace data -{ +namespace i2p { +namespace data { template class Tag { @@ -62,31 +58,28 @@ namespace data std::string ToBase64 (size_t len = sz) const { - return i2p::data::ByteStreamToBase64 (m_Buf, len); + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2); + return std::string (str, str + l); } std::string ToBase32 (size_t len = sz) const { - return i2p::data::ByteStreamToBase32 (m_Buf, len); + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2); + return std::string (str, str + l); } - size_t FromBase32 (std::string_view s) + size_t FromBase32 (const std::string& s) { - return i2p::data::Base32ToByteStream (s, m_Buf, sz); + return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); } - size_t FromBase64 (std::string_view s) + size_t FromBase64 (const std::string& s) { - return i2p::data::Base64ToByteStream (s, m_Buf, sz); + return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); } - uint8_t GetBit (int i) const - { - int pos = i >> 3; // /8 - if (pos >= (int)sz) return 0; - return m_Buf[pos] & (0x80 >> (i & 0x07)); - } - private: union // 8 bytes aligned diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index a22e9bde..99507398 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -60,16 +60,18 @@ namespace util static void SyncTimeWithNTP (const std::string& address) { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); - boost::asio::io_context service; + boost::asio::io_service service; boost::system::error_code ec; - auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec); + auto it = boost::asio::ip::udp::resolver (service).resolve ( + boost::asio::ip::udp::resolver::query (address, "ntp"), ec); if (!ec) { bool found = false; + boost::asio::ip::udp::resolver::iterator end; boost::asio::ip::udp::endpoint ep; - for (const auto& it: endpoints) + while (it != end) { - ep = it; + ep = *it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) @@ -86,6 +88,7 @@ namespace util } } if (found) break; + it++; } if (!found) { @@ -151,7 +154,7 @@ namespace util { m_IsRunning = true; LogPrint(eLogInfo, "Timestamp: NTP time sync starting"); - boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this)); + m_Service.post (std::bind (&NTPTimeSync::Sync, this)); m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this))); } else @@ -229,34 +232,11 @@ 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 6b224685..995ea36f 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,7 +14,6 @@ #include #include #include -#include // for boost 1.89 namespace i2p { @@ -25,13 +24,8 @@ namespace util uint32_t GetMinutesSinceEpoch (); uint32_t GetHoursSinceEpoch (); - 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 GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes + void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes void AdjustTimeOffset (int64_t offset); // in seconds from current class NTPTimeSync @@ -53,7 +47,7 @@ namespace util bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_context m_Service; + boost::asio::io_service 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 b24c8ac5..c4f3fa19 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,14 +8,9 @@ #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" @@ -25,39 +20,19 @@ namespace i2p namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, - const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, - const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): - TunnelBase (receiveTunnelID, nextTunnelID, nextIdent), - m_LayerKey (layerKey), m_IVKey (ivKey) + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey): + TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) { + m_Encryption.SetKeys (layerKey, ivKey); } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { - if (!m_Encryption) - { - m_Encryption.reset (new i2p::crypto::TunnelEncryption); - m_Encryption->SetKeys (m_LayerKey, m_IVKey); - } - m_Encryption->Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); + m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } - std::string TransitTunnel::GetNextPeerName () const - { - return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()); - } - - void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) - { - LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); - } - - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) - { - LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); - } - TransitTunnelParticipant::~TransitTunnelParticipant () { } @@ -79,103 +54,48 @@ namespace tunnel 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 + i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); + m_TunnelDataMsgs.clear (); } } - std::string TransitTunnelParticipant::GetNextPeerName () const + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { - if (m_Sender) - { - auto transport = m_Sender->GetCurrentTransport (); - if (transport) - return TransitTunnel::GetNextPeerName () + "-" + - i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); - } - return TransitTunnel::GetNextPeerName (); - } - + LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); + } + + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + { + LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); + } + void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; - std::lock_guard l(m_SendMutex); + std::unique_lock l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { - std::lock_guard l(m_SendMutex); + std::unique_lock 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 ()); - std::lock_guard l(m_HandleMutex); - if (!m_Endpoint) m_Endpoint = std::make_unique(false); // transit endpoint is always outbound - m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg); + 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, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) @@ -194,440 +114,5 @@ 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 34bcc79f..bce90958 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,11 +10,10 @@ #define TRANSIT_TUNNEL_H__ #include -#include +#include #include #include #include "Crypto.h" -#include "Queue.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" @@ -29,21 +28,18 @@ namespace tunnel public: TransitTunnel (uint32_t receiveTunnelID, - const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, - const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey); + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; - virtual std::string GetNextPeerName () const; // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg) override; - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; - + void SendTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: - i2p::crypto::AESKey m_LayerKey, m_IVKey; - std::unique_ptr m_Encryption; + i2p::crypto::TunnelEncryption m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel @@ -51,22 +47,20 @@ namespace tunnel public: TransitTunnelParticipant (uint32_t receiveTunnelID, - const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, - const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_NumTransmittedBytes (0) {}; ~TransitTunnelParticipant (); - size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; }; - std::string GetNextPeerName () const override; - void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; - void FlushTunnelDataMsgs () override; + size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); + void FlushTunnelDataMsgs (); private: size_t m_NumTransmittedBytes; - std::list > m_TunnelDataMsgs; - std::unique_ptr m_Sender; + std::vector > m_TunnelDataMsgs; }; class TransitTunnelGateway: public TransitTunnel @@ -74,16 +68,15 @@ namespace tunnel public: TransitTunnelGateway (uint32_t receiveTunnelID, - const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, - const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, - layerKey, ivKey), m_Gateway(*this) {}; + layerKey, ivKey), m_Gateway(this) {}; + + void SendTunnelDataMsg (std::shared_ptr msg); + void FlushTunnelDataMsgs (); + size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; - 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; @@ -95,70 +88,25 @@ namespace tunnel public: 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) {}; + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey): + TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), + m_Endpoint (false) {}; // transit endpoint is always outbound + + void Cleanup () { m_Endpoint.Cleanup (); } + + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); + size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } - 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: - std::mutex m_HandleMutex; - std::unique_ptr m_Endpoint; + TunnelEndpoint 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, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * 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 2cff0b1f..12d4894b 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, 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 @@ -24,74 +24,51 @@ namespace i2p { namespace transport { - const size_t IPV4_HEADER_SIZE = 20; - const size_t IPV6_HEADER_SIZE = 40; - const size_t UDP_HEADER_SIZE = 8; - - template class SignedData { public: - SignedData (): m_Size(0) {} + SignedData () {} SignedData (const SignedData& other) { - m_Size = other.m_Size; - memcpy (m_Buf, other.m_Buf, m_Size); + m_Stream << other.m_Stream.rdbuf (); } - - void Reset () + void Insert (const uint8_t * buf, size_t len) { - m_Size = 0; - } - - size_t Insert (const uint8_t * buf, size_t len) - { - if (m_Size + len > sz) len = sz - m_Size; - memcpy (m_Buf + m_Size, buf, len); - m_Size += len; - return len; + m_Stream.write ((char *)buf, len); } template void Insert (T t) { - Insert ((const uint8_t *)&t, sizeof (T)); + m_Stream.write ((char *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { - return ident->Verify (m_Buf, m_Size, signature); + return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { - keys.Sign (m_Buf, m_Size, signature); + keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } private: - uint8_t m_Buf[sz]; - size_t m_Size; + std::stringstream m_Stream; }; - 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_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) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), + m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); - m_CreationTime = m_LastActivityTimestamp; } virtual ~TransportSession () {}; @@ -111,92 +88,25 @@ namespace transport } size_t GetNumSentBytes () const { return m_NumSentBytes; }; - void UpdateNumSentBytes (size_t len) - { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumSentBytes += len; - UpdateBandwidth (); - } size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - void UpdateNumReceivedBytes (size_t len) - { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumReceivedBytes += len; - UpdateBandwidth (); - } - size_t GetSendQueueSize () const { return m_SendQueueSize; }; - void SetSendQueueSize (size_t s) { m_SendQueueSize = s; }; bool IsOutgoing () const { return m_IsOutgoing; }; - bool IsSlow () const { return m_HandshakeInterval > TRANSPORT_SESSION_SLOWNESS_THRESHOLD && - m_HandshakeInterval < TRANSPORT_SESSION_MAX_HANDSHAKE_INTERVAL; }; - bool IsBandwidthExceeded (bool isHighBandwidth) const - { - auto limit = isHighBandwidth ? i2p::data::HIGH_BANDWIDTH_LIMIT*1024 : i2p::data::LOW_BANDWIDTH_LIMIT*1024; // convert to bytes - return std::max (m_InBandwidth, m_OutBandwidth) > limit; - } - + int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; bool IsTerminationTimeoutExpired (uint64_t ts) const - { - return ts >= m_LastActivityTimestamp + GetTerminationTimeout () || - ts + GetTerminationTimeout () < m_LastActivityTimestamp; - }; + { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; - uint32_t GetCreationTime () const { return m_CreationTime; }; - void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers + virtual void SendLocalRouterInfo () { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; + virtual void SendI2NPMessages (const std::vector >& msgs) = 0; - 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) - { - std::list > msgs{ CreateDatabaseStoreMsg () }; - SendI2NPMessages (msgs); - }; - virtual void SendI2NPMessages (std::list >& msgs) = 0; - virtual bool IsEstablished () const = 0; - virtual i2p::data::RouterInfo::SupportedTransports GetTransportType () const = 0; - - private: - - void UpdateBandwidth () - { - int64_t interval = m_LastActivityTimestamp - m_LastBandwidthUpdateTimestamp; - if (interval < 0 || interval > 60*10) // 10 minutes - { - // clock was adjusted, copy new values - m_LastBandWidthUpdateNumSentBytes = m_NumSentBytes; - m_LastBandWidthUpdateNumReceivedBytes = m_NumReceivedBytes; - m_LastBandwidthUpdateTimestamp = m_LastActivityTimestamp; - return; - } - if ((uint64_t)interval > TRANSPORT_SESSION_BANDWIDTH_UPDATE_MIN_INTERVAL) - { - m_OutBandwidth = (m_NumSentBytes - m_LastBandWidthUpdateNumSentBytes)/interval; - m_LastBandWidthUpdateNumSentBytes = m_NumSentBytes; - m_InBandwidth = (m_NumReceivedBytes - m_LastBandWidthUpdateNumReceivedBytes)/interval; - m_LastBandWidthUpdateNumReceivedBytes = m_NumReceivedBytes; - m_LastBandwidthUpdateTimestamp = m_LastActivityTimestamp; - } - } - protected: std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; + size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; - uint32_t m_CreationTime; // seconds since epoch - int64_t m_HandshakeInterval; // in milliseconds between SessionRequest->SessionCreated or SessionCreated->SessionConfirmed - - private: - - size_t m_SendQueueSize, m_NumSentBytes, m_NumReceivedBytes, - m_LastBandWidthUpdateNumSentBytes, m_LastBandWidthUpdateNumReceivedBytes; - uint64_t m_LastActivityTimestamp, m_LastBandwidthUpdateTimestamp; - uint32_t m_InBandwidth, m_OutBandwidth; + uint64_t m_LastActivityTimestamp; }; } } diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 98dbcd94..8b656561 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,12 +1,11 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ -#include // for boost::to_lower #include "Log.h" #include "Crypto.h" #include "RouterContext.h" @@ -25,7 +24,7 @@ namespace transport { template EphemeralKeysSupplier::EphemeralKeysSupplier (int size): - m_QueueSize (size), m_IsRunning (false) + m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) { } @@ -39,7 +38,7 @@ namespace transport void EphemeralKeysSupplier::Start () { m_IsRunning = true; - m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier::Run, this))); + m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); } template @@ -53,15 +52,9 @@ namespace transport if (m_Thread) { m_Thread->join (); - m_Thread = nullptr; + delete m_Thread; + m_Thread = 0; } - if (!m_Queue.empty ()) - { - // clean up queue - std::queue > tmp; - std::swap (m_Queue, tmp); - } - m_KeysPool.CleanUpMt (); } template @@ -72,19 +65,18 @@ namespace transport while (m_IsRunning) { int num, total = 0; - while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize) + while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10) { CreateEphemeralKeys (num); total += num; } - if (total > m_QueueSize) + if (total >= 10) { 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 @@ -99,7 +91,7 @@ namespace transport { for (int i = 0; i < num; i++) { - auto pair = m_KeysPool.AcquireSharedMt (); + auto pair = std::make_shared (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); @@ -121,7 +113,7 @@ namespace transport } } // queue is empty, create new - auto pair = m_KeysPool.AcquireSharedMt (); + auto pair = std::make_shared (); pair->GenerateKeys (); return pair; } @@ -131,37 +123,25 @@ namespace transport { if (pair) { - std::unique_lock l(m_AcquiredMutex); + std::unique_lockl(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else - LogPrint(eLogError, "Transports: Return null keys"); + LogPrint(eLogError, "Transports: Return null DHKeys"); } - 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 (NUM_X25519_PRE_GENERATED_KEYS), - m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), - m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), - m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), - m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), - m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) + m_SSUServer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), + m_X25519KeysPairSupplier (15), // 15 pre-generated keys + m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), + m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), + m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), + m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) { } @@ -172,25 +152,21 @@ namespace transport { delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr; delete m_PeerTestTimer; m_PeerTestTimer = nullptr; - delete m_UpdateBandwidthTimer; m_UpdateBandwidthTimer = nullptr; delete m_Work; m_Work = nullptr; delete m_Service; m_Service = nullptr; } } - void Transports::Start (bool enableNTCP2, bool enableSSU2) + void Transports::Start (bool enableNTCP2, bool enableSSU, bool enableSSU2) { if (!m_Service) { - m_Service = new boost::asio::io_context (); - m_Work = new boost::asio::executor_work_guard (m_Service->get_executor ()); + m_Service = new boost::asio::io_service (); + m_Work = new boost::asio::io_service::work (*m_Service); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); - m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service); } - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); i2p::config::GetOption("nat", m_IsNAT); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; @@ -214,91 +190,65 @@ namespace transport m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); i2p::context.SetStatus (eRouterStatusProxy); - if (ipv6) - i2p::context.SetStatusV6 (eRouterStatusProxy); } else - LogPrint(eLogCritical, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogError, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); } else - LogPrint(eLogCritical, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogError, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); } else m_NTCP2Server = new NTCP2Server (); } - // create SSU2 server - if (enableSSU2) + // create SSU server + int ssuPort = 0; + if (enableSSU) { - m_SSU2Server = new SSU2Server (); - std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy); - if (!ssu2proxy.empty()) + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) { - if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks") + if (!address) continue; + if (address->transportStyle == RouterInfo::eTransportSSU) { - if (m_SSU2Server->SetProxy (proxyurl.host, proxyurl.port)) - { - i2p::context.SetStatus (eRouterStatusProxy); - if (ipv6) - i2p::context.SetStatusV6 (eRouterStatusProxy); - } - else - LogPrint(eLogCritical, "Transports: Can't set SSU2 proxy ", ssu2proxy); + ssuPort = address->port; + m_SSUServer = new SSUServer (address->port); + break; } - else - LogPrint(eLogCritical, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); } } + // create SSU2 server + if (enableSSU2) m_SSU2Server = new SSU2Server (); // bind to interfaces + bool ipv4; i2p::config::GetOption("ipv4", ipv4); if (ipv4) { std::string address; i2p::config::GetOption("address4", address); if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (address, ec); + auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); - if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr); - } - } - - if (enableSSU2) - { - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu); - if (mtu) - { - if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; - if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; - i2p::context.SetMTU (mtu, true); + if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); } } } + bool ipv6; i2p::config::GetOption("ipv6", ipv6); if (ipv6) { std::string address; i2p::config::GetOption("address6", address); if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (address, ec); + auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) { if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); - if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr); - } - } - - if (enableSSU2) - { - uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); - if (mtu) - { - if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; - if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; - i2p::context.SetMTU (mtu, false); + if (m_SSUServer) m_SSUServer->SetLocalAddress (addr); } } } @@ -310,7 +260,7 @@ namespace transport if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (address, ec); + auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr)) m_NTCP2Server->SetLocalAddress (addr); } @@ -318,28 +268,30 @@ namespace transport // start servers if (m_NTCP2Server) m_NTCP2Server->Start (); - if (m_SSU2Server) m_SSU2Server->Start (); - if (m_SSU2Server) DetectExternalIP (); - - 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++) + if (m_SSUServer) { - 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; + LogPrint (eLogInfo, "Transports: Start listening UDP port ", ssuPort); + try + { + m_SSUServer->Start (); + } + catch (std::exception& ex ) + { + LogPrint(eLogError, "Transports: Failed to bind to UDP port", ssuPort); + m_SSUServer->Stop (); + delete m_SSUServer; + m_SSUServer = nullptr; + } + if (m_SSUServer) DetectExternalIP (); } - m_TrafficSamplePtr = TRAFFIC_SAMPLE_COUNT - 1; + if (m_SSU2Server) m_SSU2Server->Start (); - m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); - m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); + 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)); if (m_IsNAT) { - m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); + m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } @@ -348,6 +300,13 @@ namespace transport { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); + m_Peers.clear (); + if (m_SSUServer) + { + m_SSUServer->Stop (); + delete m_SSUServer; + m_SSUServer = nullptr; + } if (m_SSU2Server) { @@ -372,7 +331,6 @@ namespace transport delete m_Thread; m_Thread = nullptr; } - m_Peers.clear (); } void Transports::Run () @@ -392,85 +350,50 @@ namespace transport } } - void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit) + void Transports::UpdateBandwidth () { - 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) + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + if (m_LastBandwidthUpdateTime > 0) { - LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000); - return; + auto delta = ts - m_LastBandwidthUpdateTime; + if (delta > 0) + { + m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second + m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second + m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta; + } } - in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta; - out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta; - transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta; + m_LastBandwidthUpdateTime = ts; + m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; + m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; + m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; } - void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) + bool Transports::IsBandwidthExceeded () const { - if (ecode != boost::asio::error::operation_aborted) - { - m_TrafficSamplePtr++; - if (m_TrafficSamplePtr == TRAFFIC_SAMPLE_COUNT) - m_TrafficSamplePtr = 0; - - TrafficSample& sample = m_TrafficSamples[m_TrafficSamplePtr]; - sample.Timestamp = i2p::util::GetMillisecondsSinceEpoch(); - sample.TotalReceivedBytes = m_TotalReceivedBytes; - sample.TotalSentBytes = m_TotalSentBytes; - sample.TotalTransitTransmittedBytes = m_TotalTransitTransmittedBytes; - - UpdateBandwidthValues (1, m_InBandwidth, m_OutBandwidth, m_TransitBandwidth); - UpdateBandwidthValues (15, m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s); - UpdateBandwidthValues (300, m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m); - - m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); - m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); - } + auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes + auto bw = std::max (m_InBandwidth, m_OutBandwidth); + return bw > limit; } - int Transports::GetCongestionLevel (bool longTerm) const + bool Transports::IsTransitBandwidthExceeded () const { - 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); + auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes + return m_TransitBandwidth > limit; } - std::future > Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) + void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { if (m_IsOnline) - return SendMessages (ident, { msg }); - return {}; // invalid future + SendMessages (ident, std::vector > {msg }); } - std::future > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs) + void Transports::SendMessages (const i2p::data::IdentHash& ident, const 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) + m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); + } + + void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { @@ -478,171 +401,179 @@ namespace transport for (auto& it: msgs) m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); - return nullptr; + return; } - if(RoutesRestricted() && !IsRestrictedPeer(ident)) return nullptr; - std::shared_ptr peer; + if(RoutesRestricted() && !IsRestrictedPeer(ident)) return; + auto it = m_Peers.find (ident); + if (it == m_Peers.end ()) { - 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 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; + if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable + { + std::unique_lock l(m_PeersMutex); + it = m_Peers.insert (std::pair(ident, { 0, r, {}, + i2p::util::GetSecondsSinceEpoch (), {} })).first; } - if (peer) - connected = ConnectToPeer (ident, peer); + connected = ConnectToPeer (ident, it->second); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } - if (!connected) return nullptr; + if (!connected) return; } - - if (!peer) return nullptr; - if (peer->IsConnected ()) - { - auto session = peer->sessions.front (); - if (session) session->SendI2NPMessages (msgs); - return session; - } + if (!it->second.sessions.empty ()) + it->second.sessions.front ()->SendI2NPMessages (msgs); else { - auto sz = peer->delayedMessages.size (); - if (sz < MAX_NUM_DELAYED_MESSAGES) + if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) { - 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); + for (auto& it1: msgs) + it->second.delayedMessages.push_back (it1); } else { LogPrint (eLogWarning, "Transports: Delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); - std::lock_guard l(m_PeersMutex); - m_Peers.erase (ident); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); } } - return nullptr; } - bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer) + bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { - 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.router) // reconnect + peer.router = netdb.FindRouter (ident); // try to get new one from netdb + if (peer.router) // we have RI already { - if (peer->priority.empty ()) - SetPriority (peer); - while (peer->numAttempts < (int)peer->priority.size ()) + if (peer.numAttempts < 2) // NTCP2, 0 - ipv6, 1- ipv4 { - auto tr = peer->priority[peer->numAttempts]; - peer->numAttempts++; - switch (tr) + if (m_NTCP2Server) // we support NTCP2 { - case i2p::data::RouterInfo::eNTCP2V4: - case i2p::data::RouterInfo::eNTCP2V6: + std::shared_ptr address; + if (!peer.numAttempts) // NTCP2 ipv6 { - if (!m_NTCP2Server) continue; - std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? - peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address (); - if (address && IsInReservedRange(address->host)) - address = nullptr; - if (address) + if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) { - auto s = std::make_shared (*m_NTCP2Server, peer->router, address); - if( m_NTCP2Server->UsingProxy()) - m_NTCP2Server->ConnectWithProxy(s); - else - m_NTCP2Server->Connect (s); - return true; + address = peer.router->GetPublishedNTCP2V6Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; } - break; + peer.numAttempts++; } - case i2p::data::RouterInfo::eSSU2V4: - case i2p::data::RouterInfo::eSSU2V6: + if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { - if (!m_SSU2Server) continue; - std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSU2V6) ? - peer->router->GetSSU2V6Address () : peer->router->GetSSU2V4Address (); - if (address && IsInReservedRange(address->host)) - address = nullptr; - if (address && address->IsReachableSSU ()) + if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) { - if (m_SSU2Server->CreateSession (peer->router, address)) - return true; + address = peer.router->GetPublishedNTCP2V4Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; } - break; + peer.numAttempts++; } - case i2p::data::RouterInfo::eNTCP2V6Mesh: + if (address) { - if (!m_NTCP2Server) continue; - 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); + if( m_NTCP2Server->UsingProxy()) + m_NTCP2Server->ConnectWithProxy(s); + else m_NTCP2Server->Connect (s); - return true; - } - break; + return true; + } + } + else + peer.numAttempts = 2; // switch to SSU + } + if (peer.numAttempts == 2 || peer.numAttempts == 3) // SSU + { + if (m_SSUServer) + { + std::shared_ptr address; + if (peer.numAttempts == 2) // SSU ipv6 + { + if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) + { + address = peer.router->GetSSUV6Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (!address && peer.numAttempts == 3) // SSU ipv4 + { + if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) + { + address = peer.router->GetSSUAddress (true); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (address && address->IsReachableSSU ()) + { + if (m_SSUServer->CreateSession (peer.router, address)) + return true; + } + } + else + peer.numAttempts += 2; // switch to Mesh + } + if (peer.numAttempts == 4) // Mesh + { + peer.numAttempts++; + if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) + { + auto address = peer.router->GetYggdrasilAddress (); + if (address) + { + auto s = std::make_shared (*m_NTCP2Server, peer.router, address); + m_NTCP2Server->Connect (s); + return true; } - default: - LogPrint (eLogError, "Transports: Unknown transport ", (int)tr); } } - - LogPrint (eLogInfo, "Transports: No compatible addresses available"); - if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) - i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them - peer->Done (); - std::lock_guard l(m_PeersMutex); - m_Peers.erase (ident); - return false; - } - else if (i2p::data::IsRouterBanned (ident)) - { - LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); - peer->Done (); - std::lock_guard l(m_PeersMutex); + if (peer.numAttempts == 5 || peer.numAttempts == 6) // SSU2 + { + if (m_SSU2Server) + { + std::shared_ptr address; + if (peer.numAttempts == 5) // SSU2 ipv6 + { + if (context.GetRouterInfo ().IsSSU2V6 () && peer.router->IsReachableBy (RouterInfo::eSSU2V6)) + { + address = peer.router->GetSSU2V6Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (!address && peer.numAttempts == 6) // SSU2 ipv4 + { + if (context.GetRouterInfo ().IsSSU2V4 () && peer.router->IsReachableBy (RouterInfo::eSSU2V4)) + { + address = peer.router->GetSSU2V4Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (address && address->IsReachableSSU ()) + { + if (m_SSU2Server->CreateSession (peer.router, address)) + return true; + } + } + else + peer.numAttempts += 2; + } + LogPrint (eLogInfo, "Transports: No compatble NTCP2 or SSU addresses available"); + i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed + peer.Done (); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -655,126 +586,29 @@ namespace transport return true; } - void Transports::SetPriority (std::shared_ptr peer) - { - static const std::vector - ntcp2Priority = - { - i2p::data::RouterInfo::eNTCP2V6, - i2p::data::RouterInfo::eNTCP2V4, - i2p::data::RouterInfo::eSSU2V6, - i2p::data::RouterInfo::eSSU2V4, - i2p::data::RouterInfo::eNTCP2V6Mesh - }, - ssu2Priority = - { - i2p::data::RouterInfo::eSSU2V6, - i2p::data::RouterInfo::eSSU2V4, - i2p::data::RouterInfo::eNTCP2V6, - i2p::data::RouterInfo::eNTCP2V4, - i2p::data::RouterInfo::eNTCP2V6Mesh - }; - if (!peer || !peer->router) return; - auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & - peer->router->GetCompatibleTransports (true); - auto directTransports = compatibleTransports & peer->router->GetPublishedTransports (); - peer->numAttempts = 0; - peer->priority.clear (); - - std::shared_ptr profile; - if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory - bool ssu2 = false; // NTCP2 by default - bool isReal = profile ? profile->IsReal () : true; - if (isReal) - { - ssu2 = m_Rng () & 1; // 1/2 - if (ssu2 && !profile) - { - profile = peer->router->GetProfile (); // load profile if necessary - isReal = profile->IsReal (); - if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real - } - } - const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; - if (directTransports) - { - // direct connections have higher priority - if (!isReal && (directTransports & (i2p::data::RouterInfo::eNTCP2V4 | i2p::data::RouterInfo::eNTCP2V6))) - { - // Non-confirmed router and a NTCP2 direct connection is presented - compatibleTransports &= ~directTransports; // exclude SSU2 direct connections - directTransports &= ~(i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6); - } - for (auto transport: priority) - if (transport & directTransports) - peer->priority.push_back (transport); - compatibleTransports &= ~directTransports; - } - if (compatibleTransports) - { - // then remaining - for (auto transport: priority) - if (transport & compatibleTransports) - peer->priority.push_back (transport); - } - if (peer->priority.empty ()) - { - // try recently connected SSU2 if any - auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & - peer->router->GetCompatibleTransports (false); - if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && - peer->router->HasProfile ()) - { - auto ep = peer->router->GetProfile ()->GetLastEndpoint (); - if (!ep.address ().is_unspecified () && ep.port ()) - { - if (ep.address ().is_v4 ()) - { - if ((supportedTransports & i2p::data::RouterInfo::eSSU2V4) && - m_SSU2Server->IsConnectedRecently (ep, false)) - peer->priority.push_back (i2p::data::RouterInfo::eSSU2V4); - } - else if (ep.address ().is_v6 ()) - { - if ((supportedTransports & i2p::data::RouterInfo::eSSU2V6) && - m_SSU2Server->IsConnectedRecently (ep)) - peer->priority.push_back (i2p::data::RouterInfo::eSSU2V6); - } - } - } - } - } - void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { - boost::asio::post (*m_Service, std::bind (&Transports::HandleRequestComplete, this, r, ident)); + m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); } void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { - std::shared_ptr peer; + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) { - std::lock_guard l(m_PeersMutex); - auto it = m_Peers.find (ident); - if (it != m_Peers.end ()) + if (r) { - if (r) - peer = it->second; - else - m_Peers.erase (it); - } + LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); + it->second.router = r; + ConnectToPeer (ident, it->second); + } + else + { + LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages"); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); + } } - - if (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 () @@ -785,89 +619,67 @@ namespace transport i2p::context.SetStatus (eRouterStatusOK); return; } - if (m_SSU2Server) + if (m_SSUServer) PeerTest (); else - LogPrint (eLogWarning, "Transports: Can't detect external IP. SSU or SSU2 is not available"); + LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); } void Transports::PeerTest (bool ipv4, bool ipv6) { - if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return; + if (RoutesRestricted() || !m_SSUServer) return; if (ipv4 && i2p::context.SupportsV4 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv4"); - std::unordered_set excluded; + std::set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - int testDelay = 0; + bool statusChanged = false; for (int i = 0; i < 5; i++) { - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 + auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4 if (router) { - if (!i2p::context.GetTesting ()) - { - i2p::context.SetTesting (true); - // send first peer test immediately - m_SSU2Server->StartPeerTest (router, true); - } - else + auto addr = router->GetSSUAddress (true); // ipv4 + if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { - 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); - }); - } - } + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatus (eRouterStatusTesting); // first time only + } + m_SSUServer->CreateSession (router, addr, true); // peer test v4 + } excluded.insert (router->GetIdentHash ()); } } - if (excluded.size () <= 1) + if (!statusChanged) LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); } if (ipv6 && i2p::context.SupportsV6 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv6"); - std::unordered_set excluded; + std::set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - int testDelay = 0; + bool statusChanged = false; for (int i = 0; i < 5; i++) { - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 + auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6 if (router) { - if (!i2p::context.GetTestingV6 ()) - { - i2p::context.SetTestingV6 (true); - // send first peer test immediately - m_SSU2Server->StartPeerTest (router, false); - } - else + auto addr = router->GetSSUV6Address (); + if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { - 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); - }); - } + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only + } + m_SSUServer->CreateSession (router, addr, true); // peer test v6 } excluded.insert (router->GetIdentHash ()); } } - if (excluded.size () <= 1) + if (!statusChanged) LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); } } @@ -884,7 +696,7 @@ namespace transport void Transports::PeerConnected (std::shared_ptr session) { - boost::asio::post (*m_Service, [session, this]() + m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; @@ -892,36 +704,12 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - 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 + it->second.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 = peer->delayedMessages.front (); + auto firstMsg = it->second.delayedMessages[0]; 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 @@ -930,10 +718,11 @@ namespace transport session->SendLocalRouterInfo (); else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds - peer->sessions.push_back (session); - session->SendI2NPMessages (peer->delayedMessages); // send and clear + it->second.sessions.push_back (session); + session->SendI2NPMessages (it->second.delayedMessages); + it->second.delayedMessages.clear (); } - else // incoming connection or peer test + else // incoming connection { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted @@ -941,30 +730,16 @@ namespace transport session->Done(); return; } - 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 (); - 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); + session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore + std::unique_lock l(m_PeersMutex); + m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); } }); } void Transports::PeerDisconnected (std::shared_ptr session) { - boost::asio::post (*m_Service, [session, this]() + m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; @@ -972,26 +747,20 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - auto peer = it->second; - bool wasConnected = peer->IsConnected (); - peer->sessions.remove (session); - if (!peer->IsConnected ()) + auto before = it->second.sessions.size (); + it->second.sessions.remove (session); + if (it->second.sessions.empty ()) { - if (peer->delayedMessages.size () > 0) + if (it->second.delayedMessages.size () > 0) { - if (wasConnected) // we had an active session before - peer->numAttempts = 0; // start over - ConnectToPeer (ident, peer); + if (before > 0) // we had an active session before + it->second.numAttempts = 0; // start over + ConnectToPeer (ident, it->second); } else { - { - 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 (); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); } } } @@ -1000,13 +769,9 @@ namespace transport bool Transports::IsConnected (const i2p::data::IdentHash& ident) const { - std::lock_guard l(m_PeersMutex); -#if __cplusplus >= 202002L // C++20 - return m_Peers.contains (ident); -#else + std::unique_lock l(m_PeersMutex); auto it = m_Peers.find (ident); return it != m_Peers.end (); -#endif } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) @@ -1016,46 +781,27 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - it->second->sessions.remove_if ( - [](std::shared_ptr session)->bool - { - return !session || !session->IsEstablished (); - }); - if (!it->second->IsConnected () && ts > it->second->creationTime + SESSION_CREATION_TIMEOUT) + if (it->second.sessions.empty () && 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"); - /* 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); + auto profile = i2p::data::GetRouterProfile(it->first); + if (profile) + { + profile->TunnelNonReplied(); + } + std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else - { - if (ts > it->second->nextRouterInfoUpdateTime) - { - auto session = it->second->sessions.front (); - if (session) - session->SendLocalRouterInfo (true); - it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + - m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; - } ++it; - } } - 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 + UpdateBandwidth (); // TODO: use separate timer(s) for it + bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting; + bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting; + // if still testing, repeat peer test if (ipv4Testing || ipv6Testing) PeerTest (ipv4Testing, ipv6Testing); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } @@ -1065,116 +811,23 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { PeerTest (); - m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); + m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } - template - std::shared_ptr Transports::GetRandomPeer (Filter filter) const + std::shared_ptr Transports::GetRandomPeer () const { - if (m_Peers.empty()) return nullptr; - auto ts = i2p::util::GetSecondsSinceEpoch (); - bool found = false; + if (m_Peers.empty ()) return nullptr; i2p::data::IdentHash ident; { - uint16_t inds[3]; - RAND_bytes ((uint8_t *)inds, sizeof (inds)); - std::lock_guard l(m_PeersMutex); - auto count = m_Peers.size (); - if(count == 0) return nullptr; - inds[0] %= count; + std::unique_lock l(m_PeersMutex); auto it = m_Peers.begin (); - std::advance (it, inds[0]); - // try random peer - if (it != m_Peers.end () && filter (it->second)) - { - ident = it->first; - found = true; - } - else - { - // try some peers around - auto it1 = m_Peers.begin (); - if (inds[0]) - { - // before - inds[1] %= inds[0]; - std::advance (it1, (inds[1] + inds[0])/2); - } - else - it1 = it; - auto it2 = it; - if (inds[0] < m_Peers.size () - 1) - { - // after - inds[2] %= (m_Peers.size () - 1 - inds[0]); inds[2] /= 2; - std::advance (it2, inds[2]); - } - // it1 - from, it2 - to - it = it1; - while (it != it2 && it != m_Peers.end ()) - { - if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && - filter (it->second)) - { - ident = it->first; - it->second->lastSelectionTime = ts; - found = true; - break; - } - it++; - } - if (!found) - { - // still not found, try from the beginning - it = m_Peers.begin (); - while (it != it1 && it != m_Peers.end ()) - { - if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && - filter (it->second)) - { - ident = it->first; - it->second->lastSelectionTime = ts; - found = true; - break; - } - it++; - } - if (!found) - { - // still not found, try to the beginning - it = it2; - while (it != m_Peers.end ()) - { - if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL && - filter (it->second)) - { - ident = it->first; - it->second->lastSelectionTime = ts; - found = true; - break; - } - it++; - } - } - } - } + std::advance (it, rand () % m_Peers.size ()); + if (it == m_Peers.end () || it->second.router) return nullptr; // not connected + ident = it->first; } - return found ? i2p::data::netdb.FindRouter (ident) : nullptr; - } - - std::shared_ptr Transports::GetRandomPeer (bool isHighBandwidth) const - { - return GetRandomPeer ( - [isHighBandwidth](std::shared_ptr peer)->bool - { - // connected, not overloaded and not slow - return !peer->router && peer->IsConnected () && peer->isEligible && - peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && - !peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) && - (!isHighBandwidth || peer->isHighBandwidth); - }); + return i2p::data::netdb.FindRouter (ident); } void Transports::RestrictRoutesToFamilies(const std::set& families) @@ -1190,29 +843,22 @@ namespace transport } } - void Transports::RestrictRoutesToRouters(const std::set& routers) + void Transports::RestrictRoutesToRouters(std::set routers) { - std::lock_guard lock(m_TrustedRoutersMutex); + std::unique_lock lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) - m_TrustedRouters.insert(ri); + m_TrustedRouters.push_back(ri); } - 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; + 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; } /** XXX: if routes are not restricted this dies */ - std::shared_ptr Transports::GetRestrictedPeer() + std::shared_ptr Transports::GetRestrictedPeer() const { { std::lock_guard l(m_FamilyMutex); @@ -1221,7 +867,7 @@ namespace transport if(sz > 1) { auto it = m_TrustedFamilies.begin (); - std::advance(it, m_Rng() % sz); + std::advance(it, rand() % sz); fam = *it; } else if (sz == 1) @@ -1232,38 +878,29 @@ namespace transport return i2p::data::netdb.GetRandomRouterInFamily(fam); } { - std::lock_guard l(m_TrustedRoutersMutex); + std::unique_lock 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(); - if(sz > 1) - std::advance(it, m_Rng() % sz); + std::advance(it, rand() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } - bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const + bool Transports::IsRestrictedPeer(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::lock_guard l(m_FamilyMutex); + std::unique_lock l(m_TrustedRoutersMutex); + for (const auto & r : m_TrustedRouters ) + if ( r == ih ) return true; + } + { + std::unique_lock l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); for (const auto & fam : m_TrustedFamilies) if(ri->IsFamily(fam)) return true; @@ -1282,123 +919,5 @@ namespace transport i2p::context.SetError (eRouterErrorOffline); } } - - bool Transports::IsInReservedRange (const boost::asio::ip::address& host) const - { - return IsCheckReserved () && i2p::util::net::IsInReservedRange (host); - } - - void InitAddressFromIface () - { - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - - // ifname -> address - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ipv4 && i2p::config::IsDefault ("address4")) - { - std::string ifname4; i2p::config::GetOption("ifname4", ifname4); - if (!ifname4.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 - else if (!ifname.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 - } - if (ipv6 && i2p::config::IsDefault ("address6")) - { - std::string ifname6; i2p::config::GetOption("ifname6", ifname6); - if (!ifname6.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 - else if (!ifname.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 - } - } - - void InitTransports () - { - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - uint16_t port; i2p::config::GetOption("port", port); - - boost::asio::ip::address_v6 yggaddr; - if (ygg) - { - std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); - if (!yggaddress.empty ()) - { - yggaddr = boost::asio::ip::make_address (yggaddress).to_v6 (); - if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || - !i2p::util::net::IsLocalAddress (yggaddr)) - { - LogPrint(eLogWarning, "Transports: Can't find Yggdrasil address ", yggaddress); - ygg = false; - } - } - else - { - yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (yggaddr.is_unspecified ()) - { - LogPrint(eLogWarning, "Transports: Yggdrasil is not running. Disabled"); - ygg = false; - } - } - } - - if (!i2p::config::IsDefault("port")) - { - LogPrint(eLogInfo, "Transports: Accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); - } - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - i2p::context.SetSupportsMesh (ygg, yggaddr); - - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - if (!ntcp2proxy.empty ()) published = false; - } - if (published) - { - uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); - if (!ntcp2port) ntcp2port = port; // use standard port - i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish - if (ipv6) - { - std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); - auto addr = boost::asio::ip::make_address (ipv6Addr).to_v6 (); - if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) - i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured - } - } - else - i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish - } - if (ygg) - { - i2p::context.PublishNTCP2Address (port, true, false, false, true); - i2p::context.UpdateNTCP2V6Address (yggaddr); - if (!ipv4 && !ipv6) - i2p::context.SetStatus (eRouterStatusMesh); - } - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2 && i2p::config::IsDefault ("ssu2.enabled") && !ipv4 && !ipv6) - ssu2 = false; // don't enable ssu2 for yggdrasil only router - if (ssu2) - { - uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); - if (!ssu2port && port) ssu2port = port; - bool published; i2p::config::GetOption("ssu2.published", published); - if (published) - i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish - else - i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish - } - } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index fcd2cfc6..80d1855e 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,25 +11,22 @@ #include #include -#include #include #include #include -#include #include #include #include #include #include -#include #include #include "TransportSession.h" +#include "SSU.h" #include "SSU2.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" -#include "util.h" namespace i2p { @@ -56,76 +53,33 @@ namespace transport private: const int m_QueueSize; - i2p::util::MemoryPoolMt m_KeysPool; std::queue > m_Queue; bool m_IsRunning; - std::unique_ptr m_Thread; + std::thread * m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; typedef EphemeralKeysSupplier X25519KeysPairSupplier; - const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds - const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds - const 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, lastSelectionTime; - std::list > delayedMessages; - std::vector priority; - bool isHighBandwidth, isEligible; + uint64_t creationTime; + std::vector > delayedMessages; - Peer (std::shared_ptr r, uint64_t ts): - numAttempts (0), router (r), creationTime (ts), - nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL), - lastSelectionTime (0), isHighBandwidth (false), isEligible (false) - { - UpdateParams (router); - } - void Done () { for (auto& it: sessions) it->Done (); - // drop not sent delayed messages - for (auto& it: delayedMessages) - it->Drop (); } - - void SetRouter (std::shared_ptr r) - { - router = r; - UpdateParams (router); - } - - bool IsConnected () const { return !sessions.empty (); } - void UpdateParams (std::shared_ptr router); }; - const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds - const int PEER_TEST_INTERVAL = 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 size_t SESSION_CREATION_TIMEOUT = 15; // in seconds + const int PEER_TEST_INTERVAL = 71; // in minutes 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: @@ -133,22 +87,21 @@ namespace transport Transports (); ~Transports (); - void Start (bool enableNTCP2=true, bool enableSSU2=true); + void Start (bool enableNTCP2=true, bool enableSSU=true, bool enableSSU2=false); void Stop (); - bool IsRunning () const { return m_IsRunning; } - bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } + bool IsBoundSSU() const { return m_SSUServer != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; void SetOnline (bool online); - auto& GetService () { return *m_Service; }; + boost::asio::io_service& GetService () { return *m_Service; }; std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); - std::future > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); - std::future > SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs); + void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); + void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); @@ -163,100 +116,82 @@ namespace transport uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; - uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; }; - uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; }; - uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; }; - int GetCongestionLevel (bool longTerm) const; + bool IsBandwidthExceeded () const; + bool IsTransitBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; - std::shared_ptr GetRandomPeer (bool isHighBandwidth) const; + std::shared_ptr GetRandomPeer () const; /** get a trusted first hop for restricted routes */ - std::shared_ptr GetRestrictedPeer(); + std::shared_ptr GetRestrictedPeer() const; /** 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(const std::set& routers); + void RestrictRoutesToRouters(std::set routers); - bool IsTrustedRouter (const i2p::data::IdentHash& ih) const; - bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const; + bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; void PeerTest (bool ipv4 = true, bool ipv6 = true); void SetCheckReserved (bool check) { m_CheckReserved = check; }; - bool IsCheckReserved () const { return m_CheckReserved; }; - bool IsInReservedRange (const boost::asio::ip::address& host) const; + bool IsCheckReserved () { return m_CheckReserved; }; private: void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); - std::shared_ptr PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs); - bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer); - void SetPriority (std::shared_ptr peer); + void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); + bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); - void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); - void UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit); + void UpdateBandwidth (); void DetectExternalIP (); - template - std::shared_ptr GetRandomPeer (Filter filter) const; - private: volatile bool m_IsOnline; bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; - boost::asio::io_context * m_Service; - boost::asio::executor_work_guard * m_Work; - boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer; + boost::asio::io_service * m_Service; + boost::asio::io_service::work * m_Work; + boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; + SSUServer * m_SSUServer; SSU2Server * m_SSU2Server; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; - std::unordered_map > m_Peers; + std::unordered_map m_Peers; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; - - TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT]; - int m_TrafficSamplePtr; - - // Bandwidth per second - uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; - // Bandwidth during last 15 seconds - uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s; - // Bandwidth during last 5 minutes - uint32_t m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m; + uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second + uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; + uint64_t m_LastBandwidthUpdateTime; /** 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::unordered_set m_TrustedRouters; + std::vector m_TrustedRouters; mutable std::mutex m_TrustedRoutersMutex; i2p::I2NPMessagesHandler m_LoopbackHandler; - std::mt19937 m_Rng; public: // for HTTP only + const SSUServer * GetSSUServer () const { return m_SSUServer; }; const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; const SSU2Server * GetSSU2Server () const { return m_SSU2Server; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; - - void InitAddressFromIface (); - void InitTransports (); } } diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 1347b5b8..b578f6c1 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, 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 (UNKNOWN_LATENCY) + m_IsRecreated (false), m_Latency (0) { } @@ -44,13 +44,7 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - 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; + const 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; @@ -58,7 +52,8 @@ namespace tunnel // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); - std::shuffle (recordIndicies.begin(), recordIndicies.end(), m_Pool ? m_Pool->GetRng () : std::mt19937(std::random_device()())); + std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()())); + // create real records uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); @@ -66,7 +61,7 @@ namespace tunnel while (hop) { uint32_t msgID; - if (hop->next && hop->next->ident) // we set replyMsgID for last non-phony hop only + if (hop->next) // we set replyMsgID for last hop only RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; @@ -94,30 +89,21 @@ 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->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP + if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); if (msg1) msg = msg1; } } - outboundTunnel->SendTunnelDataMsgTo (GetNextIdentHash (), 0, msg); + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); } else { @@ -130,7 +116,7 @@ namespace tunnel if (m_Pool && m_Pool->GetLocalDestination ()) m_Pool->GetLocalDestination ()->SubmitECIESx25519Key (key, tag); else - i2p::context.SubmitECIESx25519Key (key, tag); + i2p::context.AddECIESx25519Key (key, tag); } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } @@ -138,19 +124,8 @@ namespace tunnel bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { - 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; - } - + LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); + TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { @@ -171,7 +146,7 @@ namespace tunnel while (hop1) { auto idx = hop1->recordIndex; - if (idx >= 0 && idx < num) + if (idx >= 0 && idx < msg[0]) hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range"); @@ -187,12 +162,9 @@ namespace tunnel { uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); - if (hop->ident) - i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), - [ret](std::shared_ptr profile) - { - if (profile) profile->TunnelBuildResponse (ret); - }); + auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); + if (profile) + profile->TunnelBuildResponse (ret); if (ret) // if any of participants declined the tunnel is not established established = false; @@ -220,10 +192,10 @@ namespace tunnel return established; } - bool Tunnel::LatencyFitsRange(int lowerbound, int upperbound) const + bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const { auto latency = GetMeanLatency(); - return latency >= lowerbound && latency <= upperbound; + return latency >= lower && latency <= upper; } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) @@ -272,38 +244,12 @@ namespace tunnel void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& msg) { - 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); - } - } + if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive EncryptTunnelMsg (msg, msg); - msg->from = GetSharedFromThis (); + msg->from = shared_from_this (); 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) @@ -315,39 +261,33 @@ namespace tunnel if (msg) { m_NumReceivedBytes += msg->GetLength (); - msg->from = GetSharedFromThis (); + msg->from = shared_from_this (); HandleI2NPMessage (msg); } } - void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) + void OutboundTunnel::SendTunnelDataMsg (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; // Set tunnelID only if gwTunnel is non-zero + block.tunnelID = gwTunnel; } else - { block.deliveryType = eDeliveryTypeRouter; - } } else - { block.deliveryType = eDeliveryTypeLocal; - } - block.data = msg; - SendTunnelDataMsgs({block}); + + SendTunnelDataMsg ({block}); } - void OutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) + void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) @@ -360,28 +300,13 @@ 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::SendTunnelDataMsgs (const std::vector& msgs) + void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { for (auto& msg : msgs) { @@ -406,40 +331,23 @@ namespace tunnel Tunnels tunnels; - 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 (): m_IsRunning (false), m_Thread (nullptr), + m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) { } 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); @@ -481,7 +389,7 @@ namespace tunnel std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; - uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0; + uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { @@ -495,12 +403,10 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, - int numOutboundHops, int numInboundTunnels, int numOutboundTunnels, - int inboundVariance, int outboundVariance, bool isHighBandwidth) + std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, + int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance) { - auto pool = std::make_shared (numInboundHops, numOutboundHops, - numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth); + auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -527,16 +433,22 @@ namespace tunnel } } + void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) + { + if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) + m_TransitTunnels.push_back (tunnel); + else + LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); + } + void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); - m_TransitTunnels.Start (); } void Tunnels::Stop () { - m_TransitTunnels.Stop (); m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) @@ -553,21 +465,17 @@ 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 { - if (m_Queue.Wait (1,0)) // 1 sec + auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec + if (msg) { - m_Queue.GetWholeQueue (msgs); - int numMsgs = 0; uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; - while (!msgs.empty ()) + do { - auto msg = msgs.front (); msgs.pop_front (); - if (!msg) continue; std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) @@ -595,57 +503,44 @@ namespace tunnel break; } - case eI2NPShortTunnelBuild: - HandleShortTunnelBuildMsg (msg); - break; case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msg); - break; - case eI2NPShortTunnelBuildReply: - HandleTunnelBuildReplyMsg (msg, true); - break; case eI2NPVariableTunnelBuildReply: - HandleTunnelBuildReplyMsg (msg, false); - break; + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - LogPrint (eLogWarning, "Tunnel: TunnelBuild is too old for ECIES router"); - break; + HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + break; default: LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } - 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 - } + msg = m_Queue.Get (); + if (msg) + { + prevTunnelID = tunnelID; + prevTunnel = tunnel; + } + else if (tunnel) + tunnel->FlushTunnelDataMsgs (); } + while (msg); } if (i2p::transport::transports.IsOnline()) { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= TUNNEL_MANAGE_INTERVAL || // manage tunnels every 15 seconds - ts + TUNNEL_MANAGE_INTERVAL < lastTs) + if (ts - lastTs >= 15) // manage tunnels every 15 seconds { - ManageTunnels (ts); + ManageTunnels (); lastTs = ts; } - if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL || // manage pools every 5 seconds - ts + TUNNEL_POOLS_MANAGE_INTERVAL < lastPoolsTs) + if (ts - lastPoolsTs >= 5) // manage pools every 5 seconds { ManageTunnelPools (ts); lastPoolsTs = ts; } - if (ts - lastMemoryPoolTs >= TUNNEL_MEMORY_POOL_MANAGE_INTERVAL || - ts + TUNNEL_MEMORY_POOL_MANAGE_INTERVAL < lastMemoryPoolTs) // manage memory pool every 2 minutes + if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes { m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt (); m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); @@ -680,115 +575,39 @@ 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) + void Tunnels::ManageTunnels () { - if (!msg) return; - auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) - { - LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (eTunnelStateEstablished); - AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (eTunnelStateBuildFailed); - } - return; - } - else - m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); - } - - void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr msg) - { - auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) - { - LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (eTunnelStateEstablished); - AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (eTunnelStateBuildFailed); - } - } - else - m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); - } - - void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort) - { - auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID - if (tunnel) - { - // reply for outbound tunnel - LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) - { - LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (eTunnelStateEstablished); - AddOutboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (eTunnelStateBuildFailed); - } - } - else - LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found"); - - } - - void Tunnels::ManageTunnels (uint64_t ts) - { - ManagePendingTunnels (ts); - std::vector > tunnelsToRecreate; - ManageInboundTunnels (ts, tunnelsToRecreate); - ManageOutboundTunnels (ts, tunnelsToRecreate); - // rec-create in random order - if (!tunnelsToRecreate.empty ()) - { - if (tunnelsToRecreate.size () > 1) - std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng); - for (auto& it: tunnelsToRecreate) - it->Recreate (); - } + ManagePendingTunnels (); + ManageInboundTunnels (); + ManageOutboundTunnels (); + ManageTransitTunnels (); } - void Tunnels::ManagePendingTunnels (uint64_t ts) + void Tunnels::ManagePendingTunnels () { - ManagePendingTunnels (m_PendingInboundTunnels, ts); - ManagePendingTunnels (m_PendingOutboundTunnels, ts); + ManagePendingTunnels (m_PendingInboundTunnels); + ManagePendingTunnels (m_PendingOutboundTunnels); } template - void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts) + void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) { // check pending tunnel. delete failed or timeout + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) { case eTunnelStatePending: - if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT || - ts + TUNNEL_CREATION_TIMEOUT < tunnel->GetCreationTime ()) + if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " timeout, deleted"); // update stats @@ -799,17 +618,17 @@ namespace tunnel while (hop) { if (hop->ident) - i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), - [](std::shared_ptr profile) - { - if (profile) profile->TunnelNonReplied (); - }); + { + auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); + if (profile) + profile->TunnelNonReplied (); + } hop = hop->next; } } // delete it = pendingTunnels.erase (it); - FailedTunnelCreation(); + m_NumFailedTunnelCreations++; } else ++it; @@ -817,7 +636,7 @@ namespace tunnel case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); - FailedTunnelCreation(); + m_NumFailedTunnelCreations++; break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed @@ -826,51 +645,56 @@ namespace tunnel default: // success it = pendingTunnels.erase (it); - SuccesiveTunnelCreation(); + m_NumSuccesiveTunnelCreations++; } } } - void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate) + void Tunnels::ManageOutboundTunnels () { - for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { - auto tunnel = *it; - if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || - ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) + for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - // we don't have outbound tunnels in m_Tunnels - it = m_OutboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool has been reconfigured and this is old - if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) - toRecreate.push_back (tunnel); - } - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + // we don't have outbound tunnels in m_Tunnels + it = m_OutboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) + { + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + auto pool = tunnel->GetTunnelPool (); + // let it die if the tunnel pool has been reconfigured and this is old + if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) + { + tunnel->SetRecreated (true); + pool->RecreateOutboundTunnel (tunnel); + } + } + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); + } + ++it; } - ++it; } } if (m_OutboundTunnels.size () < 3) { - // trying to create one more outbound tunnel + // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); // reachable by us if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); CreateTunnel ( @@ -880,39 +704,44 @@ namespace tunnel } } - void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate) + void Tunnels::ManageInboundTunnels () { - for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { - auto tunnel = *it; - if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || - ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) + for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { - LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - RemoveTunnel (tunnel->GetTunnelID ()); - it = m_InboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool was reconfigured and has different number of hops - if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) - toRecreate.push_back (tunnel); - } - - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); - else // we don't need to cleanup expiring tunnels - tunnel->Cleanup (); + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + m_Tunnels.erase (tunnel->GetTunnelID ()); + it = m_InboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) + { + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + auto pool = tunnel->GetTunnelPool (); + // let it die if the tunnel pool was reconfigured and has different number of hops + if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) + { + tunnel->SetRecreated (true); + pool->RecreateInboundTunnel (tunnel); + } + } + + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); + else // we don't need to cleanup expiring tunnels + tunnel->Cleanup (); + } + it++; } - it++; } } @@ -927,7 +756,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, false); + m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; @@ -939,7 +768,7 @@ namespace tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : // should be reachable by us because we send build request directly - i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false); if (!router) { LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); return; @@ -951,6 +780,26 @@ namespace tunnel } } + void Tunnels::ManageTransitTunnels () + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) + { + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); + m_Tunnels.erase (tunnel->GetTunnelID ()); + it = m_TransitTunnels.erase (it); + } + else + { + tunnel->Cleanup (); + it++; + } + } + } + void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); @@ -966,7 +815,7 @@ namespace tunnel if (msg) m_Queue.Put (msg); } - void Tunnels::PostTunnelData (std::list >& msgs) + void Tunnels::PostTunnelData (const std::vector >& msgs) { m_Queue.Put (msgs); } @@ -1024,7 +873,7 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { - if (AddTunnel (newTunnel)) + if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); @@ -1054,7 +903,7 @@ namespace tunnel inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); - AddTunnel (inboundTunnel); + m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } @@ -1088,12 +937,21 @@ namespace tunnel int Tunnels::GetTransitTunnelsExpirationTimeout () { - return m_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; } size_t Tunnels::CountTransitTunnels() const { - return m_TransitTunnels.GetNumTransitTunnels (); + // TODO: locking + return m_TransitTunnels.size(); } size_t Tunnels::CountInboundTunnels() const @@ -1107,14 +965,5 @@ 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 78e2d124..503b7f9c 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,7 +18,6 @@ #include #include #include -#include #include "util.h" #include "Queue.h" #include "Crypto.h" @@ -40,20 +39,11 @@ 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 UNKNOWN_LATENCY = -1; - const int HIGH_LATENCY_PER_HOP = 250000; // in microseconds - const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt - const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000; - const int TUNNEL_MANAGE_INTERVAL = 15; // in seconds - const int TUNNEL_POOLS_MANAGE_INTERVAL = 5; // in seconds - const int TUNNEL_MEMORY_POOL_MANAGE_INTERVAL = 120; // in seconds + const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12 const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6 - const double TCSR_SMOOTHING_CONSTANT = 0.0005; // smoothing constant in exponentially weighted moving average - const double TCSR_START_VALUE = 0.1; // start value of tunnel creation success rate - enum TunnelState { eTunnelStatePending, @@ -67,8 +57,7 @@ namespace tunnel class OutboundTunnel; class InboundTunnel; - class Tunnel: public TunnelBase, - public std::enable_shared_from_this + class Tunnel: public TunnelBase { struct TunnelHop { @@ -81,25 +70,24 @@ 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 || m_State == eTunnelStateTestFailed; }; + bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; 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; }; @@ -107,51 +95,50 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg) override; - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; + void SendTunnelDataMsg (std::shared_ptr msg); + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); /** @brief add latency sample */ - void AddLatencySample(const int us) { m_Latency = LatencyIsKnown() ? (m_Latency + us) >> 1 : us; } + void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; } /** @brief get this tunnel's estimated latency */ - int GetMeanLatency() const { return (m_Latency + 500) / 1000; } + uint64_t GetMeanLatency() const { return m_Latency; } /** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */ - bool LatencyFitsRange(int lowerbound, int upperbound) const; + bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; - bool LatencyIsKnown() const { return m_Latency != UNKNOWN_LATENCY; } - bool IsSlow () const { return LatencyIsKnown() && m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } + bool LatencyIsKnown() const { return m_Latency > 0; } + bool IsSlow () const { return LatencyIsKnown() && (int)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 - int m_Latency; // in microseconds + uint64_t m_Latency; // in milliseconds }; 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 SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); - virtual void SendTunnelDataMsgs (const std::vector& msgs); // multiple messages + void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); + virtual void SendTunnelDataMsg (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) override; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); - bool IsInbound() const override { return false; } - bool Recreate () override; + bool IsInbound() const { return false; } private: @@ -160,26 +147,18 @@ namespace tunnel i2p::data::IdentHash m_EndpointIdentHash; }; - class InboundTunnel: public Tunnel + class InboundTunnel: public Tunnel, public std::enable_shared_from_this { public: - InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr&& msg) override; + InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; + void HandleTunnelDataMsg (std::shared_ptr&& msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - bool IsInbound() const override { return true; } - bool Recreate () override; + bool IsInbound() const { return true; } // override TunnelBase - void Cleanup () override { m_Endpoint.Cleanup (); }; + void Cleanup () { m_Endpoint.Cleanup (); }; - protected: - - std::shared_ptr GetSharedFromThis () - { - return std::static_pointer_cast(shared_from_this ()); - } - private: TunnelEndpoint m_Endpoint; @@ -190,8 +169,8 @@ namespace tunnel public: ZeroHopsInboundTunnel (); - void SendTunnelDataMsg (std::shared_ptr msg) override; - size_t GetNumReceivedBytes () const override { return m_NumReceivedBytes; }; + void SendTunnelDataMsg (std::shared_ptr msg); + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; private: @@ -203,8 +182,8 @@ namespace tunnel public: ZeroHopsOutboundTunnel (); - void SendTunnelDataMsgs (const std::vector& msgs) override; - size_t GetNumSentBytes () const override { return m_NumSentBytes; }; + void SendTunnelDataMsg (const std::vector& msgs); + size_t GetNumSentBytes () const { return m_NumSentBytes; }; private: @@ -226,29 +205,23 @@ namespace tunnel std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); - bool AddTunnel (std::shared_ptr tunnel); - void RemoveTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); + void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); - void PostTunnelData (std::list >& msgs); // and cleanup msgs + void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); - std::shared_ptr CreateTunnelPool (int numInboundHops, - int numOuboundHops, int numInboundTunnels, int numOutboundTunnels, - int inboundVariance, int outboundVariance, bool isHighBandwidth); + std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, + int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); std::shared_ptr NewI2NPTunnelMessage (bool endpoint); - void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels); - uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; - int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } - private: template @@ -259,83 +232,56 @@ 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, std::vector >& toRecreate); - void ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate); - void ManagePendingTunnels (uint64_t ts); + void ManageTunnels (); + void ManageOutboundTunnels (); + void ManageInboundTunnels (); + void ManageTransitTunnels (); + void ManagePendingTunnels (); template - void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); + void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (uint64_t ts); std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); - // Calculating of tunnel creation success rate - void SuccesiveTunnelCreation() - { - // total TCSR - m_TotalNumSuccesiveTunnelCreations++; - // A modified version of the EWMA algorithm, where alpha is increased at the beginning to accelerate similarity - double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; - m_TunnelCreationSuccessRate = alpha * 1 + (1 - alpha) * m_TunnelCreationSuccessRate; - - } - void FailedTunnelCreation() - { - m_TotalNumFailedTunnelCreations++; - - double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; - m_TunnelCreationSuccessRate = alpha * 0 + (1 - alpha) * m_TunnelCreationSuccessRate; - } - private: bool m_IsRunning; std::thread * m_Thread; - i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; - i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; - mutable std::mutex m_TunnelsMutex; + std::list > m_TransitTunnels; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id - mutable std::mutex m_PoolsMutex; + std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - 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; - + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; + + // some stats + int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; + public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; - const auto& GetTransitTunnels () const { return m_TransitTunnels.GetTransitTunnels (); }; + const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; size_t CountTransitTunnels() const; size_t CountInboundTunnels() const; size_t CountOutboundTunnels() const; - 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 GetQueueSize () { return m_Queue.GetSize (); }; + int GetTunnelCreationSuccessRate () const // in percents { - int totalNum = m_TotalNumSuccesiveTunnelCreations + m_TotalNumFailedTunnelCreations; - return totalNum ? m_TotalNumSuccesiveTunnelCreations*100/totalNum : 0; + int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; + return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; } }; diff --git a/libi2pd/TunnelBase.cpp b/libi2pd/TunnelBase.cpp deleted file mode 100644 index b5a4a0b3..00000000 --- a/libi2pd/TunnelBase.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -* 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 39d6e780..8d0edff1 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,19 +11,12 @@ #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; @@ -48,7 +41,7 @@ namespace tunnel { public: - TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent): + TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; @@ -83,25 +76,6 @@ 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 1ae8f3e9..e19b515d 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -79,7 +79,8 @@ namespace tunnel uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record); + decryption.SetIV (replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) @@ -219,42 +220,6 @@ 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; @@ -281,35 +246,5 @@ 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 a024f359..9dcf2c02 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -77,35 +77,28 @@ 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 + 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 ()); + } 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 - + 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); + } + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; @@ -188,11 +181,6 @@ 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 66b7effa..9c294374 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,10 @@ namespace i2p { namespace tunnel { - + TunnelEndpoint::~TunnelEndpoint () + { + } + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; @@ -205,7 +208,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 (msg.data->len + size); + auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } @@ -258,8 +261,9 @@ namespace tunnel void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size) { - if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, - isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second) + 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) LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -289,17 +293,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 (msg.data->len + size); + auto newMsg = NewI2NPMessage (); *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 @@ -319,7 +323,11 @@ 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: @@ -327,13 +335,13 @@ namespace tunnel break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel - SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); + i2p::transport::transports.SendMessage (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); // send right away, because most likely it's single message + i2p::transport::transports.SendMessage (msg.hash, msg.data); else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; @@ -348,7 +356,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; @@ -362,35 +370,5 @@ 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 1e81c445..17590a5f 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,10 +11,8 @@ #include #include -#include #include #include -#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -22,7 +20,7 @@ namespace i2p { namespace tunnel { - class TunnelEndpoint final + class TunnelEndpoint { struct TunnelMessageBlockEx: public TunnelMessageBlock { @@ -32,8 +30,7 @@ namespace tunnel struct Fragment { - Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): - isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); }; + Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; bool isLastFragment; uint64_t receiveTime; // milliseconds since epoch std::vector data; @@ -42,23 +39,18 @@ namespace tunnel public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; - ~TunnelEndpoint () = default; + ~TunnelEndpoint (); 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 @@ -68,15 +60,11 @@ 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 9e27d207..12e7652f 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -35,13 +35,6 @@ 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; } @@ -162,6 +155,7 @@ 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; @@ -220,24 +214,20 @@ namespace tunnel void TunnelGateway::SendBuffer () { - // create list or tunnel messages m_Buffer.CompleteCurrentTunnelDataMessage (); - std::list > newTunnelMsgs; + std::vector > 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 (); - // send - if (!m_Sender) m_Sender = std::make_unique(); - m_Sender->SendMessagesTo (m_Tunnel.GetNextIdentHash (), std::move (newTunnelMsgs)); + i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs); } } } diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h index 75f27581..741bbe84 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -45,20 +45,18 @@ 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 26367aa6..204ac294 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, 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,12 +41,11 @@ namespace tunnel } TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth): + int numOutboundTunnels, int inboundVariance, int outboundVariance): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance), - m_IsActive (true), m_IsHighBandwidth (isHighBandwidth), m_CustomPeerSelector(nullptr), - m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) + m_IsActive (true), m_CustomPeerSelector(nullptr) { if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY; @@ -60,7 +59,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 () + m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL; + m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL; } TunnelPool::~TunnelPool () @@ -77,12 +76,12 @@ namespace tunnel if (m_NumInboundHops > size) { m_NumInboundHops = size; - LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has been adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; - LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has been adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; @@ -103,10 +102,7 @@ namespace tunnel it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } - { - std::unique_lock l(m_TestsMutex); - m_Tests.clear (); - } + m_Tests.clear (); } bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) @@ -141,7 +137,7 @@ namespace tunnel m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) - m_LocalDestination->SetLeaseSetUpdated (true); + m_LocalDestination->SetLeaseSetUpdated (); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -149,11 +145,8 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - { - std::unique_lock l(m_TestsMutex); - for (auto& it: m_Tests) - if (it.second.second == expiredTunnel) it.second.second = nullptr; - } + for (auto& it: m_Tests) + if (it.second.second == expiredTunnel) it.second.second = nullptr; std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); @@ -174,11 +167,8 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - { - std::unique_lock l(m_TestsMutex); - for (auto& it: m_Tests) - if (it.second.first == expiredTunnel) it.second.first = nullptr; - } + for (auto& it: m_Tests) + if (it.second.first == expiredTunnel) it.second.first = nullptr; std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); @@ -211,14 +201,14 @@ namespace tunnel } std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, - i2p::data::RouterInfo::CompatibleTransports compatible) + i2p::data::RouterInfo::CompatibleTransports compatible) const { 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) + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded, compatible); @@ -226,10 +216,10 @@ namespace tunnel template typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const { if (tunnels.empty ()) return nullptr; - uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; + uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; bool skipped = false; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) @@ -249,7 +239,7 @@ namespace tunnel } if (!tunnel && skipped) { - ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; + ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) @@ -264,11 +254,10 @@ namespace tunnel return tunnel; } - std::pair, bool> TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) + std::shared_ptr TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) const { - if (old && old->IsEstablished ()) return std::make_pair(old, false); + if (old && old->IsEstablished ()) return old; std::shared_ptr tunnel; - bool freshTunnel = false; if (old) { std::unique_lock l(m_OutboundTunnelsMutex); @@ -281,11 +270,8 @@ namespace tunnel } if (!tunnel) - { tunnel = GetNextOutboundTunnel (); - freshTunnel = true; - } - return std::make_pair(tunnel, freshTunnel); + return tunnel; } void TunnelPool::CreateTunnels () @@ -296,13 +282,8 @@ namespace tunnel for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } - num = m_NumOutboundTunnels - num; - if (num > 0) - { - if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS; - for (int i = 0; i < num; i++) - CreateOutboundTunnel (); - } + for (int i = num; i < m_NumOutboundTunnels; i++) + CreateOutboundTunnel (); num = 0; { @@ -310,27 +291,20 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0 && - m_NumInboundHops == m_NumOutboundHops) + if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { 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; } } - num = m_NumInboundTunnels - num; - if (num > 0) - { - if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS; - for (int i = 0; i < num; i++) - CreateInboundTunnel (); - } + for (int i = num; i < m_NumInboundTunnels; i++) + CreateInboundTunnel (); if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB - m_LocalDestination->SetLeaseSetUpdated (true); // update LeaseSet immediately + m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately } void TunnelPool::TestTunnels () @@ -351,15 +325,9 @@ namespace tunnel { it.second.first->SetState (eTunnelStateFailed); std::unique_lock l(m_OutboundTunnelsMutex); - 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 - } + m_OutboundTunnels.erase (it.second.first); } - else if (it.second.first->GetState () != eTunnelStateExpiring) + else it.second.first->SetState (eTunnelStateTestFailed); } if (it.second.second) @@ -368,100 +336,48 @@ namespace tunnel { it.second.second->SetState (eTunnelStateFailed); { - 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); + std::unique_lock l(m_InboundTunnelsMutex); + m_InboundTunnels.erase (it.second.second); } if (m_LocalDestination) - m_LocalDestination->SetLeaseSetUpdated (true); + m_LocalDestination->SetLeaseSetUpdated (); } - else if (it.second.second->GetState () != eTunnelStateExpiring) + else it.second.second->SetState (eTunnelStateTestFailed); } } // new tests - if (!m_LocalDestination) return; - std::vector, std::shared_ptr > > newTests; - std::vector > outboundTunnels; + 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 ()) { - 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); + bool failed = false; + if ((*it1)->IsFailed ()) { - std::unique_lock l(m_TestsMutex); - m_Tests[msgID] = it; + failed = true; + ++it1; } - auto msg = CreateTunnelTestMsg (msgID); - auto outbound = it.first; - auto s = shared_from_this (); - msg->onDrop = [msgID, outbound, s]() + if ((*it2)->IsFailed ()) + { + failed = true; + ++it2; + } + if (!failed) + { + uint32_t msgID; + RAND_bytes ((uint8_t *)&msgID, 4); { - // 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); + std::unique_lock l(m_TestsMutex); + m_Tests[msgID] = std::make_pair (*it1, *it2); + } + (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), + CreateDeliveryStatusMsg (msgID)); + ++it1; ++it2; } - 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) @@ -470,7 +386,7 @@ namespace tunnel { CreateTunnels (); TestTunnels (); - m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL)/2; + m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2; } } @@ -483,25 +399,12 @@ 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; { @@ -516,68 +419,58 @@ namespace tunnel } if (found) { - 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 + uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; + LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " milliseconds"); 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 () != eTunnelStateExpiring) + if (test.first->GetState () == eTunnelStateTestFailed) test.first->SetState (eTunnelStateEstablished); // update latency - int latency = 0; + uint64_t 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 () != eTunnelStateExpiring) + if (test.second->GetState () == eTunnelStateTestFailed) test.second->SetState (eTunnelStateEstablished); // update latency - int latency = 0; + uint64_t latency = 0; if (numHops) latency = dlt*test.second->GetNumHops ()/numHops; if (!latency) latency = dlt/2; - test.second->AddLatencySample (latency); + test.second->AddLatencySample(latency); } } - return found; - } - + else + { + if (m_LocalDestination) + m_LocalDestination->ProcessDeliveryStatusMessage (msg); + else + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); + } + } + bool TunnelPool::IsExploratory () const { return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); } - std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, - bool reverse, bool endpoint) const + std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, bool reverse) const { - 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; - } + 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); return hop; } - bool TunnelPool::StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); @@ -593,9 +486,9 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - 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 + auto r = i2p::transport::transports.GetRandomPeer (); + if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && + (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { prevHop = r; path.Add (r); @@ -605,11 +498,11 @@ namespace tunnel for(int i = start; i < numHops; i++ ) { - auto hop = nextHop (prevHop, inbound, i == numHops - 1); + auto hop = nextHop (prevHop, inbound); if (!hop && !i) // if no suitable peer found for first hop, try already connected { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); - hop = i2p::transport::transports.GetRandomPeer (false); + hop = i2p::transport::transports.GetRandomPeer (); if (hop && !hop->IsECIES ()) hop = nullptr; } if (!hop) @@ -617,6 +510,12 @@ 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); } @@ -635,7 +534,7 @@ namespace tunnel numHops = m_NumInboundHops; if (m_InboundVariance) { - int offset = m_Rng () % (std::abs (m_InboundVariance) + 1); + int offset = rand () % (std::abs (m_InboundVariance) + 1); if (m_InboundVariance < 0) offset = -offset; numHops += offset; } @@ -645,7 +544,7 @@ namespace tunnel numHops = m_NumOutboundHops; if (m_OutboundVariance) { - int offset = m_Rng () % (std::abs (m_OutboundVariance) + 1); + int offset = rand () % (std::abs (m_OutboundVariance) + 1); if (m_OutboundVariance < 0) offset = -offset; numHops += offset; } @@ -658,15 +557,14 @@ 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, std::placeholders::_3)); + return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } 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]; @@ -730,13 +628,8 @@ namespace tunnel outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; - 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 > 0 && tunnel->GetPeers().size()) + config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); if (!m_NumInboundHops || config) { auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); @@ -800,12 +693,10 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); std::shared_ptr config; - if (m_NumOutboundHops > 0) + if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) { - auto peers = tunnel->GetPeers(); - if (peers.size () && ValidatePeers (peers)) - config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); + config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); } if (!m_NumOutboundHops || config) { @@ -846,26 +737,11 @@ 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); - int min = 1000000; + uint64_t min = 1000000; for (const auto & itr : m_InboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); @@ -881,7 +757,7 @@ namespace tunnel { std::shared_ptr tun = nullptr; std::unique_lock lock(m_OutboundTunnelsMutex); - int min = 1000000; + uint64_t 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 0ebfd1ac..d8c60d69 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,7 +15,6 @@ #include #include #include -#include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" @@ -31,8 +30,6 @@ namespace tunnel const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds const int TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY = 16; const int TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY = 16; - const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 3; - const int TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS = 3; class Tunnel; class InboundTunnel; @@ -56,13 +53,16 @@ namespace tunnel virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; }; + + typedef std::function(std::shared_ptr, bool)> SelectHopFunc; + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); + class TunnelPool: public std::enable_shared_from_this // per local destination { - typedef std::function(std::shared_ptr, bool, bool)> SelectHopFunc; public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth); + int numOutboundTunnels, int inboundVariance, int outboundVariance); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -78,15 +78,13 @@ 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); + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr, - i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports); - std::pair, bool> GetNewOutboundTunnel (std::shared_ptr old); + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; + std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; 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; }; @@ -106,7 +104,7 @@ namespace tunnel bool HasCustomPeerSelector(); /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ - void RequireLatency(int min, int max) { m_MinLatency = min; m_MaxLatency = max; } + void RequireLatency(uint64_t min, uint64_t 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; } @@ -116,11 +114,8 @@ 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, bool endpoint) const; - bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); + std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse) const; - std::mt19937& GetRng () { return m_Rng; } - private: void TestTunnels (); @@ -129,10 +124,9 @@ 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); + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const; bool SelectPeers (Path& path, bool isInbound); bool SelectExplicitPeers (Path& path, bool isInbound); - bool ValidatePeers (std::vector >& peers) const; private: @@ -146,16 +140,14 @@ namespace tunnel std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; - bool m_IsActive, m_IsHighBandwidth; + bool m_IsActive; uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; - 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 + 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 - std::mt19937 m_Rng; - public: // for HTTP only diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 7dc11157..905fab75 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, 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); - i2p::crypto::InitCrypto (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); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); - bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); - i2p::transport::transports.SetCheckReserved(checkReserved); - i2p::context.Init (); } @@ -60,22 +60,17 @@ namespace api else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); - i2p::transport::InitTransports (); LogPrint(eLogInfo, "API: Starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: Starting Transports"); i2p::transport::transports.Start(); LogPrint(eLogInfo, "API: Starting Tunnels"); i2p::tunnel::tunnels.Start(); - LogPrint(eLogInfo, "API: Starting Router context"); - i2p::context.Start(); } void StopI2P () { LogPrint(eLogInfo, "API: Shutting down"); - LogPrint(eLogInfo, "API: Stopping Router context"); - i2p::context.Stop(); LogPrint(eLogInfo, "API: Stopping Tunnels"); i2p::tunnel::tunnels.Stop(); LogPrint(eLogInfo, "API: Stopping Transports"); diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 925cf629..07681dbd 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,15 +8,12 @@ #include #include -#include -#include #include #include "util.h" #include "Log.h" -#include "I2PEndian.h" -#if !defined (__FreeBSD__) && !defined(_MSC_VER) +#if not defined (__FreeBSD__) #include #endif @@ -24,20 +21,6 @@ #include #endif -#if defined(__APPLE__) -# include -#endif - -#if defined(__HAIKU__) -#include -#include -#include -#include -#ifndef _DEFAULT_SOURCE -#define _DEFAULT_SOURCE -#include -#endif -#endif #ifdef _WIN32 #include @@ -49,23 +32,10 @@ #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)) -// inet_pton and inet_ntop have been in Windows since Vista, but XP doesn't have these functions! +// inet_pton exists Windows since Vista, but XP doesn't have that function! // This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found int inet_pton_xp (int af, const char *src, void *dst) { @@ -91,30 +61,6 @@ int inet_pton_xp (int af, const char *src, void *dst) } return 0; } - -const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) -{ - struct sockaddr_storage ss; - unsigned long s = size; - - ZeroMemory(&ss, sizeof(ss)); - ss.ss_family = af; - - switch (af) - { - case AF_INET: - ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; - break; - case AF_INET6: - ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; - break; - default: - return NULL; - } - /* cannot directly use &size because of strict aliasing rules */ - return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)? dst : NULL; -} - #else /* !_WIN32 => UNIX */ #include #ifdef ANDROID @@ -124,8 +70,8 @@ const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) #endif #endif -#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 () } +#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 () } namespace i2p { @@ -172,64 +118,48 @@ 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) || \ - (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) || \ - defined(__POWERPC__)) - /* pthread_setname_np is not there on <10.6 and all PPC. - So do nothing. */ -# else +#if defined(__APPLE__) && !defined(__powerpc__) pthread_setname_np((char*)name); -# endif #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); #elif !defined(__gnu_hurd__) - #if defined(_MSC_VER) - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = -1; - info.dwFlags = 0; - #pragma warning(push) - #pragma warning(disable: 6320 6322) - __try { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) { - } - #pragma warning(pop) - #else pthread_setname_np(pthread_self(), name); - #endif #endif } namespace net { #ifdef _WIN32 + bool IsWindowsXPorLater () + { + static bool isRequested = false; + static bool isXP = false; + if (!isRequested) + { + // request + OSVERSIONINFO osvi; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + + isXP = osvi.dwMajorVersion <= 5; + isRequested = true; + } + return isXP; + } + int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { - typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); - 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; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; 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); @@ -240,33 +170,30 @@ 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"); + LogPrint(eLogError, "NetIface: GetMTU(): Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } pCurrAddresses = pAddresses; - while (pCurrAddresses) + while(pCurrAddresses) { - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - while (pUnicast != nullptr) + pUnicast = pCurrAddresses->FirstUnicastAddress; + if(pUnicast == nullptr) + LogPrint(eLogError, "NetIface: GetMTU(): Not a unicast IPv4 address, this is not supported"); + + for(int i = 0; pUnicast != nullptr; ++i) { 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); - - auto result = pCurrAddresses->Mtu; + auto result = pAddresses->Mtu; FREE(pAddresses); - pAddresses = nullptr; - LogPrint(eLogInfo, "NetIface: GetMTU: Using ", result, " bytes for IPv4 address ", addr); return result; } pUnicast = pUnicast->Next; @@ -274,23 +201,19 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv4 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU(): No usable unicast IPv4 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { - typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); - 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; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; 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); @@ -301,22 +224,23 @@ namespace net AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if (dwRetVal != NO_ERROR) + if(dwRetVal != NO_ERROR) { - LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); + LogPrint(eLogError, "NetIface: GetMTU(): Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; - while (pCurrAddresses) + 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"); + if(pUnicast == nullptr) + LogPrint(eLogError, "NetIface: GetMTU(): Not a unicast IPv6 address, this is not supported"); - while (pUnicast != nullptr) + for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -331,13 +255,9 @@ namespace net if (found_address) { - char addr[INET6_ADDRSTRLEN]; - inetntop(AF_INET6, &(((struct sockaddr_in6 *)localInterfaceAddress)->sin6_addr), addr, INET6_ADDRSTRLEN); - - auto result = pCurrAddresses->Mtu; + auto result = pAddresses->Mtu; FREE(pAddresses); pAddresses = nullptr; - LogPrint(eLogInfo, "NetIface: GetMTU: Using ", result, " bytes for IPv6 address ", addr); return result; } pUnicast = pUnicast->Next; @@ -346,7 +266,7 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv6 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU(): No usable unicast IPv6 addresses found"); FREE(pAddresses); return fallback; } @@ -361,16 +281,16 @@ namespace net #endif typedef int (* IPN)(int af, const char *src, void *dst); - IPN inetpton = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); + IPN inetpton = (IPN)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)); @@ -378,7 +298,7 @@ namespace net } else { - LogPrint(eLogError, "NetIface: GetMTU: Address family is not supported"); + LogPrint(eLogError, "NetIface: GetMTU(): Address family is not supported"); return fallback; } } @@ -386,7 +306,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; @@ -394,34 +314,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)); @@ -454,10 +374,10 @@ namespace net { #ifdef _WIN32 LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32"); - if (ipv6) - return boost::asio::ip::make_address("::1"); + if(ipv6) + return boost::asio::ip::address::from_string("::1"); else - return boost::asio::ip::make_address("127.0.0.1"); + return boost::asio::ip::address::from_string("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs *addrs; @@ -473,13 +393,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::make_address(cur_ifaddr); + return boost::asio::ip::address::from_string(cur_ifaddr); } } } @@ -489,9 +409,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); @@ -499,31 +419,10 @@ namespace net fallback = "127.0.0.1"; LogPrint(eLogWarning, "NetIface: Cannot find IPv4 address for interface ", ifname); } - return boost::asio::ip::make_address(fallback); + return boost::asio::ip::address::from_string(fallback); #endif } - int GetMaxMTU (const boost::asio::ip::address_v6& localAddress) - { - uint32_t prefix = bufbe32toh (localAddress.to_bytes ().data ()); - switch (prefix) - { - case 0x20010470: - case 0x260070ff: - // Hurricane Electric - return 1480; - break; - case 0x2a06a003: - case 0x2a06a004: - case 0x2a06a005: - // route48 - return 1420; - break; - default: ; - } - return 1500; - } - static bool IsYggdrasilAddress (const uint8_t addr[16]) { return addr[0] == 0x02 || addr[0] == 0x03; @@ -535,22 +434,6 @@ namespace net return IsYggdrasilAddress (addr.to_v6 ().to_bytes ().data ()); } - bool IsPortInReservedRange (const uint16_t port) noexcept - { - // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers (Feb. 3, 2023) + Tor browser (9150) - static const std::unordered_set reservedPorts{ - 9119,9150,9306,9312,9389,9418,9535,9536,9695, - 9800,9899,10000,10050,10051,10110,10212, - 10933,11001,11112,11235,11371,12222,12223, - 13075,13400,13720,13721,13724,13782,13783, - 13785,13786,15345,17224,17225,17500,18104, - 19788,19812,19813,19814,19999,20000,24465, - 24554,26000,27000,27001,27002,27003,27004, - 27005,27006,27007,27008,27009,28000}; - - return (reservedPorts.find(port) != reservedPorts.end()); - } - boost::asio::ip::address_v6 GetYggdrasilAddress () { #if defined(_WIN32) @@ -559,7 +442,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); @@ -570,7 +453,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); @@ -578,11 +461,12 @@ namespace net } pCurrAddresses = pAddresses; - while (pCurrAddresses) + while(pCurrAddresses) { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; - while (pUnicast != nullptr) + for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -626,7 +510,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 } @@ -646,10 +530,9 @@ 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::array, 14> reservedIPv4Ranges - { + static const std::vector< std::pair > 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"), @@ -666,17 +549,15 @@ namespace net address_pair_v4("224.0.0.0", "255.255.255.255") }; - uint32_t ipv4_address = host.to_v4 ().to_uint (); - for (const auto& it : reservedIPv4Ranges) { + uint32_t ipv4_address = host.to_v4 ().to_ulong (); + 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::array, 7> reservedIPv6Ranges - { - address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64 + static const std::vector< std::pair > reservedIPv6Ranges { 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"), @@ -686,7 +567,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 7bd35e67..2c74d4dd 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -112,7 +112,7 @@ namespace util }; template - class MemoryPoolMt: private MemoryPool + class MemoryPoolMt: public MemoryPool { public: @@ -131,14 +131,6 @@ 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) { @@ -177,14 +169,12 @@ namespace util RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {} virtual ~RunnableService () {} - auto& GetIOService () { return m_Service; } + boost::asio::io_service& GetIOService () { return m_Service; } bool IsRunning () const { return m_IsRunning; }; void StartIOService (); void StopIOService (); - void SetName (std::string_view name); - private: void Run (); @@ -194,7 +184,7 @@ namespace util std::string m_Name; volatile bool m_IsRunning; std::unique_ptr m_Thread; - boost::asio::io_context m_Service; + boost::asio::io_service m_Service; }; class RunnableServiceWithWork: public RunnableService @@ -202,11 +192,11 @@ namespace util protected: RunnableServiceWithWork (const std::string& name): - RunnableService (name), m_Work (GetIOService ().get_executor ()) {} + RunnableService (name), m_Work (GetIOService ()) {} private: - boost::asio::executor_work_guard m_Work; + boost::asio::io_service::work m_Work; }; void SetThreadName (const char *name); @@ -228,13 +218,11 @@ namespace util namespace net { int GetMTU (const boost::asio::ip::address& localAddress); - int GetMaxMTU (const boost::asio::ip::address_v6& localAddress); // check tunnel broker for ipv6 address const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); bool IsLocalAddress (const boost::asio::ip::address& addr); bool IsInReservedRange (const boost::asio::ip::address& host); bool IsYggdrasilAddress (const boost::asio::ip::address& addr); - bool IsPortInReservedRange (const uint16_t port) noexcept; } } } diff --git a/libi2pd/version.h b/libi2pd/version.h index f88133e4..02ce472e 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,18 +11,16 @@ #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 58 +#define I2PD_VERSION_MINOR 42 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #ifdef GITVER - #define I2PD_VERSION XSTRINGIZE(GITVER) + #define I2PD_VERSION GITVER #else #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #endif @@ -33,7 +31,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 67 +#define I2P_VERSION_MICRO 54 #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 c7cd80c0..4f80f4c4 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, 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,14 +27,6 @@ #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 @@ -50,23 +42,22 @@ namespace client if (m_IsPersist) i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); } - 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; + std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; + void AddAddress (std::shared_ptr address); + void RemoveAddress (const i2p::data::IdentHash& ident); - bool Init () override; - int Load (Addresses& addresses) override; - int LoadLocal (Addresses& addresses) override; - int Save (const Addresses& addresses) 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 (); - 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, Addresses& addresses); // returns -1 if can't open file, otherwise number of records + int LoadFromFile (const std::string& filename, std::map >& addresses); // returns -1 if can't open file, otherwise number of records private: @@ -74,8 +65,6 @@ 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() @@ -96,19 +85,8 @@ namespace client return false; } - std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - 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"); @@ -116,72 +94,48 @@ 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); - 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); + uint8_t * buf = new uint8_t[len]; + f.read((char *)buf, len); + auto address = std::make_shared(buf, len); + delete[] buf; + return address; } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - if (!address) return; - size_t len = address->GetFullLen (); - 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) 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 (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); - } + size_t len = address->GetFullLen (); + uint8_t * buf = new uint8_t[len]; + address->ToBuffer (buf, len); + f.write ((char *)buf, len); + delete[] buf; } 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, Addresses& addresses) + int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map >& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode @@ -207,7 +161,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Load (Addresses& addresses) + int AddressBookFilesystemStorage::Load (std::map >& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) @@ -221,7 +175,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) + int AddressBookFilesystemStorage::LoadLocal (std::map >& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; @@ -229,7 +183,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Save (const Addresses& addresses) + int AddressBookFilesystemStorage::Save (const std::map >& addresses) { if (addresses.empty()) { @@ -312,30 +266,17 @@ namespace client void AddressBookFilesystemStorage::ResetEtags () { LogPrint (eLogError, "Addressbook: Resetting eTags"); - for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) + for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it) { - if (!fs_lib::is_regular_file (it->status ())) + if (!boost::filesystem::is_regular_file (it->status ())) continue; - fs_lib::remove (it->path ()); + boost::filesystem::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 (std::string_view b32): + Address::Address (const std::string& b32): addressType (eAddressInvalid) { if (b32.length () <= B33_ADDRESS_THRESHOLD) @@ -357,9 +298,8 @@ namespace client identHash = hash; } - AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), - m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr), - m_IsEnabled (true) + AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), + m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } @@ -370,17 +310,12 @@ namespace client void AddressBook::Start () { - i2p::config::GetOption("addressbook.enabled", m_IsEnabled); - if (m_IsEnabled) - { - if (!m_Storage) - m_Storage = new AddressBookFilesystemStorage; - m_Storage->Init(); - LoadHosts (); /* try storage, then hosts.txt, then download */ - StartSubscriptions (); - StartLookups (); - ScheduleCacheUpdate (); - } + if (!m_Storage) + m_Storage = new AddressBookFilesystemStorage; + m_Storage->Init(); + LoadHosts (); /* try storage, then hosts.txt, then download */ + StartSubscriptions (); + StartLookups (); } void AddressBook::StartResolvers () @@ -394,36 +329,23 @@ namespace client StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { - m_SubscriptionsUpdateTimer->cancel (); + delete m_SubscriptionsUpdateTimer; m_SubscriptionsUpdateTimer = nullptr; } - if (m_AddressCacheUpdateTimer) + 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 are downloading, abort"); + for (int i = 0; i < 30; i++) + { + if (!m_IsDownloading) { - 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; - } + LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); + break; } - } - if (!isDownloading) - m_Downloading.get (); - else - LogPrint (eLogError, "Addressbook: Subscription download timeout"); + std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds + } + LogPrint (eLogError, "Addressbook: Subscription download timeout"); + m_IsDownloading = false; } if (m_Storage) { @@ -435,7 +357,7 @@ namespace client m_Subscriptions.clear (); } - std::shared_ptr AddressBook::GetAddress (std::string_view address) + std::shared_ptr AddressBook::GetAddress (const std::string& address) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) @@ -443,18 +365,16 @@ namespace client auto addr = std::make_shared(address.substr (0, pos)); return addr->IsValid () ? addr : nullptr; } - else -#if __cplusplus >= 202002L // C++20 - if (address.ends_with (".i2p")) -#else - if (address.find (".i2p") != std::string::npos) -#endif + else { - if (!m_IsEnabled) return nullptr; - auto addr = FindAddress (address); - if (!addr) - LookupAddress (address); // TODO: - return addr; + pos = address.find (".i2p"); + if (pos != std::string::npos) + { + auto addr = FindAddress (address); + if (!addr) + LookupAddress (address); // TODO: + return addr; + } } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; @@ -463,7 +383,7 @@ namespace client return std::make_shared(dest.GetIdentHash ()); } - std::shared_ptr AddressBook::FindAddress (std::string_view address) + std::shared_ptr AddressBook::FindAddress (const std::string& address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) @@ -471,29 +391,6 @@ namespace client return nullptr; } - bool AddressBook::RecordExists (const std::string& address, const std::string& jump) - { - auto addr = FindAddress(address); - if (!addr) - return false; - - auto pos = jump.find(".b32.i2p"); - if (pos != std::string::npos) - { - i2p::data::IdentHash identHash; - if (identHash.FromBase32(jump.substr (0, pos)) && identHash == addr->identHash) - return true; - } - else - { - i2p::data::IdentityEx ident; - if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) - return true; - } - - return false; - } - void AddressBook::InsertAddress (const std::string& address, const std::string& jump) { auto pos = jump.find(".b32.i2p"); @@ -508,7 +405,7 @@ namespace client auto ident = std::make_shared(); if (ident->FromBase64 (jump)) { - if (m_Storage) m_Storage->AddAddress (ident); + m_Storage->AddAddress (ident); m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); } @@ -519,19 +416,18 @@ namespace client void AddressBook::InsertFullAddress (std::shared_ptr address) { - if (m_Storage) m_Storage->AddAddress (address); + 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 ? m_Storage->GetAddress (addr->identHash) : nullptr; + return m_Storage->GetAddress (addr->identHash); } void AddressBook::LoadHosts () { - if (!m_Storage) return; if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; @@ -567,35 +463,29 @@ namespace client if (pos != std::string::npos) { - std::string_view name = std::string_view(s).substr(0, pos++); - std::string_view addr = std::string_view(s).substr(pos); + std::string name = s.substr(0, pos++); + std::string addr = s.substr(pos); size_t pos = addr.find('#'); - if (pos != addr.npos) + if (pos != std::string::npos) addr = addr.substr(0, pos); // remove comments -#if __cplusplus >= 202002L // C++20 - if (name.ends_with (".b32.i2p")) -#else - if (name.find(".b32.i2p") != name.npos) -#endif + + pos = name.find(".b32.i2p"); + if (pos != std::string::npos) { LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); continue; } -#if __cplusplus >= 202002L // C++20 - if (!name.ends_with (".i2p")) -#else - if (name.find(".i2p") == name.npos) -#endif + pos = name.find(".i2p"); + if (pos == std::string::npos) { 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; @@ -608,18 +498,15 @@ namespace client ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); - if (m_Storage) - { - m_Storage->AddAddress (ident); - m_Storage->RemoveAddress (it->second->identHash); - } + 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 ())); - if (m_Storage) m_Storage->AddAddress (ident); + m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: Added new host: ", name); } @@ -631,7 +518,7 @@ namespace client if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; - if (m_Storage) m_Storage->Save (m_Addresses); + m_Storage->Save (m_Addresses); } return !incomplete; } @@ -655,15 +542,16 @@ namespace client } else { - LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config"); + LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config file"); // 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) - if (!s.empty ()) - m_Subscriptions.push_back (std::make_shared (*this, s)); + { + m_Subscriptions.push_back (std::make_shared (*this, s)); + } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } } @@ -673,8 +561,7 @@ namespace client void AddressBook::LoadLocal () { - if (!m_Storage) return; - AddressBookStorage::Addresses localAddresses; + std::map> localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { @@ -717,6 +604,7 @@ 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) @@ -747,13 +635,13 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - m_SubscriptionsUpdateTimer = std::make_unique(dest->GetService ()); + m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (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 (eLogCritical, "Addressbook: Can't start subscriptions: missing shared local destination"); + LogPrint (eLogError, "Addressbook: Can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () @@ -771,13 +659,7 @@ namespace client LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); return; } - 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_IsDownloading && dest->IsReady ()) { if (!m_IsLoaded) { @@ -786,15 +668,17 @@ namespace client std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); - m_Downloading = std::async (std::launch::async, - std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); + m_IsDownloading = true; + std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); + load_hosts.detach(); // TODO: use join } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); - m_Downloading = std::async (std::launch::async, - std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); + m_IsDownloading = true; + std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); + load_hosts.detach(); // TODO: use join } } else @@ -831,7 +715,7 @@ namespace client } } - void AddressBook::LookupAddress (std::string_view address) + void AddressBook::LookupAddress (const std::string& address) { std::shared_ptr addr; auto dot = address.find ('.'); @@ -861,7 +745,7 @@ namespace client memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); - memcpy (buf + 9, address.data (), address.length ()); + memcpy (buf + 9, address.c_str (), address.length ()); datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } @@ -898,30 +782,7 @@ namespace client } } - void AddressBook::ScheduleCacheUpdate () - { - if (!m_AddressCacheUpdateTimer) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - if(dest) - m_AddressCacheUpdateTimer = std::make_unique(dest->GetService ()); - } - if (m_AddressCacheUpdateTimer) - { - m_AddressCacheUpdateTimer->expires_from_now (boost::posix_time::seconds(ADDRESS_CACHE_UPDATE_INTERVAL )); - m_AddressCacheUpdateTimer->async_wait ( - [this](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - if (m_Storage) m_Storage->CleanUpCache (); - ScheduleCacheUpdate (); - } - }); - } - } - - AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link): + AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): m_Book (book), m_Link (link) { } @@ -952,22 +813,40 @@ namespace client } else m_Ident = addr->identHash; - // save url parts for later use - std::string dest_host = url.host; - int dest_port = url.port ? url.port : 80; - // try to create stream to addressbook site - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (m_Ident, dest_port); - if (!stream) + /* this code block still needs some love */ + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); + if (!leaseSet) { + std::unique_lock l(newDataReceivedMutex); + i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, + [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) + { + leaseSet = ls; + std::unique_lock l1(newDataReceivedMutex); + newDataReceived.notify_all (); + }); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) + { + LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already + return false; + } + } + if (!leaseSet) { + /* still no leaseset found */ LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; } - if (m_Etag.empty() && m_LastModified.empty()) - { + if (m_Etag.empty() && m_LastModified.empty()) { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); LogPrint (eLogDebug, "Addressbook: Loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } - // create http request & send it + /* save url parts for later use */ + std::string dest_host = url.host; + int dest_port = url.port ? url.port : 80; + /* create http request & send it */ i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); @@ -978,29 +857,34 @@ namespace client req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); - // convert url to relative + /* convert url to relative */ url.schema = ""; url.host = ""; req.uri = url.to_string(); req.version = "HTTP/1.1"; + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); - // read response + /* read response */ std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { - size_t received = stream->Receive (recv_buf, 4096, SUBSCRIPTION_REQUEST_TIMEOUT); - if (received) - { - response.append ((char *)recv_buf, received); - if (!stream->IsOpen ()) end = true; - } - else if (!stream->IsOpen ()) - end = true; - else + stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), + [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (bytes_transferred) + response.append ((char *)recv_buf, bytes_transferred); + if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) + end = true; + newDataReceived.notify_all (); + }, + SUBSCRIPTION_REQUEST_TIMEOUT); + std::unique_lock l(newDataReceivedMutex); + // wait 1 more second + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT + 1)) == std::cv_status::timeout) { LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); numAttempts++; @@ -1010,7 +894,7 @@ namespace client // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); - // parse response + /* parse response */ i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) @@ -1023,7 +907,7 @@ namespace client LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } - // assert: res_head_len > 0 + /* assert: res_head_len > 0 */ response.erase(0, res_head_len); if (res.code == 304) { @@ -1046,7 +930,7 @@ namespace client LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } - // assert: res.code == 200 + /* assert: res.code == 200 */ auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("Last-Modified"); diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 8b32aa93..ded87de5 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,12 +11,10 @@ #include #include -#include #include #include #include #include -#include #include #include #include "Base.h" @@ -34,9 +32,7 @@ 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 seconds - const int ADDRESS_CACHE_EXPIRATION_TIMEOUT = 710; // in seconds - const int ADDRESS_CACHE_UPDATE_INTERVAL = 76; // in seconds + const int SUBSCRIPTION_REQUEST_TIMEOUT = 120; //in second const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; @@ -49,7 +45,7 @@ namespace client i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; - Address (std::string_view b32); + Address (const std::string& b32); Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; bool IsValid () const { return addressType != eAddressInvalid; }; @@ -61,18 +57,15 @@ namespace client { public: - typedef std::map, std::less<> > Addresses; - virtual ~AddressBookStorage () {}; - virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) = 0; + virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 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 (Addresses& addresses) = 0; - virtual int LoadLocal (Addresses& addresses) = 0; - virtual int Save (const Addresses& addresses) = 0; + virtual int Load (std::map >& addresses) = 0; + virtual int LoadLocal (std::map >& addresses) = 0; + virtual int Save (const std::map >& 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; @@ -84,21 +77,19 @@ namespace client class AddressBook { public: - + AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); - std::shared_ptr GetAddress (std::string_view address); + std::shared_ptr GetAddress (const std::string& address); std::shared_ptr GetFullAddress (const std::string& address); - std::shared_ptr FindAddress (std::string_view address); - void LookupAddress (std::string_view address); + std::shared_ptr FindAddress (const std::string& address); + void LookupAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& jump); // for jump links void InsertFullAddress (std::shared_ptr address); - bool RecordExists (const std::string& address, const std::string& jump); - bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address @@ -106,8 +97,7 @@ 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 (); @@ -123,30 +113,26 @@ 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; - AddressBookStorage::Addresses m_Addresses; + std::map > 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; - std::future m_Downloading; + volatile bool m_IsLoaded, m_IsDownloading; int m_NumRetries; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet - std::unique_ptr m_SubscriptionsUpdateTimer, m_AddressCacheUpdateTimer; - bool m_IsEnabled; + boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; }; class AddressBookSubscription { public: - AddressBookSubscription (AddressBook& book, std::string_view link); + AddressBookSubscription (AddressBook& book, const std::string& link); void CheckUpdates (); private: diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index 08d3c3f0..c331dc78 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,24 +16,6 @@ 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) { @@ -145,9 +127,9 @@ namespace client connection->I2PConnect (receiver->data, receiver->dataLen); } - BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, + BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), - m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet) + m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet) { } @@ -174,7 +156,7 @@ namespace client { if (stream) { - auto conn = std::make_shared (this, stream, m_Endpoint, m_IsQuiet); + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } @@ -182,11 +164,11 @@ namespace client BOBDestination::BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const uint16_t inport, const uint16_t outport, const bool quiet): + const int inport, const int outport, const bool quiet): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), - m_InPort(inport), m_OutPort(outport), m_Quiet(quiet), m_IsRunning(false) + m_InPort(inport), m_OutPort(outport), m_Quiet(quiet) { } @@ -201,7 +183,6 @@ namespace client { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); - m_IsRunning = true; } void BOBDestination::Stop () @@ -212,7 +193,6 @@ namespace client void BOBDestination::StopTunnels () { - m_IsRunning = false; if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); @@ -227,7 +207,7 @@ namespace client } } - void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) + void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost) { if (!m_InboundTunnel) { @@ -238,7 +218,7 @@ namespace client if (!inhost.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (inhost, ec); + auto addr = boost::asio::ip::address::from_string (inhost, ec); if (!ec) ep.address (addr); else @@ -248,7 +228,7 @@ namespace client } } - void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet) + void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet) { if (!m_OutboundTunnel) { @@ -343,28 +323,19 @@ namespace client } } - void BOBCommandSession::SendReplyOK (std::string_view msg) + void BOBCommandSession::SendReplyOK (const char * msg) { std::ostream os(&m_SendBuffer); os << "OK"; - if (!msg.empty ()) + if(msg) + { os << " " << msg; + } os << std::endl; Send (); - } - - 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) + } + + void BOBCommandSession::SendReplyError (const char * msg) { std::ostream os(&m_SendBuffer); os << "ERROR " << msg << std::endl; @@ -375,32 +346,23 @@ namespace client { std::ostream os(&m_SendBuffer); os << "BOB 00.00.10" << std::endl; - SendReplyOK(std::string_view()); // empty string + SendReplyOK(); } - void BOBCommandSession::SendRaw (std::string_view data) + void BOBCommandSession::SendRaw (const char * data) { std::ostream os(&m_SendBuffer); os << data << std::endl; } - void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr dest, std::string &out) + void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *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 && dest->IsRunning(); }; + const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); }; 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(); @@ -409,14 +371,10 @@ 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.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 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 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; @@ -425,8 +383,7 @@ 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 << " " - << "PROXYTYPE: "<< proxyType << " " << "PROXYSTART: " << bool_str(proxyStatus); + << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost; out = ss.str(); } @@ -466,7 +423,7 @@ namespace client { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; - boost::asio::ip::make_address(m_InHost, ec); + boost::asio::ip::address::from_string(m_InHost, ec); if (ec) { SendReplyError("inhost must be a valid IPv4 address."); @@ -477,7 +434,7 @@ namespace client { // TODO: FIXME: temporary validation, until hostname support is added boost::system::error_code ec; - boost::asio::ip::make_address(m_OutHost, ec); + boost::asio::ip::address::from_string(m_OutHost, ec); if (ec) { SendReplyError("outhost must be a IPv4 address."); @@ -487,55 +444,15 @@ namespace client if (!m_CurrentDestination) { - m_CurrentDestination = std::make_shared (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command + m_CurrentDestination = new BOBDestination (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_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; - } - } + 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 (); SendReplyOK ("Tunnel starting"); m_IsActive = true; } @@ -549,15 +466,10 @@ 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"); @@ -567,40 +479,26 @@ namespace client void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setnick ", operand); - if(*operand) - { - auto dest = m_Owner.FindDestination (operand); - if (!dest) - { - m_Nickname = operand; - SendReplyOK ({ "Nickname set to ", m_Nickname }); - } - else - SendReplyError ("tunnel is active"); - } - else - SendReplyError ("no nickname has been set"); + m_Nickname = operand; + std::string msg ("Nickname set to "); + msg += m_Nickname; + SendReplyOK (msg.c_str ()); } void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getnick ", operand); - if(*operand) + m_CurrentDestination = m_Owner.FindDestination (operand); + if (m_CurrentDestination) { - 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) - SendReplyOK ({"Nickname set to ", m_Nickname}); - else - SendReplyError ("no nickname has been set"); + m_Keys = m_CurrentDestination->GetKeys (); + m_Nickname = operand; + } + if (m_Nickname == operand) + { + std::string msg ("Nickname set to "); + msg += m_Nickname; + SendReplyOK (msg.c_str ()); } else SendReplyError ("no nickname has been set"); @@ -609,7 +507,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_EDDSA_SHA512_ED25519; + i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (*operand) { @@ -630,15 +528,15 @@ namespace client } - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); - SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } 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 ()); + if (m_Keys.FromBase64 (operand)) + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); } @@ -647,7 +545,7 @@ namespace client { LogPrint (eLogDebug, "BOB: getkeys"); if (m_Keys.GetPublic ()) // keys are set ? - SendReplyOK (m_Keys.ToBase64 ()); + SendReplyOK (m_Keys.ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } @@ -656,7 +554,7 @@ namespace client { LogPrint (eLogDebug, "BOB: getdest"); if (m_Keys.GetPublic ()) // keys are set ? - SendReplyOK (m_Keys.GetPublic ()->ToBase64 ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } @@ -664,61 +562,35 @@ namespace client void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); - if (*operand) - { - m_OutHost = operand; - SendReplyOK ("outhost set"); - } - else - SendReplyError ("empty outhost"); + m_OutHost = operand; + SendReplyOK ("outhost set"); } void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outport ", operand); - if (*operand) - { - int port = std::stoi(operand); - if (port >= 0 && port < 65536) - { - m_OutPort = port; - SendReplyOK ("outbound port set"); - } - else - SendReplyError ("port out of range"); - } + m_OutPort = std::stoi(operand); + if (m_OutPort >= 0) + SendReplyOK ("outbound port set"); else - SendReplyError ("empty outport"); + SendReplyError ("port out of range"); } void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); - if (*operand) - { - m_InHost = operand; - SendReplyOK ("inhost set"); - } - else - SendReplyError ("empty inhost"); + m_InHost = operand; + SendReplyOK ("inhost set"); } void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inport ", operand); - if (*operand) - { - int port = std::stoi(operand); - if (port >= 0 && port < 65536) - { - m_InPort = port; - SendReplyOK ("inbound port set"); - } - else - SendReplyError ("port out of range"); - } + m_InPort = std::stoi(operand); + if (m_InPort >= 0) + SendReplyOK ("inbound port set"); else - SendReplyError ("empty inport"); + SendReplyError ("port out of range"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) @@ -741,158 +613,54 @@ namespace client void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); - if (*operand) + auto addr = context.GetAddressBook ().GetAddress (operand); + if (!addr) { - 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) - { - SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ()); - return; - } - } - // trying to request - auto s = shared_from_this (); - auto requstCallback = [s](std::shared_ptr ls) - { - if (ls) - s->SendReplyOK (ls->GetIdentity ()->ToBase64 ()); - else - s->SendReplyError ("LeaseSet Not found"); - }; - if (addr->IsIdentHash ()) - localDestination->RequestDestination (addr->identHash, requstCallback); - else - localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); + SendReplyError ("Address Not found"); + return; } + auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + if (addr->IsIdentHash ()) + { + // we might have leaseset already + auto leaseSet = localDestination->FindLeaseSet (addr->identHash); + if (leaseSet) + { + SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); + return; + } + } + // trying to request + auto s = shared_from_this (); + auto requstCallback = [s](std::shared_ptr ls) + { + if (ls) + s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); + else + s->SendReplyError ("LeaseSet Not found"); + }; + if (addr->IsIdentHash ()) + localDestination->RequestDestination (addr->identHash, requstCallback); else - SendReplyError ("empty lookup address"); + localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); } void BOBCommandSession::LookupLocalCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup local ", operand); - if (*operand) + auto addr = context.GetAddressBook ().GetAddress (operand); + if (!addr) { - auto addr = context.GetAddressBook ().GetAddress (operand); - if (!addr) - { - SendReplyError ("Address Not found"); - return; - } - auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); - if (ls) - SendReplyOK (ls->GetIdentity ()->ToBase64 ()); - else - SendReplyError ("Local LeaseSet Not found"); + SendReplyError ("Address Not found"); + return; } + auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); + if (ls) + SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else - SendReplyError ("empty lookup address"); + SendReplyError ("Local LeaseSet Not found"); } - 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"); @@ -910,7 +678,7 @@ namespace client for (const auto& it: destinations) { BuildStatusLine(false, it.second, statusLine); - SendRaw(statusLine); + SendRaw(statusLine.c_str()); if(m_Nickname.compare(it.second->GetNickname()) == 0) sentCurrent = true; } @@ -919,7 +687,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); + SendRaw(statusLine.c_str()); } SendReplyOK ("Listing done"); } @@ -930,10 +698,14 @@ namespace client const char * value = strchr (operand, '='); if (value) { + std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; - SendReplyOK ({ "option ", operand, " set to ", value + 1 }); + msg += operand; *(const_cast(value)) = '='; + msg += " set to "; + msg += value + 1; + SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); @@ -946,12 +718,12 @@ namespace client std::string statusLine; // always prefer destination - auto dest = m_Owner.FindDestination(name); - if(dest) + auto ptr = m_Owner.FindDestination(name); + if(ptr != nullptr) { // tunnel destination exists - BuildStatusLine(false, dest, statusLine); - SendReplyOK(statusLine); + BuildStatusLine(false, ptr, statusLine); + SendReplyOK(statusLine.c_str()); } else { @@ -959,7 +731,7 @@ namespace client { // tunnel is incomplete / has not been started yet BuildStatusLine(true, nullptr, statusLine); - SendReplyOK(statusLine); + SendReplyOK(statusLine.c_str()); } else { @@ -970,7 +742,7 @@ namespace client void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) { auto helpStrings = m_Owner.GetHelpStrings(); - if(!*operand) + if(len == 0) { std::stringstream ss; ss << "COMMANDS:"; @@ -978,44 +750,24 @@ namespace client { ss << " " << x.first; } - SendReplyOK(ss.str ()); + const std::string &str = ss.str(); + SendReplyOK(str.c_str()); } else { auto it = helpStrings.find(operand); if (it != helpStrings.end ()) { - SendReplyOK(it->second); + SendReplyOK(it->second.c_str()); 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, uint16_t port): + BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): RunnableService ("BOB"), - m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)) + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; @@ -1035,13 +787,11 @@ 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; @@ -1064,13 +814,14 @@ 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 () @@ -1087,9 +838,9 @@ namespace client StopIOService (); } - void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr dest) + void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) { - m_Destinations.emplace (name, dest); + m_Destinations[name] = dest; } void BOBCommandChannel::DeleteDestination (const std::string& name) @@ -1098,39 +849,18 @@ namespace client if (it != m_Destinations.end ()) { it->second->Stop (); + delete it->second; m_Destinations.erase (it); } } - std::shared_ptr BOBCommandChannel::FindDestination (const std::string& name) + BOBDestination * 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 a3f6754f..164ca47c 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,25 +14,18 @@ #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"; @@ -50,15 +43,12 @@ 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."; @@ -80,25 +70,7 @@ 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: @@ -152,7 +124,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -177,21 +149,20 @@ namespace client BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const uint16_t inport, const uint16_t outport, const bool quiet); + const int inport, const int outport, const bool quiet); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); - void CreateInboundTunnel (uint16_t port, const std::string& inhost); - void CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet); + void CreateInboundTunnel (int port, const std::string& inhost); + void CreateOutboundTunnel (const std::string& outhost, int 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; } - uint16_t GetInPort() const { return m_InPort; } - uint16_t GetOutPort() const { return m_OutPort; } + int GetInPort() const { return m_InPort; } + int 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 (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -203,9 +174,8 @@ namespace client std::string m_Nickname; std::string m_InHost, m_OutHost; - uint16_t m_InPort, m_OutPort; + int m_InPort, m_OutPort; bool m_Quiet; - bool m_IsRunning; }; class BOBCommandChannel; @@ -238,13 +208,11 @@ 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: @@ -254,15 +222,12 @@ namespace client void Send (); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - 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 SendReplyOK (const char * msg = nullptr); + void SendReplyError (const char * msg); + void SendRaw (const char * data); - void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); + void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); - void SendPing (std::shared_ptr ls); - private: BOBCommandChannel& m_Owner; @@ -270,17 +235,10 @@ namespace client boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; - uint16_t m_InPort, m_OutPort; + int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; - std::shared_ptr m_CurrentDestination; - - enum class TunnelType - { - SOCKS = 0, - HTTP_PROXY = 1 - }; - std::optional m_tunnelType; + BOBDestination * m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); @@ -288,19 +246,16 @@ namespace client { public: - BOBCommandChannel (const std::string& address, uint16_t port); + BOBCommandChannel (const std::string& address, int port); ~BOBCommandChannel (); void Start (); void Stop (); - auto& GetService () { return GetIOService (); }; - void AddDestination (const std::string& name, std::shared_ptr dest); + boost::asio::io_service& GetService () { return GetIOService (); }; + void AddDestination (const std::string& name, BOBDestination * dest); void DeleteDestination (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); + BOBDestination * FindDestination (const std::string& name); private: @@ -310,10 +265,9 @@ 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 b890fbc6..41b2a494 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,7 +16,6 @@ #include "Identity.h" #include "util.h" #include "ClientContext.h" -#include "HTTPProxy.h" #include "SOCKS.h" #include "MatchedDestination.h" @@ -64,19 +63,18 @@ namespace client if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); - uint16_t samPortTCP; i2p::config::GetOption("sam.port", samPortTCP); - uint16_t samPortUDP; i2p::config::GetOption("sam.portudp", samPortUDP); + uint16_t samPort; i2p::config::GetOption("sam.port", samPort); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP, "]"); + LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":", samPort); try { - m_SamBridge = new SAMBridge (samAddr, samPortTCP, samPortUDP, singleThread); + m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); m_SamBridge->Start (); } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in SAM bridge: ", e.what()); - ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP,"]: ", e.what ()); + LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); + ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); } } @@ -93,7 +91,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in BOB bridge: ", e.what()); + LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); } } @@ -113,7 +111,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in I2CP: ", e.what()); + LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); } } @@ -187,30 +185,22 @@ 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; } - { - 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 (); - } + for (auto& it: m_Destinations) + it.second->Stop (); + m_Destinations.clear (); - LogPrint(eLogInfo, "Clients: Stopping SharedLocalDestination"); m_SharedLocalDestination->Release (); m_SharedLocalDestination = nullptr; } @@ -265,17 +255,13 @@ namespace client } } - bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, std::string_view filename, + bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { -#if __cplusplus >= 202002L // C++20 - if (filename.starts_with ("transient")) -#else - std::string_view transient("transient"); + static const std::string transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient -#endif { - keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } @@ -292,7 +278,7 @@ namespace client s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { - LogPrint (eLogCritical, "Clients: Failed to load keyfile ", filename); + LogPrint (eLogError, "Clients: Failed to load keyfile ", filename); success = false; } else @@ -301,8 +287,8 @@ namespace client } else { - 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); + LogPrint (eLogError, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; @@ -342,18 +328,18 @@ namespace client i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); auto localDestination = std::make_shared (keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; } std::shared_ptr ClientContext::CreateNewLocalDestination ( - boost::asio::io_context& service, bool isPublic, + boost::asio::io_service& service, bool isPublic, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); auto localDestination = std::make_shared (service, keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; @@ -403,7 +389,7 @@ namespace client return localDestination; } - std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_context& service, + std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); @@ -420,10 +406,13 @@ namespace client void ClientContext::CreateNewSharedLocalDestination () { - std::map params; - ReadI2CPOptionsFromConfig ("shareddest.", params); - params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SharedDest"; - + 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" } + }; 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 (); @@ -472,19 +461,9 @@ 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); -#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 + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; @@ -528,10 +507,6 @@ 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 () @@ -555,11 +530,7 @@ 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); } @@ -597,30 +568,20 @@ 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); - uint16_t port = section.second.get (I2P_CLIENT_TUNNEL_PORT); + int 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"); - uint16_t 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"); + int 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 == "shareddest") - localDestination = m_SharedLocalDestination; - else if (keys.length () > 0) + if (keys.length () > 0) { auto it = destinations.find (keys); if (it != destinations.end ()) @@ -644,18 +605,15 @@ 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::make_address(address), port); + boost::asio::ip::udp::endpoint end (boost::asio::ip::address::from_string(address), port); if (!localDestination) localDestination = m_SharedLocalDestination; bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); - 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 clientTunnel = std::make_shared (name, dest, end, localDestination, destinationPort, gzip); auto ins = m_ClientForwards.insert (std::make_pair (end, clientTunnel)); if (ins.second) @@ -677,9 +635,7 @@ 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) @@ -695,9 +651,7 @@ namespace client // http proxy std::string outproxy = section.second.get("outproxy", ""); bool addresshelper = section.second.get("addresshelper", true); - bool senduseragent = section.second.get("senduseragent", false); - auto tun = std::make_shared(name, address, port, - outproxy, addresshelper, senduseragent, localDestination); + auto tun = std::make_shared(name, address, port, outproxy, addresshelper, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } @@ -757,66 +711,52 @@ namespace client { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); - uint16_t port = section.second.get (I2P_SERVER_TUNNEL_PORT); + int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params - uint16_t inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, port); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + 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, (host == "::1") ? false : true); - bool ssl = section.second.get (I2P_SERVER_TUNNEL_SSL, false); + bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); // 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; - if (keys == "shareddest") - localDestination = m_SharedLocalDestination; + auto it = destinations.find (keys); + if (it != destinations.end ()) + { + localDestination = it->second; + localDestination->SetPublic (true); + } else - { - auto it = destinations.find (keys); - if (it != destinations.end ()) + { + i2p::data::PrivateKeys k; + if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) + continue; + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) { - localDestination = it->second; - localDestination->SetPublic (true); + localDestination = CreateNewLocalDestination (k, true, &options); + destinations[keys] = localDestination; } else - { - 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); - } - } + localDestination->SetPublic (true); + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: hostnames - boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::make_address(host), port); + boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); if (address.empty ()) { if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) @@ -824,8 +764,8 @@ namespace client else address = "127.0.0.1"; } - auto localAddress = boost::asio::ip::make_address(address); - auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, inPort, gzip); + auto localAddress = boost::asio::ip::address::from_string(address); + auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); @@ -859,13 +799,11 @@ namespace client if (!address.empty ()) serverTunnel->SetLocalAddress (address); - if (!isUniqueLocal) + if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } - if (ssl) - serverTunnel->SetSSL (true); if (accessList.length () > 0) { std::set idents; @@ -905,11 +843,11 @@ namespace client } else - LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); + LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { - LogPrint (eLogCritical, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); + LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); } } @@ -926,42 +864,29 @@ 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 == "shareddest") - { - localDestination = m_SharedLocalDestination; - localDestination->Acquire (); - } - else if (httpProxyKeys.length () > 0) + 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(eLogCritical, "Clients: Failed to load HTTP Proxy key"); + LogPrint(eLogError, "Clients: Failed to load HTTP Proxy key"); } try { - m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, - httpOutProxyURL, httpAddresshelper, httpSendUserAgent, localDestination); + m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what()); + LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); } } @@ -982,16 +907,8 @@ 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 (socksProxyKeys == "shareddest") - { - localDestination = m_SharedLocalDestination; - localDestination->Acquire (); - } - else if (httpProxyKeys == socksProxyKeys && m_HttpProxy) + if (httpProxyKeys == socksProxyKeys && m_HttpProxy) { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); @@ -1003,12 +920,11 @@ 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(eLogCritical, "Clients: Failed to load SOCKS Proxy key"); + LogPrint(eLogError, "Clients: Failed to load SOCKS Proxy key"); } try { @@ -1018,7 +934,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); + LogPrint(eLogError, "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 52790836..d512a55b 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,12 +12,12 @@ #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" #include "BOB.h" #include "I2CP.h" @@ -61,9 +61,8 @@ namespace client const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; - const char I2P_SERVER_TUNNEL_SSL[] = "ssl"; - const char UDP_CLIENT_TUNNEL_DATAGRAM_VERSION[] = "datagramversion"; - + + class ClientContext { public: @@ -81,20 +80,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_context& service, + std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& 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_context& service, + std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& 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, std::string_view filename, + bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); @@ -141,7 +140,8 @@ namespace client AddressBook m_AddressBook; - I2PService * m_HttpProxy, * m_SocksProxy; + i2p::proxy::HTTPProxy * m_HttpProxy; + i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint -> tunnel std::map, std::shared_ptr > m_ServerTunnels; // -> tunnel @@ -166,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 I2PService * GetHttpProxy () const { return m_HttpProxy; } - const I2PService * GetSocksProxy () const { return m_SocksProxy; } + const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; } + const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; } }; extern ClientContext context; diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 4c2771b5..984ad0e7 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +29,6 @@ #include "Config.h" #include "HTTP.h" #include "I18N.h" -#include "Socks5.h" namespace i2p { namespace proxy { @@ -38,14 +36,12 @@ namespace proxy { "reg.i2p", "stats.i2p", "identiguy.i2p", - "notbob.i2p" }; static const std::map jumpservices = { { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, - { "notbob.i2p", "http://nytzrhrjjfsutowojvxi7hphesskpqqr65wpistz6wa7cpajhp7a.b32.i2p/cgi-bin/jump.cgi?q=" } }; static const char *pageHead = @@ -60,8 +56,7 @@ namespace proxy { "\r\n" ; - static bool str_rmatch(std::string_view str, const char *suffix) - { + bool str_rmatch(std::string & str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ @@ -78,27 +73,28 @@ namespace proxy { void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - 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); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm); + void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ - 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 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 ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); - void HTTPConnect(std::string_view host, uint16_t port); + void HTTPConnect(const std::string & 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::results_type endpoints, ProxyResolvedHandler handler); + void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler); void SocksProxySuccess(); void HandoverToUpstreamProxy(); @@ -109,11 +105,12 @@ namespace proxy { std::shared_ptr m_sock; std::shared_ptr m_proxysock; boost::asio::ip::tcp::resolver m_proxy_resolver; - std::string m_OutproxyUrl, m_Response; - bool m_Addresshelper, m_SendUserAgent; + std::string m_OutproxyUrl; + bool m_Addresshelper; i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; - int m_req_len; + uint8_t m_socks_buf[255+8]; // for socks request/response + ssize_t m_req_len; i2p::http::URL m_ClientRequestURL; i2p::http::HTTPReq m_ClientRequest; i2p::http::HTTPRes m_ClientResponse; @@ -126,8 +123,7 @@ namespace proxy { m_proxysock(std::make_shared(parent->GetService())), m_proxy_resolver(parent->GetService()), m_OutproxyUrl(parent->GetOutproxyURL()), - m_Addresshelper(parent->GetHelperSupport()), - m_SendUserAgent (parent->GetSendUserAgent ()) {} + m_Addresshelper(parent->GetHelperSupport()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; @@ -162,24 +158,23 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) - { + void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { std::stringstream ss; ss << "

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

\r\n"; ss << "

" << description << "

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

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

\r\n"; ss << "

" << description << "

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

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

\r\n" << "

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

\r\n" @@ -189,13 +184,14 @@ namespace proxy { { auto js = jumpservices.find (jump); if (js != jumpservices.end()) - ss << "
  • second << host << "\">" << js->first << "
  • \r\n"; + ss << "
  • first << host << "\">" << js->second << "
  • \r\n"; } ss << "\r\n"; - SendProxyError(ss.str ()); + std::string content = ss.str(); + SendProxyError(content); } - void HTTPReqHandler::SendProxyError(std::string_view content) + void HTTPReqHandler::SendProxyError(std::string & content) { i2p::http::HTTPRes res; res.code = 500; @@ -206,23 +202,12 @@ namespace proxy { << "" << content << "\r\n" << "\r\n"; res.body = ss.str(); - m_Response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), + std::string response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::SendRedirect(const std::string& address) - { - i2p::http::HTTPRes res; - res.code = 302; - res.add_header("Location", address); - res.add_header("Connection", "close"); - m_Response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); - } - - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm) + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) { confirm = false; const char *param = "i2paddresshelper="; @@ -238,108 +223,37 @@ namespace proxy { std::string value = params["i2paddresshelper"]; len += value.length(); - jump = i2p::http::UrlDecode(value); - if (!VerifyAddressHelper (jump)) - { - LogPrint (eLogError, "HTTPProxy: Malformed jump link ", jump); - return false; - } - + b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param - if (params["update"] == "true") - { - len += std::strlen("&update=true"); - confirm = true; - } - - // if helper is not only one query option and it placed after user's query - if (pos != 0 && url.query[pos-1] == '&') - { - pos--; - len++; - } - // if helper is not only one query option and it placed before user's query - else if (pos == 0 && url.query.length () > len && url.query[len] == '&') - { - // we don't touch the '?' but remove the trailing '&' - len++; - } - else - { - // there is no more query options, resetting hasquery flag - url.hasquery = false; - } - - // reset hasquery flag and remove addresshelper from URL + if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } + if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option url.query.replace(pos, len, ""); return true; } - 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) + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { /* drop common headers */ req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); - req.RemoveHeader("DNT"); // Useless DoNotTrack flag req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ - if (!m_SendUserAgent) - req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); - - /** - * i2pd PR #1816: - * Android Webview send this with the value set to the application ID, so we drop it, - * but only if it does not belong to an AJAX request (*HttpRequest, like XMLHttpRequest). - */ - if(req.GetHeader("X-Requested-With") != "") { - auto h = req.GetHeader ("X-Requested-With"); - auto x = h.find("HttpRequest"); - if (x == std::string::npos) // not found - req.RemoveHeader("X-Requested-With"); - } + req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /** * according to i2p ticket #1862: - * leave Referer if requested URL with same schema, host and port, + * leave Referrer if requested URL with same schema, host and port, * otherwise, drop it. */ - if(req.GetHeader("Referer") != "") { + if(req.GetHeader("Referrer") != "") { i2p::http::URL reqURL; reqURL.parse(req.uri); - i2p::http::URL refURL; refURL.parse(req.GetHeader("Referer")); + i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer")); if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) - req.RemoveHeader("Referer"); + req.RemoveHeader("Referrer"); } /* add headers */ @@ -377,66 +291,31 @@ namespace proxy { std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { - if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) + if (!m_Addresshelper) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); - GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); + GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); return true; } - - if (i2p::client::context.GetAddressBook ().RecordExists (m_RequestURL.host, jump)) + if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) { - std::string full_url = m_RequestURL.to_string(); - SendRedirect(full_url); - return true; - } - else if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) - { - const std::string referer_raw = m_ClientRequest.GetHeader("Referer"); - i2p::http::URL referer_url; - if (!referer_raw.empty ()) - { - referer_url.parse (referer_raw); - } - if (m_RequestURL.host != referer_url.host) - { - if (m_Confirm) // Attempt to forced overwriting by link with "&update=true" from harmful URL - { - LogPrint (eLogWarning, "HTTPProxy: Address update from addresshelper rejected for ", m_RequestURL.host, " (referer is ", m_RequestURL.host.empty() ? "empty" : "harmful", ")"); - std::string full_url = m_RequestURL.to_string(); - std::stringstream ss; - ss << tr("Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", - m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); - GenericProxyInfo(tr("Addresshelper forced update rejected"), ss.str()); - } - else // Preventing unauthorized additions to the address book - { - LogPrint (eLogDebug, "HTTPProxy: Adding address from addresshelper for ", m_RequestURL.host, " (generate refer-base page)"); - std::string full_url = m_RequestURL.to_string(); - std::stringstream ss; - ss << tr("To add host %s in router's addressbook, click here: Continue.", - m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); - GenericProxyInfo(tr("Addresshelper request"), ss.str()); - } - return true; /* request processed */ - } - i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << tr("Host %s added to router's addressbook from helper. Click here to proceed: Continue.", - m_RequestURL.host.c_str(), full_url.c_str()); - GenericProxyInfo(tr("Addresshelper adding"), ss.str()); + ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; + ss << tr("Click here to proceed:") << " " << tr("Continue") << "."; + GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } else { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << tr("Host %s is already in router's addressbook. Click here to update record: Continue.", - m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); - GenericProxyInfo(tr("Addresshelper update"), ss.str()); + ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; + ss << tr("Click here to update record:") << " " << tr("Continue") << "."; + GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } } @@ -445,11 +324,11 @@ namespace proxy { bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { - const std::string& uri = m_ClientRequest.uri; + std::string uri(m_ClientRequest.uri); auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); + GenericProxyError(tr("Invalid request"), tr("invalid request uri")); return true; } else @@ -472,7 +351,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); @@ -509,10 +388,10 @@ namespace proxy { if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else - GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); + GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); - std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ()); + std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled"); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; @@ -543,8 +422,8 @@ namespace proxy { void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); + // build http request - /* build http request */ m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; @@ -552,17 +431,17 @@ 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 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"); + // 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"); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); - /* assume http if empty schema */ + // assume http if empty schema if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") { - /* handle upstream http proxy */ + // handle upstream http proxy if (!m_ProxyURL.port) m_ProxyURL.port = 80; if (m_ProxyURL.is_i2p()) { @@ -570,9 +449,9 @@ namespace proxy { auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); if (!auth.empty ()) { - /* remove existing authorization if any */ + // remove existing authorization if any m_ClientRequest.RemoveHeader("Proxy-"); - /* add own http proxy authorization */ + // add own http proxy authorization m_ClientRequest.AddHeader("Proxy-Authorization", auth); } m_send_buf = m_ClientRequest.to_string(); @@ -583,68 +462,79 @@ namespace proxy { } else { - 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)); - })); + 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)); + })); } } else if (m_ProxyURL.schema == "socks") { - /* handle upstream socks proxy */ + // handle upstream socks proxy if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified - 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)); - })); + 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)); + })); } else { - /* unknown type, complain */ - GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string()); + // unknown type, complain + GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); } } - void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler) + void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) { - if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); - else handler(*endpoints.begin ()); + if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); + else handler(*it); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { - if(!ec) - { - if(m_RequestURL.host.size() > 255) - { - GenericProxyError(tr("Hostname is too long"), m_RequestURL.host); + if(!ec) { + if(m_RequestURL.host.size() > 255) { + GenericProxyError(tr("hostname 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; - 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()); + 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)); } void HTTPReqHandler::HandoverToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy"); - auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock); + auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); @@ -652,10 +542,11 @@ namespace proxy { Terminate(); } - void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port) + void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); - if(str_rmatch(host, ".i2p")) + std::string hostname(host); + if(str_rmatch(hostname, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else @@ -678,7 +569,7 @@ namespace proxy { } else { - GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); + GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); } } @@ -689,7 +580,7 @@ namespace proxy { m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError(tr("SOCKS proxy error"), ec.message()); + if(ec) GenericProxyError(tr("socks proxy error"), ec.message()); else HandoverToUpstreamProxy(); }); } else { @@ -697,18 +588,36 @@ namespace proxy { LogPrint(eLogDebug, "HTTPProxy: Send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError(tr("Failed to send request to upstream"), ec.message()); + if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message()); else HandoverToUpstreamProxy(); }); } } + void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred) + { + if(!ec) + { + if(m_socks_buf[1] == 90) { + // success + SocksProxySuccess(); + } else { + std::stringstream ss; + ss << "error code: "; + ss << (int) m_socks_buf[1]; + std::string msg = ss.str(); + GenericProxyError(tr("socks proxy error"), msg); + } + } + else GenericProxyError(tr("No Reply From socks proxy"), ec.message()); + } + void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { LogPrint(eLogDebug, "HTTPProxy: Connected to http upstream"); - GenericProxyError(tr("Cannot connect"), tr("HTTP out proxy not implemented")); - } else GenericProxyError(tr("Cannot connect to upstream HTTP proxy"), ec.message()); + GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented")); + } else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message()); } /* will be called after some data received from client */ @@ -753,10 +662,9 @@ namespace proxy { Done (shared_from_this()); } - HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, - const std::string & outproxy, bool addresshelper, bool senduseragent, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), - m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent) + m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper) { } diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 507a87e2..69ed4cef 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,15 +15,13 @@ namespace proxy { { public: - 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(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() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } - bool GetHelperSupport() const { return m_Addresshelper; } - bool GetSendUserAgent () const { return m_SendUserAgent; } + bool GetHelperSupport() { return m_Addresshelper; } protected: @@ -35,7 +33,7 @@ namespace proxy { std::string m_Name; std::string m_OutproxyUrl; - bool m_Addresshelper, m_SendUserAgent; + bool m_Addresshelper; }; } // http } // i2p diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 2c2b6801..cc0837b7 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,7 +16,6 @@ #include "ClientContext.h" #include "Transports.h" #include "Signature.h" -#include "Config.h" #include "I2CP.h" namespace i2p @@ -24,76 +23,41 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (boost::asio::io_context& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, bool isSameThread, - const std::map& params): + I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), - m_Owner (owner), m_Identity (identity), m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), - m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service) + m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), + m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) { } void I2CPDestination::Stop () { - m_LeaseSetCreationTimer.cancel (); - m_ReadinessCheckTimer.cancel (); LeaseSetDestination::Stop (); m_Owner = nullptr; + m_LeaseSetCreationTimer.cancel (); } - void I2CPDestination::SetEncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) + void I2CPDestination::SetEncryptionPrivateKey (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 ()); - encryptionKey->CreateDecryptor (); // from private key - m_EncryptionKeys.insert_or_assign (keyType, encryptionKey); - } + m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); } - void I2CPDestination::SetECIESx25519EncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key) + void I2CPDestination::SetECIESx25519EncryptionPrivateKey (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) + if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? { - 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); + m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public + memcpy (m_ECIESx25519PrivateKey, key, 32); } } bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - 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); + 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); else LogPrint (eLogError, "I2CP: Decryptor is not set"); return false; @@ -101,35 +65,18 @@ namespace client const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - auto it = m_EncryptionKeys.find (keyType); - if (it != m_EncryptionKeys.end ()) - return it->second->pub.data (); + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) + return m_ECIESx25519Decryptor->GetPubicKey (); return nullptr; } bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { -#if __cplusplus >= 202002L // C++20 - return m_EncryptionKeys.contains (keyType); -#else - return m_EncryptionKeys.count (keyType) > 0; -#endif + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; } - i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const - { - return !m_EncryptionKeys.empty () ? m_EncryptionKeys.rbegin ()->first : 0; - } - 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) + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; @@ -139,7 +86,7 @@ namespace client void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { - boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels)); + GetService ().post (std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); } void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) @@ -149,33 +96,19 @@ 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 (); - int numLeases = leases[-1]; - if (m_Owner && numLeases) + leases[-1] = tunnels.size (); + if (m_Owner) { 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*numLeases; + size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); 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 (); @@ -189,8 +122,6 @@ namespace client }); } } - else - LogPrint (eLogError, "I2CP: Can't request LeaseSet"); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -221,32 +152,20 @@ namespace client memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); + auto s = GetSharedFromThis (); auto remote = FindLeaseSet (ident); if (remote) { - 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); - }); - } + GetService ().post ( + [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) { @@ -270,7 +189,6 @@ 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; @@ -284,43 +202,35 @@ namespace client else remoteSession->SetSharedRoutingPath (nullptr); } - if (!outboundTunnel || !remoteLease) + else { auto leases = remote->GetNonExpiredLeases (false); // without threshold if (leases.empty ()) leases = remote->GetNonExpiredLeases (true); // with threshold if (!leases.empty ()) { - auto pool = GetTunnelPool (); - remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()]; + remoteLease = leases[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})); // 10 secs RTT + i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 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) { - outboundTunnel->SendTunnelDataMsgs ( - { - i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeTunnel, - remoteLease->tunnelGateway, remoteLease->tunnelID, - garlic - } + std::vector msgs; + auto garlic = remoteSession->WrapSingleMessage (msg); + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeTunnel, + remoteLease->tunnelGateway, remoteLease->tunnelID, + garlic }); + outboundTunnel->SendTunnelDataMsg (msgs); return true; } else @@ -330,55 +240,13 @@ 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, false, params) + I2CPDestination (GetIOService (), owner, identity, isPublic, params) { } @@ -407,8 +275,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) { } @@ -419,11 +287,6 @@ 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 (); } @@ -470,27 +333,7 @@ namespace client if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) - { - 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 (); - } - } + ReceivePayload (); else { LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); @@ -576,7 +419,7 @@ namespace client if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (std::move(sendBuf)); + m_SendQueue.Add (sendBuf); else { LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); @@ -620,30 +463,30 @@ namespace client m_IsSending = false; } - std::string_view I2CPSession::ExtractString (const uint8_t * buf, size_t len) const + std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { uint8_t l = buf[0]; if (l > len) l = len; - return { (const char *)(buf + 1), l }; + return std::string ((const char *)(buf + 1), l); } - size_t I2CPSession::PutString (uint8_t * buf, size_t len, std::string_view str) + size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& 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.data (), l); + memcpy (buf + 1, str.c_str (), l); return l + 1; } - void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const + void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) // TODO: move to Base.cpp { size_t offset = 0; while (offset < len) { - auto param = ExtractString (buf + offset, len - offset); + std::string param = ExtractString (buf + offset, len - offset); offset += param.length () + 1; if (buf[offset] != '=') { @@ -652,7 +495,7 @@ namespace client } offset++; - auto value = ExtractString (buf + offset, len - offset); + std::string value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { @@ -660,20 +503,23 @@ namespace client break; } offset++; - mapping.emplace (param, value); + mapping.insert (std::make_pair (param, value)); } } void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { - constexpr std::string_view version(I2P_VERSION); - std::array payload; + // get version + auto version = ExtractString (buf, len); + auto l = version.length () + 1 + 8; + uint8_t * payload = new uint8_t[l]; // set date auto ts = i2p::util::GetMillisecondsSinceEpoch (); - 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()); + htobe64buf (payload, ts); + // echo vesrion back + PutString (payload + 8, l - 8, version); + SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); + delete[] payload; } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) @@ -712,13 +558,13 @@ namespace client if (!m_Destination) { m_Destination = m_Owner.IsSingleThread () ? - std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, true, params): + std::make_shared(m_Owner.GetService (), shared_from_this (), identity, 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 { @@ -738,7 +584,7 @@ namespace client SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid } } - + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy @@ -823,26 +669,6 @@ 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); @@ -855,7 +681,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 (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, buf + offset); + m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); } @@ -888,10 +714,13 @@ 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 (keyType, buf + offset); + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); else - m_Destination->SetEncryptionPrivateKey (keyType, buf + offset); // from identity + { + m_Destination->SetEncryptionType (keyType); + m_Destination->SetEncryptionPrivateKey (buf + offset); + } offset += keyLen; } @@ -910,44 +739,19 @@ namespace client size_t offset = 2; if (m_Destination) { - const uint8_t * ident = buf + offset; - size_t identSize = i2p::data::GetIdentityBufferLen (ident, len - offset); - if (identSize) + i2p::data::IdentityEx identity; + size_t identsize = identity.FromBuffer (buf + offset, 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_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); - } + if (m_IsSendAccepted) + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } else LogPrint(eLogError, "I2CP: Cannot send message, too big"); @@ -1087,12 +891,8 @@ namespace client { uint8_t limits[64]; memset (limits, 0, 64); - 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 + htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound + htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); } @@ -1116,7 +916,7 @@ namespace client if (sendBuf) { if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (std::move(sendBuf)); + m_SendQueue.Add (sendBuf); else { LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); @@ -1136,10 +936,10 @@ namespace client } } - I2CPServer::I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread): + I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), m_Acceptor (GetIOService (), - boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(interface), port)) + boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -1170,12 +970,12 @@ namespace client void I2CPServer::Stop () { m_Acceptor.cancel (); - - decltype(m_Sessions) sessions; - m_Sessions.swap (sessions); - for (auto& it: sessions) - it.second->Stop (); - + { + auto sessions = m_Sessions; + for (auto& it: sessions) + it.second->Stop (); + } + m_Sessions.clear (); StopIOService (); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 102fc554..e38da0ac 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,17 +11,13 @@ #include #include -#include #include -#include #include #include -#include #include #include "util.h" #include "Destination.h" #include "Streaming.h" -#include "CryptoKey.h" namespace i2p { @@ -32,8 +28,6 @@ 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; @@ -64,7 +58,6 @@ namespace client eI2CPMessageStatusAccepted = 1, eI2CPMessageStatusGuaranteedSuccess = 4, eI2CPMessageStatusGuaranteedFailure = 5, - eI2CPMessageStatusNoLocalTunnels = 16, eI2CPMessageStatusNoLeaseSet = 21 }; @@ -85,55 +78,50 @@ namespace client { public: - I2CPDestination (boost::asio::io_context& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, bool isSameThread, - const std::map& params); + I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params); ~I2CPDestination () {}; - void Stop () override; + void Stop (); - void SetEncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key); - void SetECIESx25519EncryptionPrivateKey (i2p::data::CryptoKeyType keyType, const uint8_t * key); + void SetEncryptionPrivateKey (const uint8_t * key); + void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; + void SetECIESx25519EncryptionPrivateKey (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 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; }; + 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; }; 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, i2p::garlic::ECIESX25519AEADRatchetSession * from) override; - void CreateNewLeaseSet (const std::vector >& tunnels) override; - + void HandleDataMessage (const uint8_t * buf, size_t len); + void CreateNewLeaseSet (const std::vector >& tunnels); + 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; - std::map > m_EncryptionKeys; // last is most preferable + i2p::data::CryptoKeyType m_EncryptionKeyType; + std::shared_ptr m_Decryptor; // standard + std::shared_ptr m_ECIESx25519Decryptor; + uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; - bool m_IsCreatingLeaseSet, m_IsSameThread; - boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer; + bool m_IsCreatingLeaseSet; + boost::asio::deadline_timer m_LeaseSetCreationTimer; i2p::util::MemoryPoolMt > m_I2NPMsgsPool; }; @@ -167,8 +155,6 @@ 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); @@ -195,12 +181,12 @@ namespace client void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - 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; + 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); void SendSessionStatusMessage (I2CPSessionStatus status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); - + private: I2CPServer& m_Owner; @@ -209,8 +195,6 @@ 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; @@ -226,12 +210,12 @@ namespace client { public: - I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread); + I2CPServer (const std::string& interface, int port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); - auto& GetService () { return GetIOService (); }; + boost::asio::io_service& GetService () { return GetIOService (); }; bool IsSingleThread () const { return m_IsSingleThread; }; bool InsertSession (std::shared_ptr session); @@ -240,6 +224,8 @@ 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 4ec2648a..2a6fe44e 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, 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, std::string_view dest, uint16_t port) { + void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int 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, uint16_t port) + void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, int port) { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { @@ -147,5 +147,196 @@ 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 d19c28e2..e14f85c1 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2020, 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, 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 (); } + 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 (); } virtual void Start () = 0; virtual void Stop () = 0; @@ -99,7 +99,6 @@ 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 (); }; @@ -120,171 +119,72 @@ namespace client std::atomic m_Dead; //To avoid cleaning up multiple times }; - const size_t SOCKETS_PIPE_BUFFER_SIZE = 8192 * 8; + const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8; - // bidirectional pipe for 2 stream sockets - template - class SocketsPipe: public I2PServiceHandler, - public std::enable_shared_from_this > + // bidirectional pipe for 2 tcp/ip sockets + class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this { public: - 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: - - 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; - }; - - 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: - - 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 char* GetName() override { return "Generic TCP/IP accepting daemon"; } + TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); + ~TCPIPPipe(); + void Start(); protected: - virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; + 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); private: - 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; + 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; }; - class TCPIPAcceptor: public ServiceAcceptor + /* TODO: support IPv6 too */ + //This is a service that listens for connections on the IP network and interacts with I2P + class TCPIPAcceptor: public I2PService { 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) {} - }; + 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 (); + + const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; + + virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } + + protected: + + 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; + }; } } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 3fea7172..e8af25bd 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,7 +7,6 @@ */ #include -#include #include "Base.h" #include "Log.h" #include "Destination.h" @@ -21,7 +20,7 @@ namespace client { /** set standard socket options */ - static void I2PTunnelSetSocketOptions (std::shared_ptr socket) + static void I2PTunnelSetSocketOptions(std::shared_ptr socket) { if (socket && socket->is_open()) { @@ -31,9 +30,9 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, uint16_t port): + std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), - m_IsReceiving (false) + m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -41,17 +40,15 @@ 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_IsReceiving (false) + m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target,std::shared_ptr sslCtx): - I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsReceiving (false) + std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): + I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), + m_RemoteEndpoint (target), m_IsQuiet (quiet) { - m_Socket = std::make_shared (owner->GetService ()); - if (sslCtx) - m_SSL = std::make_shared > (*m_Socket, *sslCtx); } I2PTunnelConnection::~I2PTunnelConnection () @@ -71,7 +68,7 @@ namespace client Receive (); } - boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) + static boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; @@ -81,53 +78,32 @@ 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, bool isV4) + static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) { - if (sock) - { - // bind to 127.x.x.x address for ipv4 - // where x.x.x are first three bytes from ident - // 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) - LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); - } + // bind to 127.x.x.x address + // where x.x.x are first three bytes from ident + auto ourIP = GetLoopbackAddressFor(addr); + boost::system::error_code ec; + sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); + if (ec) + LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); + } #endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { + I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { - I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ - if (isUniqueLocal && m_RemoteEndpoint.address ().is_loopback ()) - { + if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && + m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) + { + m_Socket->open (boost::asio::ip::tcp::v4 ()); auto ident = m_Stream->GetRemoteIdentity()->GetIdentHash(); - 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); - } + MapToLoopback(m_Socket, ident); } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, @@ -154,7 +130,6 @@ namespace client void I2PTunnelConnection::Terminate () { if (Kill()) return; - if (m_SSL) m_SSL = nullptr; if (m_Stream) { m_Stream->Close (); @@ -169,29 +144,13 @@ 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, 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, bufSize), - std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } - void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - m_IsReceiving = false; if (ecode) { if (ecode != boost::asio::error::operation_aborted) @@ -201,41 +160,16 @@ 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 = shared_from_this ()](const boost::system::error_code& ecode) + [s](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); @@ -264,7 +198,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_STREAM_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); @@ -272,7 +206,7 @@ namespace client else // closed by peer { // get remaining data - auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE); + auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); else // no more data @@ -304,12 +238,8 @@ namespace client void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { - if (m_SSL) - boost::asio::async_write (*m_SSL, boost::asio::buffer (buf, len), boost::asio::transfer_all (), - std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); - else - boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), - std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), + std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) @@ -322,34 +252,22 @@ namespace client else { LogPrint (eLogDebug, "I2PTunnel: Connected"); - if (m_SSL) - m_SSL->async_handshake (boost::asio::ssl::stream_base::client, - std::bind (&I2PTunnelConnection::HandleHandshake, shared_from_this (), std::placeholders::_1)); + if (m_IsQuiet) + StreamReceive (); else - Established (); + { + // send destination first like received from I2P + std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); + dest += "\n"; + if(sizeof(m_StreamBuffer) >= dest.size()) { + memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); + } + HandleStreamReceive (boost::system::error_code (), dest.size ()); + } + Receive (); } } - void I2PTunnelConnection::HandleHandshake (const boost::system::error_code& ecode) - { - if (ecode) - { - LogPrint (eLogError, "I2PTunnel: Handshake error: ", ecode.message ()); - Terminate (); - } - else - { - LogPrint (eLogDebug, "I2PTunnel: SSL connected"); - Established (); - } - } - - void I2PTunnelConnection::Established () - { - StreamReceive (); - Receive (); - } - void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) @@ -389,12 +307,7 @@ namespace client } } else - { - // insert incomplete line back - m_InHeader.clear (); - m_InHeader << line; break; - } } if (endOfHeader) @@ -407,24 +320,15 @@ namespace client m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } - else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) - StreamReceive (); // read more header - else - { - LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); - Terminate (); - } } } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, - std::shared_ptr sslCtx): - I2PTunnelConnection (owner, stream, target, sslCtx), m_Host (host), m_XI2P (XI2P), - m_HeaderSent (false), m_ResponseHeaderSent (false) + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host): + I2PTunnelConnection (owner, stream, socket, target), m_Host (host), + m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) { - if (sslCtx) - SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ()); } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) @@ -436,77 +340,42 @@ namespace client m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; - bool endOfHeader = false, connection = false; + bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); - if (m_InHeader.fail ()) break; - if (!m_InHeader.eof ()) + if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { - // strip up some headers - static constexpr std::array excluded // list of excluded headers - { - "Keep-Alive:", "X-I2P" - }; - bool matched = false; - for (const auto& it: excluded) - if (boost::iequals (line.substr (0, it.length ()), it)) - { - matched = true; - break; - } - if (matched) continue; - - // replace some headers - if (!m_Host.empty () && boost::iequals (line.substr (0, 5), "Host:")) + if (m_Host.length () > 0 && !line.compare(0, 5, "Host:")) m_OutHeader << "Host: " << m_Host << "\r\n"; // override host - else if (boost::iequals (line.substr (0, 11), "Connection:")) - { - auto x = line.find("pgrade"); - if (x != std::string::npos && x && std::tolower(line[x - 1]) != 'u') // upgrade or Upgrade - m_OutHeader << line << "\n"; - else - m_OutHeader << "Connection: close\r\n"; - connection = true; - } - else // forward as is + else m_OutHeader << line << "\n"; } } else - { - // insert incomplete line back - m_InHeader.clear (); - m_InHeader << line; break; - } } if (endOfHeader) { - // add Connection if not presented - if (!connection) - m_OutHeader << "Connection: close\r\n"; // add X-I2P fields - m_OutHeader << m_XI2P; - // end of header - m_OutHeader << "\r\n"; - + 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_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); + m_From = nullptr; m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } - else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) - StreamReceive (); // read more header - else - { - LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); - Terminate (); - } } } @@ -524,13 +393,12 @@ namespace client while (!endOfHeader) { std::getline(m_InHeader, line); - if (m_InHeader.fail ()) break; - if (!m_InHeader.eof ()) + if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { - static constexpr std::array excluded // list of excluded headers + static const std::vector excluded // list of excluded headers { "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" }; @@ -546,12 +414,7 @@ namespace client } } else - { - // insert incomplete line back - m_InHeader.clear (); - m_InHeader << line; break; - } } if (endOfHeader) @@ -569,9 +432,9 @@ 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, sslCtx), m_From (stream->GetRemoteIdentity ()), + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): + I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } @@ -582,8 +445,7 @@ namespace client if (m_NeedsWebIrc) { m_NeedsWebIrc = false; - m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) - << " " << GetSocket ()->local_endpoint ().address () << std::endl; + m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; } m_InPacket.clear (); @@ -619,7 +481,7 @@ namespace client { public: I2PClientTunnelHandler (I2PClientTunnel * parent, std::shared_ptr address, - uint16_t destinationPort, std::shared_ptr socket): + int destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_Address(address), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); @@ -627,7 +489,7 @@ namespace client private: void HandleStreamRequestComplete (std::shared_ptr stream); std::shared_ptr m_Address; - uint16_t m_DestinationPort; + int m_DestinationPort; std::shared_ptr m_Socket; }; @@ -668,7 +530,7 @@ namespace client } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort): + const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { @@ -743,62 +605,49 @@ namespace client } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, - uint16_t port, std::shared_ptr localDestination, uint16_t inport, bool gzip): + int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { - m_PortDestination = localDestination->GetStreamingDestination (inport); - if (!m_PortDestination) // default destination - m_PortDestination = localDestination->CreateStreamingDestination (inport, gzip); + m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); } void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (m_Address, ec); + auto addr = boost::asio::ip::address::from_string (m_Address, ec); if (!ec) + { m_Endpoint.address (addr); + Accept (); + } else - Resolve (nullptr); - Accept (); + { + 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)); + } } void I2PServerTunnel::Stop () { - if (m_PortDestination) - m_PortDestination->ResetAcceptor (); - auto localDestination = GetLocalDestination (); - if (localDestination) - localDestination->StopAcceptingStreams (); - if (m_Resolver) - m_Resolver->cancel (); - ClearHandlers (); } - bool I2PServerTunnel::Resolve (std::shared_ptr stream) + void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + std::shared_ptr resolver) { - 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) { - for (const auto& it: endpoints) + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) { - ep = it; + ep = *it; if (!ep.address ().is_unspecified ()) { if (ep.address ().is_v4 ()) @@ -817,27 +666,27 @@ namespace client } } if (found) break; + it++; } } else { found = true; - ep = *endpoints.begin (); // first available + ep = *it; // first available } if (!found) { - LogPrint (eLogError, "I2PTunnel: Unable to resolve ", m_Address, " to compatible address"); + LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); return; } auto addr = ep.address (); - LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*endpoints.begin ()).host_name (), " has been resolved to ", addr); + LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); - if (stream) - Connect (stream); + Accept (); } else - LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address ", m_Address, ": ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) @@ -849,24 +698,13 @@ namespace client void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address(localAddress, ec); + auto addr = boost::asio::ip::address::from_string(localAddress, ec); if (!ec) m_LocalAddress.reset (new boost::asio::ip::address (addr)); else LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress); } - void I2PServerTunnel::SetSSL (bool ssl) - { - if (ssl) - { - m_SSLCtx = std::make_shared (boost::asio::ssl::context::sslv23); - m_SSLCtx->set_verify_mode(boost::asio::ssl::context::verify_none); - } - else - m_SSLCtx = nullptr; - } - void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -895,37 +733,25 @@ namespace client return; } } - 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; - } + // new connection + auto conn = CreateI2PConnection (stream); + AddHandler (conn); + if (m_LocalAddress) + conn->Connect (*m_LocalAddress); + else + conn->Connect (m_IsUniqueLocal); } } - 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 (), m_SSLCtx); + return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint ()); } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, - uint16_t port, std::shared_ptr localDestination, - const std::string& host, uint16_t inport, bool gzip): + int port, std::shared_ptr localDestination, + const std::string& host, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { @@ -933,22 +759,13 @@ namespace client std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { - 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 ()); + return std::make_shared (this, stream, + std::make_shared (GetService ()), GetEndpoint (), m_Host); } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, - uint16_t port, std::shared_ptr localDestination, - const std::string& webircpass, uint16_t inport, bool gzip): + int port, std::shared_ptr localDestination, + const std::string& webircpass, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { @@ -956,8 +773,370 @@ namespace client std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, GetEndpoint (), m_WebircPass, GetSSLCtx ()); + return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); + } + + void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) + { + std::lock_guard lock(m_SessionsMutex); + m_LastSession = ObtainUDPSession(from, toPort, fromPort); + } + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + + void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) + { + if (m_LastSession) + { + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + } + + void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + auto itr = m_Sessions.begin(); + while(itr != m_Sessions.end()) { + if(now - (*itr)->LastActivity >= delta ) + itr = m_Sessions.erase(itr); + else + ++itr; + } + } + + void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + std::vector removePorts; + for (const auto & s : m_Sessions) { + if (now - s.second->second >= delta) + removePorts.push_back(s.first); + } + for(auto port : removePorts) { + m_Sessions.erase(port); + } + } + + UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) + { + auto ih = from.GetIdentHash(); + for (auto & s : m_Sessions ) + { + if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) + { + /** found existing session */ + LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); + return s; + } + } + boost::asio::ip::address addr; + /** create new udp session */ + if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) + { + auto ident = from.GetIdentHash(); + addr = GetLoopbackAddressFor(ident); + } + else + addr = m_LocalAddress; + boost::asio::ip::udp::endpoint ep(addr, 0); + m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); + auto & back = m_Sessions.back(); + return back; + } + + UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, + uint16_t ourPort, uint16_t theirPort) : + m_Destination(localDestination->GetDatagramDestination()), + IPSocket(localDestination->GetService(), localEndpoint), + SendEndpoint(endpoint), + LastActivity(i2p::util::GetMillisecondsSinceEpoch()), + LocalPort(ourPort), + RemotePort(theirPort) + { + IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + memcpy(Identity, to->data(), 32); + Receive(); + } + + void UDPSession::Receive() + { + LogPrint(eLogDebug, "UDPSession: Receive"); + IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), + FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); + } + + void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) + { + if(!ecode) + { + LogPrint(eLogDebug, "UDPSession: Forward ", len, "B from ", FromEndpoint); + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + auto session = m_Destination->GetSession (Identity); + if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); + else + m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = IPSocket.available(ec); + if (ec || !moreBytes) break; + len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); + m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); + numPackets++; + } + if (numPackets > 0) + LogPrint(eLogDebug, "UDPSession: Forward more ", numPackets, "packets B from ", FromEndpoint); + m_Destination->FlushSendQueue (session); + LastActivity = ts; + Receive(); + } + else + LogPrint(eLogError, "UDPSession: ", ecode.message()); + } + + I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr localDestination, + boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip) : + m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), m_LocalDest (localDestination), m_RemoteEndpoint (forwardTo), m_Gzip (gzip) + { + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel () + { + Stop (); + } + + void I2PUDPServerTunnel::Start () + { + m_LocalDest->Start (); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + } + + void I2PUDPServerTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) dgram->ResetReceiver (); + } + + std::vector > I2PUDPServerTunnel::GetSessions () + { + std::vector > sessions; + std::lock_guard lock (m_SessionsMutex); + + for (UDPSessionPtr s: m_Sessions) + { + if (!s->m_Destination) continue; + auto info = s->m_Destination->GetInfoForRemote (s->Identity); + if (!info) continue; + + auto sinfo = std::make_shared (); + sinfo->Name = m_Name; + sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); + sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); + sinfo->CurrentIBGW = info->IBGW; + sinfo->CurrentOBEP = info->OBEP; + sessions.push_back (sinfo); + } + return sessions; + } + + I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + boost::asio::ip::udp::endpoint localEndpoint, + std::shared_ptr localDestination, + uint16_t remotePort, bool gzip) : + m_Name (name), m_RemoteDest (remoteDest), m_LocalDest (localDestination), m_LocalEndpoint (localEndpoint), + m_RemoteIdent (nullptr), m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), + m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip) + { + } + + I2PUDPClientTunnel::~I2PUDPClientTunnel () + { + Stop (); + } + + void I2PUDPClientTunnel::Start () + { + // Reset flag in case of tunnel reload + if (m_cancel_resolve) m_cancel_resolve = false; + + m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); + m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); + m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5)); + dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + + m_LocalDest->Start (); + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); + RecvFromLocal (); + } + + void I2PUDPClientTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) dgram->ResetReceiver (); + m_cancel_resolve = true; + + m_Sessions.clear(); + + if(m_LocalSocket && m_LocalSocket->is_open ()) + m_LocalSocket->close (); + + if(m_ResolveThread) + { + m_ResolveThread->join (); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + if (m_RemoteIdent) + { + delete m_RemoteIdent; + m_RemoteIdent = nullptr; + } + } + + void I2PUDPClientTunnel::RecvFromLocal () + { + m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), + m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); + } + + void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) + { + if (m_cancel_resolve) { + LogPrint (eLogDebug, "UDP Client: Ignoring incomming data: stopping"); + return; + } + if (ec) { + LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); + RecvFromLocal (); // Restart listener and continue work + return; + } + if (!m_RemoteIdent) { + LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); + RecvFromLocal (); + return; // drop, remote not resolved + } + auto remotePort = m_RecvEndpoint.port (); + if (!m_LastPort || m_LastPort != remotePort) + { + auto itr = m_Sessions.find (remotePort); + if (itr != m_Sessions.end ()) + m_LastSession = itr->second; + else + { + m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); + m_Sessions.emplace (remotePort, m_LastSession); + } + m_LastPort = remotePort; + } + // send off to remote i2p destination + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteIdent->ToBase32 (), ":", RemotePort); + auto session = m_LocalDest->GetDatagramDestination ()->GetSession (*m_RemoteIdent); + if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + else + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = m_LocalSocket->available (ec); + if (ec || !moreBytes) break; + transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); + remotePort = m_RecvEndpoint.port (); + // TODO: check remotePort + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + numPackets++; + } + if (numPackets) + LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32 ()); + m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); + + // mark convo as active + if (m_LastSession) + m_LastSession->second = ts; + RecvFromLocal (); + } + + std::vector > I2PUDPClientTunnel::GetSessions () + { + // TODO: implement + std::vector > infos; + return infos; + } + + void I2PUDPClientTunnel::TryResolving () + { + i2p::util::SetThreadName ("UDP Resolver"); + LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + + std::shared_ptr addr; + while (!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) + { + LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); + std::this_thread::sleep_for (std::chrono::seconds (1)); + } + if (m_cancel_resolve) + { + LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); + return; + } + if (!addr || !addr->IsIdentHash ()) + { + LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); + return; + } + m_RemoteIdent = new i2p::data::IdentHash; + *m_RemoteIdent = addr->identHash; + LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) + HandleRecvFromI2PRaw (fromPort, toPort, buf, len); + else + LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + auto itr = m_Sessions.find (toPort); + // found convo ? + if (itr != m_Sessions.end ()) + { + // found convo + if (len > 0) + { + LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32 () : ""); + m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); + // mark convo as active + itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); + } + } + else + LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); } } } - diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 00b95726..447a345d 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,15 +11,14 @@ #include #include -#include #include #include #include #include #include -#include #include "Identity.h" #include "Destination.h" +#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" @@ -28,28 +27,24 @@ namespace i2p { namespace client { - 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 + 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 // for HTTP tunnels - 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; + 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 class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, uint16_t port = 0); // to I2P + std::shared_ptr leaseSet, int 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, - std::shared_ptr sslCtx = nullptr); // from I2P + I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (bool isUniqueLocal = true); @@ -57,35 +52,27 @@ namespace client protected: - virtual void Established (); void Terminate (); void Receive (); - void StreamReceive (); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded + void HandleWrite (const boost::system::error_code& ecode); virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded - 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 StreamReceive (); + void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleConnect (const boost::system::error_code& ecode); - void HandleHandshake (const boost::system::error_code& ecode); - void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleWrite (const boost::system::error_code& ecode); - + + std::shared_ptr GetSocket () const { return m_Socket; }; + private: - uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE]; + uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; - std::shared_ptr > m_SSL; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; - bool m_IsReceiving; + bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection @@ -99,7 +86,7 @@ namespace client protected: - void Write (const uint8_t * buf, size_t len) override; + void Write (const uint8_t * buf, size_t len); private: @@ -112,19 +99,20 @@ namespace client public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, const std::string& host, const std::string& XI2P, - std::shared_ptr sslCtx = nullptr); + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host); protected: - void Write (const uint8_t * buf, size_t len) override; - void WriteToStream (const uint8_t * buf, size_t len) override; + void Write (const uint8_t * buf, size_t len); + void WriteToStream (const uint8_t * buf, size_t len); private: - std::string m_Host, m_XI2P; + std::string m_Host; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ResponseHeaderSent; + std::shared_ptr m_From; }; class I2PTunnelConnectionIRC: public I2PTunnelConnection @@ -132,12 +120,12 @@ namespace client public: I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass, - std::shared_ptr sslCtx = nullptr); + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); protected: - void Write (const uint8_t * buf, size_t len) override; + void Write (const uint8_t * buf, size_t len); private: @@ -158,7 +146,7 @@ namespace client public: I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort = 0); + const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); @@ -178,17 +166,173 @@ namespace client std::string m_Name, m_Destination; std::shared_ptr m_Address; - uint16_t m_DestinationPort; + int m_DestinationPort; uint32_t m_KeepAliveInterval; std::unique_ptr m_KeepAliveTimer; }; + + /** 2 minute timeout for udp sessions */ + const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; + const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds + + /** max size for i2p udp */ + const size_t I2P_UDP_MAX_MTU = 64*1024; + + struct UDPSession + { + i2p::datagram::DatagramDestination * m_Destination; + boost::asio::ip::udp::socket IPSocket; + i2p::data::IdentHash Identity; + boost::asio::ip::udp::endpoint FromEndpoint; + boost::asio::ip::udp::endpoint SendEndpoint; + uint64_t LastActivity; + + uint16_t LocalPort; + uint16_t RemotePort; + + uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + + UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, + uint16_t ourPort, uint16_t theirPort); + void HandleReceived(const boost::system::error_code & ecode, std::size_t len); + void Receive(); + }; + + + /** read only info about a datagram session */ + struct DatagramSessionInfo + { + /** the name of this forward */ + std::string Name; + /** ident hash of local destination */ + std::shared_ptr LocalIdent; + /** ident hash of remote destination */ + std::shared_ptr RemoteIdent; + /** ident hash of IBGW in use currently in this session or nullptr if none is set */ + std::shared_ptr CurrentIBGW; + /** ident hash of OBEP in use for this session or nullptr if none is set */ + std::shared_ptr CurrentOBEP; + /** i2p router's udp endpoint */ + boost::asio::ip::udp::endpoint LocalEndpoint; + /** client's udp endpoint */ + boost::asio::ip::udp::endpoint RemoteEndpoint; + /** how long has this converstation been idle in ms */ + uint64_t idle; + }; + + typedef std::shared_ptr UDPSessionPtr; + + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ + class I2PUDPServerTunnel + { + public: + + I2PUDPServerTunnel (const std::string & name, + std::shared_ptr localDestination, + boost::asio::ip::address localAddress, + boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); + ~I2PUDPServerTunnel (); + + /** expire stale udp conversations */ + void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + void Start (); + void Stop (); + const char * GetName () const { return m_Name.c_str(); } + std::vector > GetSessions (); + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + + void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } + + private: + + void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + + private: + + bool m_IsUniqueLocal; + const std::string m_Name; + boost::asio::ip::address m_LocalAddress; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + std::mutex m_SessionsMutex; + std::vector m_Sessions; + std::shared_ptr m_LocalDest; + UDPSessionPtr m_LastSession; + bool m_Gzip; + + public: + + bool isUpdated; // transient, used during reload only + }; + + class I2PUDPClientTunnel + { + public: + + I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, + uint16_t remotePort, bool gzip); + ~I2PUDPClientTunnel (); + + void Start (); + void Stop (); + const char * GetName () const { return m_Name.c_str(); } + std::vector > GetSessions (); + + bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } + + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + inline void SetLocalDestination (std::shared_ptr dest) + { + if (m_LocalDest) m_LocalDest->Release (); + if (dest) dest->Acquire (); + m_LocalDest = dest; + } + + void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + + private: + + typedef std::pair UDPConvo; + void RecvFromLocal (); + void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); + void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void TryResolving (); + + private: + + const std::string m_Name; + std::mutex m_SessionsMutex; + std::unordered_map > m_Sessions; // maps i2p port -> local udp convo + const std::string m_RemoteDest; + std::shared_ptr m_LocalDest; + const boost::asio::ip::udp::endpoint m_LocalEndpoint; + i2p::data::IdentHash * m_RemoteIdent; + std::thread * m_ResolveThread; + std::unique_ptr m_LocalSocket; + boost::asio::ip::udp::endpoint m_RecvEndpoint; + uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; + uint16_t RemotePort, m_LastPort; + bool m_cancel_resolve; + bool m_Gzip; + std::shared_ptr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only + }; + class I2PServerTunnel: public I2PService { public: - I2PServerTunnel (const std::string& name, const std::string& address, uint16_t port, - std::shared_ptr localDestination, uint16_t inport = 0, bool gzip = true); + I2PServerTunnel (const std::string& name, const std::string& address, int port, + std::shared_ptr localDestination, int inport = 0, bool gzip = true); void Start (); void Stop (); @@ -198,13 +342,10 @@ namespace client void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } - void SetSSL (bool ssl); - std::shared_ptr GetSSLCtx () const { return m_SSLCtx; }; - void SetLocalAddress (const std::string& localAddress); const std::string& GetAddress() const { return m_Address; } - uint16_t GetPort () const { return m_Port; }; + int GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } @@ -212,36 +353,32 @@ namespace client private: - 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 HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + std::shared_ptr resolver); 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; - uint16_t m_Port; + int 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, uint16_t port, + I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, - uint16_t inport = 0, bool gzip = true); + int inport = 0, bool gzip = true); private: @@ -249,17 +386,16 @@ namespace client private: - std::string m_Host, m_XI2P; - std::weak_ptr m_From; + std::string m_Host; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: - I2PServerTunnelIRC (const std::string& name, const std::string& address, uint16_t port, + I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, - uint16_t inport = 0, bool gzip = true); + int inport = 0, bool gzip = true); private: @@ -269,8 +405,6 @@ namespace client std::string m_WebircPass; }; - - boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr); } } diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index 40752f5b..ce800ecc 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -72,9 +72,8 @@ namespace client bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); - if(!pool || !pool->StandardSelectPeers(path, hops, inbound, - std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3))) + if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, + std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1, std::placeholders::_2))) return false; // more here for outbound tunnels if(!inbound && m_RemoteLeaseSet) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 80987c78..fc56b2c9 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #ifdef _MSC_VER #include #endif -#include #include "Base.h" #include "Identity.h" #include "Log.h" @@ -26,9 +25,9 @@ namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), - m_BufferOffset (0), m_SocketType (SAMSocketType::eSAMSocketTypeUnknown), - m_IsSilent (false), m_IsAccepting (false), m_IsReceiving (false), - m_Version (MIN_SAM_VERSION) + m_BufferOffset (0), + m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), + m_IsAccepting (false), m_Stream (nullptr) { } @@ -44,27 +43,29 @@ namespace client m_Stream->AsyncClose (); m_Stream = nullptr; } + auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { - case SAMSocketType::eSAMSocketTypeSession: + case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; - case SAMSocketType::eSAMSocketTypeStream: - break; - case SAMSocketType::eSAMSocketTypeAcceptor: - case SAMSocketType::eSAMSocketTypeForward: + case eSAMSocketTypeStream: { - auto session = m_Owner.FindSession(m_ID); - if (session) + break; + } + case eSAMSocketTypeAcceptor: + case eSAMSocketTypeForward: + { + if (Session) { - if (m_IsAccepting && session->GetLocalDestination ()) - session->GetLocalDestination ()->StopAcceptingStreams (); + if (m_IsAccepting && Session->GetLocalDestination ()) + Session->GetLocalDestination ()->StopAcceptingStreams (); } break; } default: ; } - m_SocketType = SAMSocketType::eSAMSocketTypeTerminated; + m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open ()) { boost::system::error_code ec; @@ -81,26 +82,21 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } - static int ExtractVersion (std::string_view ver) + static bool SAMVersionAcceptable(const std::string & ver) { - int version = 0; - for (auto ch: ver) - { - if (ch >= '0' && ch <= '9') - { - version *= 10; - version += (ch - '0'); - } - } - return version; - } + return ver == "3.0" || ver == "3.1"; + } - static std::string CreateVersion (int ver) + static bool SAMVersionTooLow(const std::string & ver) { - auto d = div (ver, 10); - return std::to_string (d.quot) + "." + std::to_string (d.rem); - } - + return ver.size() && ver[0] < '3'; + } + + static bool SAMVersionTooHigh(const std::string & ver) + { + return ver.size() && ver > "3.1"; + } + void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -126,41 +122,49 @@ namespace client if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { - int minVer = 0, maxVer = 0; + std::string maxver("3.1"); + std::string minver("3.0"); // try to find MIN and MAX, 3.0 if not found if (separator) { separator++; - auto params = ExtractParams (separator); + std::map params; + ExtractParams (separator, params); auto it = params.find (SAM_PARAM_MAX); if (it != params.end ()) - maxVer = ExtractVersion (it->second); + maxver = it->second; it = params.find(SAM_PARAM_MIN); if (it != params.end ()) - minVer = ExtractVersion (it->second); + minver = it->second; } // version negotiation - 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 + 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)) { - 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 ()); + 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, CreateVersion (m_Version).c_str ()); + 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)); + 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 + SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true); } else { @@ -170,7 +174,7 @@ namespace client } } - bool SAMSocket::IsSession(std::string_view id) const + bool SAMSocket::IsSession(const std::string & id) const { return id == m_ID; } @@ -191,12 +195,12 @@ namespace client } } - void SAMSocket::SendMessageReply (std::string_view msg, bool close) + void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent || m_SocketType == SAMSocketType::eSAMSocketTypeForward) - boost::asio::async_write (m_Socket, boost::asio::buffer (msg.data (), msg.size ()), boost::asio::transfer_all (), + if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) + boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else @@ -233,7 +237,7 @@ namespace client if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } - else if (m_SocketType == SAMSocketType::eSAMSocketTypeStream) + else if (m_SocketType == eSAMSocketTypeStream) HandleReceived (ecode, bytes_transferred); else { @@ -248,40 +252,28 @@ 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, l }); + ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); 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, l }); + ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_FORWARD)) - ProcessStreamForward ({ separator + 1, l }); + ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) - ProcessDestGenerate ({ separator + 1, l }); + ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) - ProcessNamingLookup ({ separator + 1, l }); + ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_SESSION_ADD)) - ProcessSessionAdd ({ separator + 1, l }); + ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE)) - ProcessSessionRemove ({ separator + 1, l }); + ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; @@ -313,6 +305,7 @@ namespace client Terminate ("malformed message"); } } + else { LogPrint (eLogWarning, "SAM: Incomplete message ", bytes_transferred); @@ -323,7 +316,7 @@ namespace client } } - static bool IsAcceptableSessionName(std::string_view str) + static bool IsAcceptableSessionName(const std::string & str) { auto itr = str.begin(); while(itr != str.end()) @@ -336,88 +329,60 @@ namespace client return true; } - void SAMSocket::ProcessSessionCreate (std::string_view buf) + void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Session create: ", buf); - 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]; + 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]; if(!IsAcceptableSessionName(id)) { // invalid session id - SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, true); + SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, strlen(SAM_SESSION_CREATE_INVALID_ID), true); return; } m_ID = id; if (m_Owner.FindSession (id)) { // session exists - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, true); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true); return; } - 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) + 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) { // unknown style - SendSessionI2PError("Unknown STYLE"); + SendI2PError("Unknown STYLE"); return; } std::shared_ptr forward = nullptr; - if ((type == SAMSessionType::eSAMSessionTypeDatagram || type == SAMSessionType::eSAMSessionTypeRaw) && + if ((type == eSAMSessionTypeDatagram || type == 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::make_address(params[SAM_PARAM_HOST], e); + auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e); if (e) { // not an ip address - SendSessionI2PError("Invalid IP Address in HOST"); + SendI2PError("Invalid IP Address in HOST"); return; } - 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()) + auto port = std::stoi(params[SAM_PARAM_PORT]); + if (port == -1) { - SendSessionI2PError("Invalid port"); + SendI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); @@ -426,7 +391,7 @@ namespace client //ensure we actually received a destination if (destination.empty()) { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, true); + SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); return; } @@ -436,37 +401,26 @@ namespace client i2p::data::PrivateKeys keys; if (!keys.FromBase64(destination)) { - SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, true); + SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); return; } } // create destination - auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, params); + auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); if (session) { - m_SocketType = SAMSocketType::eSAMSocketTypeSession; - if (type == SAMSessionType::eSAMSessionTypeDatagram || type == SAMSessionType::eSAMSessionTypeRaw) + m_SocketType = eSAMSocketTypeSession; + if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) { session->UDPEndpoint = forward; - 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) + auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); + if (type == eSAMSessionTypeDatagram) dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - port - ); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); else // raw dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - port - ); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } if (session->GetLocalDestination ()->IsReady ()) @@ -479,30 +433,25 @@ namespace client } } else - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, true); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true); } void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { - if (m_Socket.is_open ()) + auto session = m_Owner.FindSession(m_ID); + if(session) { - auto session = m_Owner.FindSession(m_ID); - if(session) + if (session->GetLocalDestination ()->IsReady ()) + SendSessionCreateReplyOk (); + else { - if (session->GetLocalDestination ()->IsReady ()) - SendSessionCreateReplyOk (); - else - { - m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); - m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, - shared_from_this (), std::placeholders::_1)); - } + m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); + m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, + shared_from_this (), std::placeholders::_1)); } } - else - Terminate ("SAM: session socket closed"); } } @@ -511,28 +460,33 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - std::string priv = session->GetLocalDestination ()->GetPrivateKeys ().ToBase64 (); + 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; #ifdef _MSC_VER - size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); + size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); #else - size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv.c_str ()); + size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); #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 != SAMSocketType::eSAMSocketTypeUnknown) + if ( m_SocketType != eSAMSocketTypeUnknown) { - SendSessionI2PError ("Socket already in use"); + SendI2PError ("Socket already in use"); return; } - 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]; + 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]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); @@ -564,20 +518,15 @@ namespace client { if (addr->IsIdentHash ()) { - if (session->GetLocalDestination ()->GetIdentHash () != addr->identHash) - { - 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)); - } - } + auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash); + if (leaseSet) + Connect(leaseSet, session); else - SendStreamCantReachPeer ("Can't connect to myself"); + { + session->GetLocalDestination ()->RequestDestination(addr->identHash, + std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, + shared_from_this(), std::placeholders::_1)); + } } else // B33 session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, @@ -585,10 +534,10 @@ namespace client shared_from_this(), std::placeholders::_1)); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::Connect (std::shared_ptr remote, std::shared_ptr session) @@ -596,25 +545,20 @@ namespace client if (!session) session = m_Owner.FindSession(m_ID); if (session) { - if (session->GetLocalDestination ()->SupportsEncryptionType (remote->GetEncryptionType ())) + m_SocketType = eSAMSocketTypeStream; + m_Stream = session->GetLocalDestination ()->CreateStream (remote); + if (m_Stream) { - 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); + m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send + m_BufferOffset = 0; + I2PReceive (); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else - SendStreamCantReachPeer ("Incompatible crypto"); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) @@ -624,157 +568,92 @@ namespace client else { LogPrint (eLogError, "SAM: Destination to connect not found"); - SendStreamCantReachPeer ("LeaseSet not found"); + SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } - void SAMSocket::ProcessStreamAccept (std::string_view buf) + void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Stream accept: ", buf); - if ( m_SocketType != SAMSocketType::eSAMSocketTypeUnknown) + if ( m_SocketType != eSAMSocketTypeUnknown) { - SendSessionI2PError ("Socket already in use"); + SendI2PError ("Socket already in use"); return; } - auto params = ExtractParams (buf); - std::string_view id = params[SAM_PARAM_ID]; - std::string_view silent = params[SAM_PARAM_SILENT]; + std::map params; + ExtractParams (buf, params); + std::string& id = params[SAM_PARAM_ID]; + std::string& 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 = SAMSocketType::eSAMSocketTypeAcceptor; + m_SocketType = 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)); } - 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"); - } - } + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::ProcessStreamForward (std::string_view buf) + void SAMSocket::ProcessStreamForward (char * buf, size_t len) { - 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); + 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); if (!session) { - SendMessageReply(SAM_STREAM_STATUS_INVALID_ID, true); + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); return; } - if (session->GetLocalDestination()->IsAcceptingStreams()) + if (session->GetLocalDestination ()->IsAcceptingStreams ()) { - SendSessionI2PError("Already accepting"); + SendI2PError ("Already accepting"); return; } - - const auto itPort = params.find(SAM_PARAM_PORT); - if (itPort == params.end()) + auto it = params.find (SAM_PARAM_PORT); + if (it == params.end ()) { - SendSessionI2PError("PORT is missing"); + SendI2PError ("PORT is missing"); return; } - - std::string_view portStr = itPort->second; - if (!std::all_of(portStr.begin(), portStr.end(), ::isdigit)) + auto port = std::stoi (it->second); + if (port <= 0 || port >= 0xFFFF) { - SendSessionI2PError("Port must be numeric"); + SendI2PError ("Invalid PORT"); return; } - - uint16_t port = 0; - auto res = std::from_chars(portStr.data(), portStr.data() + portStr.size(), port); - if (res.ec != std::errc()) + boost::system::error_code ec; + auto ep = m_Socket.remote_endpoint (ec); + if (ec) { - SendSessionI2PError("Invalid port"); + SendI2PError ("Socket error"); return; } - - 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; + ep.port (port); + m_SocketType = eSAMSocketTypeForward; m_ID = id; m_IsAccepting = true; - - 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); + 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); } - size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { LogPrint (eLogDebug, "SAM: Datagram send: ", buf, " ", len); - 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; + std::map params; + ExtractParams (buf, params); + size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; if (offset + size <= len) { auto session = m_Owner.FindSession(m_ID); @@ -785,7 +664,7 @@ namespace client { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); - if (session->Type == SAMSessionType::eSAMSessionTypeDatagram) + if (session->Type == eSAMSessionTypeDatagram) d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); else // raw d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); @@ -804,10 +683,11 @@ namespace client return offset + size; } - void SAMSocket::ProcessDestGenerate (std::string_view buf) + void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Dest generate"); - auto params = ExtractParams (buf); + std::map params; + ExtractParams (buf, params); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; @@ -822,14 +702,14 @@ namespace client { try { - cryptoType = std::stoi(std::string (it->second)); + cryptoType = std::stoi(it->second); } catch (const std::exception& ex) { LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); + auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); @@ -837,14 +717,15 @@ 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 (std::string_view buf) + void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Naming lookup: ", buf); - auto params = ExtractParams (buf); - std::string name (params[SAM_PARAM_NAME]); + std::map params; + ExtractParams (buf, params); + std::string& name = params[SAM_PARAM_NAME]; std::shared_ptr identity; std::shared_ptr addr; auto session = m_Owner.FindSession(m_ID); @@ -878,122 +759,86 @@ 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 (std::string_view buf) + void SAMSocket::ProcessSessionAdd (char * buf, size_t len) { - 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 == SAMSessionType::eSAMSessionTypeMaster) + if (session && session->Type == eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: Subsession add: ", buf); auto masterSession = std::static_pointer_cast(session); - auto params = ExtractParams (buf); - std::string_view id = params[SAM_PARAM_ID]; + std::map params; + ExtractParams (buf, params); + std::string& id = params[SAM_PARAM_ID]; if (masterSession->subsessions.count (id) > 1) { // session exists - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, false); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); return; } - std::string_view style = params[SAM_PARAM_STYLE]; - SAMSessionType type = SAMSessionType::eSAMSessionTypeUnknown; - if (style == SAM_VALUE_STREAM) type = SAMSessionType::eSAMSessionTypeStream; + std::string& style = params[SAM_PARAM_STYLE]; + SAMSessionType type = eSAMSessionTypeUnknown; + if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; // TODO: implement other styles - if (type == SAMSessionType::eSAMSessionTypeUnknown) + if (type == eSAMSessionTypeUnknown) { // unknown style - SendSessionI2PError("Unsupported STYLE"); + SendI2PError("Unsupported STYLE"); return; } - uint16_t fromPort = 0; - auto it = params.find (SAM_PARAM_FROM_PORT); - if (it != params.end ()) + auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); + if (fromPort == -1) { - 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; - } + SendI2PError("Invalid from port"); + return; } auto subsession = std::make_shared(masterSession, id, type, fromPort); if (m_Owner.AddSession (subsession)) { - masterSession->subsessions.insert (std::string (id)); + masterSession->subsessions.insert (id); SendSessionCreateReplyOk (); } else - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, false); + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); } else - SendSessionI2PError ("Wrong session type"); + SendI2PError ("Wrong session type"); } - void SAMSocket::ProcessSessionRemove (std::string_view buf) + void SAMSocket::ProcessSessionRemove (char * buf, size_t len) { - 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 == SAMSessionType::eSAMSessionTypeMaster) + if (session && session->Type == eSAMSessionTypeMaster) { LogPrint (eLogDebug, "SAM: Subsession remove: ", buf); auto masterSession = std::static_pointer_cast(session); - auto params = ExtractParams (buf); - std::string id(params[SAM_PARAM_ID]); + std::map params; + ExtractParams (buf, params); + std::string& id = params[SAM_PARAM_ID]; if (!masterSession->subsessions.erase (id)) { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, false); + SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), false); return; } m_Owner.CloseSession (id); SendSessionCreateReplyOk (); } else - SendSessionI2PError ("Wrong session type"); + SendI2PError ("Wrong session type"); } - 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) + void SAMSocket::SendI2PError(const std::string & msg) { + LogPrint (eLogError, "SAM: I2P error: ", msg); #ifdef _MSC_VER - size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); + size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #else - size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); + size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #endif - SendMessageReply ({m_Buffer, len}, true); - } - - void SAMSocket::SendSessionI2PError(const std::string & msg) - { - LogPrint (eLogError, "SAM: Session I2P error: ", msg); - SendReplyWithMessage (SAM_SESSION_STATUS_I2P_ERROR, msg); - } - - void SAMSocket::SendStreamI2PError(const std::string & msg) - { - LogPrint (eLogError, "SAM: Stream I2P error: ", msg); - SendReplyWithMessage (SAM_STREAM_STATUS_I2P_ERROR, msg); - } - - void SAMSocket::SendStreamCantReachPeer(const std::string & msg) - { - SendReplyWithMessage (SAM_STREAM_STATUS_CANT_REACH_PEER, msg); + SendMessageReply (m_Buffer, len, true); } void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name) @@ -1011,7 +856,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); } } @@ -1023,59 +868,37 @@ 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); } - const std::map SAMSocket::ExtractParams (std::string_view buf) + void SAMSocket::ExtractParams (char * buf, std::map& params) { - 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; - } - else + char * separator; + do + { + separator = strchr (buf, ' '); + if (separator) *separator = 0; + char * value = strchr (buf, '='); + if (value) { - field = buf.substr (pos); - pos = buf.length (); + *value = 0; + value++; + params[buf] = value; } - auto value = field.find ('='); - if (value != std::string_view::npos) - params.emplace (field.substr (0, value), field.substr (value + 1)); + buf = separator + 1; } - return params; + while (separator); } void SAMSocket::Receive () { - 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)); + 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)); } 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 ()); @@ -1085,13 +908,16 @@ 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"); + } } } @@ -1102,16 +928,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_STREAM_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_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_STREAM_BUFFER_SIZE]; + uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; // get remaining data - auto len = m_Stream->ReadSome (buff, SAM_STREAM_BUFFER_SIZE); + auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { WriteI2PDataImmediate(buff, len); @@ -1162,18 +988,18 @@ namespace client else { auto s = shared_from_this (); - boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error"); }); + m_Owner.GetService ().post ([s] { s->Terminate ("stream read error"); }); } } else { auto s = shared_from_this (); - boost::asio::post (m_Owner.GetService (), [s] { s->Terminate ("stream read error (op aborted)"); }); + m_Owner.GetService ().post ([s] { s->Terminate ("stream read error (op aborted)"); }); } } else { - if (m_SocketType != SAMSocketType::eSAMSocketTypeTerminated) + if (m_SocketType != eSAMSocketTypeTerminated) { if (bytes_transferred > 0) { @@ -1204,47 +1030,35 @@ namespace client if (stream) { LogPrint (eLogDebug, "SAM: Incoming I2P connection for session ", m_ID); - m_SocketType = SAMSocketType::eSAMSocketTypeStream; + m_SocketType = eSAMSocketTypeStream; m_IsAccepting = false; m_Stream = stream; context.GetAddressBook ().InsertFullAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); - if (session && !session->acceptQueue.empty ()) + if (session) { - // 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) + // find more pending acceptors + for (auto & it: m_Owner.ListSockets (m_ID)) + if (it->m_SocketType == eSAMSocketTypeAcceptor) { - socket->m_IsAccepting = true; - session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, socket, std::placeholders::_1)); + it->m_IsAccepting = true; + session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); + break; } - } } if (!m_IsSilent) - { - 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); - }); - } + { + // 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 } else I2PReceive (); @@ -1260,7 +1074,7 @@ namespace client { LogPrint (eLogDebug, "SAM: Incoming forward I2P connection for session ", m_ID); auto newSocket = std::make_shared(m_Owner); - newSocket->SetSocketType (SAMSocketType::eSAMSocketTypeStream); + newSocket->SetSocketType (eSAMSocketTypeStream); auto s = shared_from_this (); newSocket->GetSocket ().async_connect (ep, [s, newSocket, stream](const boost::system::error_code& ecode) @@ -1308,11 +1122,11 @@ namespace client else { #ifdef _MSC_VER - size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #endif - if (len < SAM_STREAM_BUFFER_SIZE - l) + if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); @@ -1336,11 +1150,11 @@ namespace client else { #ifdef _MSC_VER - size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); + size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); + size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #endif - if (len < SAM_STREAM_BUFFER_SIZE - l) + if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); @@ -1353,10 +1167,10 @@ namespace client void SAMSocket::HandleStreamSend(const boost::system::error_code & ec) { - boost::asio::post (m_Owner.GetService (), std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); + m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); } - SAMSession::SAMSession (SAMBridge & parent, std::string_view id, SAMSessionType type): + SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) { } @@ -1369,7 +1183,7 @@ namespace client } } - SAMSingleSession::SAMSingleSession (SAMBridge & parent, std::string_view name, SAMSessionType type, std::shared_ptr dest): + SAMSingleSession::SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest): SAMSession (parent, name, type), localDestination (dest) { @@ -1398,12 +1212,11 @@ namespace client subsessions.clear (); } - SAMSubSession::SAMSubSession (std::shared_ptr master, std::string_view name, SAMSessionType type, uint16_t port): + SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port): SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port) - { - if (Type == SAMSessionType::eSAMSessionTypeStream && port) + { + if (Type == eSAMSessionTypeStream) { - // additional streaming destination, use default if port is 0 auto d = masterSession->GetLocalDestination ()->CreateStreamingDestination (inPort); if (d) d->Start (); } @@ -1418,7 +1231,7 @@ namespace client void SAMSubSession::StopLocalDestination () { auto dest = GetLocalDestination (); - if (dest && Type == SAMSessionType::eSAMSessionTypeStream) + if (dest && Type == eSAMSessionTypeStream) { auto d = dest->RemoveStreamingDestination (inPort); if (d) d->Stop (); @@ -1426,10 +1239,10 @@ namespace client // TODO: implement datagrams } - SAMBridge::SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread): + SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): RunnableService ("SAM"), m_IsSingleThread (singleThread), - 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_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_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, @@ -1468,14 +1281,12 @@ namespace client LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); } - decltype(m_Sessions) sessions; { std::unique_lock l(m_SessionsMutex); - m_Sessions.swap (sessions); - } - for (auto& it: sessions) - it.second->Close (); - + for (auto& it: m_Sessions) + it.second->Close (); + m_Sessions.clear (); + } StopIOService (); } @@ -1520,44 +1331,37 @@ namespace client Accept (); } - std::shared_ptr SAMBridge::CreateSession (std::string_view id, SAMSessionType type, - std::string_view destination, const std::map& params) + std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, + const std::string& 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, &p) : - i2p::client::context.CreateNewLocalDestination (keys, true, &p); + i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : + i2p::client::context.CreateNewLocalDestination (keys, true, params); } 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.empty ()) + if (params) { - 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(std::string (it->second)); + cryptoType = std::stoi(it->second); } catch (const std::exception& ex) { @@ -1566,16 +1370,16 @@ namespace client } } localDestination = m_IsSingleThread ? - i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, &p) : - i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, &p); + i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : + i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { localDestination->Acquire (); - auto session = (type == SAMSessionType::eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : + auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); - auto ret = m_Sessions.emplace (id, session); + auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; @@ -1590,7 +1394,7 @@ namespace client return ret.second; } - void SAMBridge::CloseSession (std::string_view id) + void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; { @@ -1607,43 +1411,18 @@ namespace client session->StopLocalDestination (); session->Close (); if (m_IsSingleThread) - ScheduleSessionCleanupTimer (session); // let all session's streams close + { + 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 + }); + } } } - 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::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); @@ -1652,7 +1431,7 @@ namespace client return nullptr; } - std::list > SAMBridge::ListSockets(std::string_view id) const + std::list > SAMBridge::ListSockets(const std::string & id) const { std::list > list; { @@ -1699,21 +1478,14 @@ namespace client auto session = FindSession (sessionID); if (session) { - auto localDest = session->GetLocalDestination (); - auto datagramDest = localDest ? localDest->GetDatagramDestination () : nullptr; - if (datagramDest) - { - i2p::data::IdentityEx dest; - dest.FromBase64 (destination); - if (session->Type == SAMSessionType::eSAMSessionTypeDatagram) - datagramDest->SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); - 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); - } - else - LogPrint (eLogError, "SAM: Datagram destination is not set for session ", sessionID); + i2p::data::IdentityEx dest; + dest.FromBase64 (destination); + if (session->Type == eSAMSessionTypeDatagram) + session->GetLocalDestination ()->GetDatagramDestination ()-> + SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); + else // raw + session->GetLocalDestination ()->GetDatagramDestination ()-> + SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); @@ -1732,23 +1504,25 @@ namespace client LogPrint (eLogError, "SAM: Datagram receive error: ", ecode.message ()); } - bool SAMBridge::ResolveSignatureType (std::string_view name, i2p::data::SigningKeyType& type) const + bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const { - auto res = std::from_chars(name.data(), name.data() + name.size(), type); - if (res.ec != std::errc()) + try { - 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; - } + 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; 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 fd73003f..88990d7c 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -30,32 +29,27 @@ 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 int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds const char SAM_HANDSHAKE[] = "HELLO VERSION"; const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n"; - constexpr std::string_view SAM_HANDSHAKE_NOVERSION { "HELLO REPLY RESULT=NOVERSION\n" }; + const char 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"; - 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_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"; 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"; - 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_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; + const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; + const char SAM_STREAM_STATUS_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\n"; + const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; + const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; const char SAM_STREAM_FORWARD[] = "STREAM FORWARD"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; @@ -83,23 +77,14 @@ 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"; - 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 + enum SAMSocketType { eSAMSocketTypeUnknown, eSAMSocketTypeSession, @@ -126,7 +111,7 @@ namespace client void Terminate (const char* reason); - bool IsSession(std::string_view id) const; + bool IsSession(const std::string & id) const; private: @@ -135,7 +120,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 (std::string_view msg, bool close); + void SendMessageReply (const char * msg, size_t len, 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); @@ -148,21 +133,17 @@ 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 (std::string_view buf); + void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len, size_t rem); - 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); + 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); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 - const std::map ExtractParams (std::string_view buf); + void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); @@ -183,18 +164,16 @@ namespace client Socket_t m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; - size_t m_BufferOffset; // for session only - uint8_t m_StreamBuffer[SAM_STREAM_BUFFER_SIZE]; + size_t m_BufferOffset; + uint8_t m_StreamBuffer[SAM_SOCKET_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 class SAMSessionType + enum SAMSessionType { eSAMSessionTypeUnknown, eSAMSessionTypeStream, @@ -209,9 +188,8 @@ namespace client std::string Name; SAMSessionType Type; std::shared_ptr UDPEndpoint; // TODO: move - std::list, uint64_t> > acceptQueue; // socket, receive time in seconds - - SAMSession (SAMBridge & parent, std::string_view name, SAMSessionType type); + + SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); virtual ~SAMSession () {}; virtual std::shared_ptr GetLocalDestination () = 0; @@ -225,7 +203,7 @@ namespace client { std::shared_ptr localDestination; - SAMSingleSession (SAMBridge & parent, std::string_view name, SAMSessionType type, std::shared_ptr dest); + SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); ~SAMSingleSession (); std::shared_ptr GetLocalDestination () { return localDestination; }; @@ -234,18 +212,18 @@ namespace client struct SAMMasterSession: public SAMSingleSession { - std::set > subsessions; - SAMMasterSession (SAMBridge & parent, std::string_view name, std::shared_ptr dest): - SAMSingleSession (parent, name, SAMSessionType::eSAMSessionTypeMaster, dest) {}; + std::set subsessions; + SAMMasterSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest): + SAMSingleSession (parent, name, eSAMSessionTypeMaster, dest) {}; void Close (); }; struct SAMSubSession: public SAMSession { std::shared_ptr masterSession; - uint16_t inPort; + int inPort; - SAMSubSession (std::shared_ptr master, std::string_view name, SAMSessionType type, uint16_t port); + SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, int port); // implements SAMSession std::shared_ptr GetLocalDestination (); void StopLocalDestination (); @@ -255,20 +233,20 @@ namespace client { public: - SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread); + SAMBridge (const std::string& address, int port, bool singleThread); ~SAMBridge (); void Start (); void Stop (); - 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); + 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); bool AddSession (std::shared_ptr session); - void CloseSession (std::string_view id); - std::shared_ptr FindSession (std::string_view id) const; + void CloseSession (const std::string& id); + std::shared_ptr FindSession (const std::string& id) const; - std::list > ListSockets(std::string_view id) const; + std::list > ListSockets(const std::string & 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); @@ -276,7 +254,7 @@ namespace client void AddSocket(std::shared_ptr socket); void RemoveSocket(const std::shared_ptr & socket); - bool ResolveSignatureType (std::string_view name, i2p::data::SigningKeyType& type) const; + bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; private: @@ -286,10 +264,6 @@ 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; @@ -297,11 +271,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, std::less<>> m_Sessions; + std::map > m_Sessions; mutable std::mutex m_OpenSocketsMutex; std::list > m_OpenSockets; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; - const std::map m_SignatureTypes; + std::map m_SignatureTypes; public: diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 27df33c8..961ff934 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,7 +19,6 @@ #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" -#include "Socks5.h" namespace i2p { @@ -28,6 +27,10 @@ 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; @@ -63,11 +66,6 @@ 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, @@ -126,10 +124,11 @@ namespace proxy void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - 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); + 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(); bool Socks5ChooseAuth(); - void Socks5UserPasswdResponse (); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); @@ -138,26 +137,27 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); - template - void SocksUpstreamSuccess(std::shared_ptr& upstreamSock); + void SocksUpstreamSuccess(); void AsyncUpstreamSockRead(); - template - void SendUpstreamRequest(std::shared_ptr& upstreamSock); + 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); void HandleUpstreamConnected(const boost::system::error_code & ecode, - const boost::asio::ip::tcp::endpoint& ep); + boost::asio::ip::tcp::resolver::iterator itr); void HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::results_type endpoints); + boost::asio::ip::tcp::resolver::iterator itr); 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,14 +216,6 @@ 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"); @@ -232,17 +224,17 @@ namespace proxy Done(shared_from_this()); } - boost::asio::const_buffer SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) + boost::asio::const_buffers_1 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_buffer (m_response,8); + return boost::asio::const_buffers_1(m_response,8); } - boost::asio::const_buffer SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) + boost::asio::const_buffers_1 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); @@ -279,14 +271,45 @@ namespace proxy } break; } - return boost::asio::const_buffer (m_response, size); + 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); } bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; // Version m_response[1] = m_authchosen; // Response code - boost::asio::const_buffer response(m_response, 2); + boost::asio::const_buffers_1 response(m_response, 2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); @@ -301,19 +324,10 @@ 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_buffer response(nullptr,0); + boost::asio::const_buffers_1 response(nullptr,0); assert(error != SOCKS4_OK && error != SOCKS5_OK); switch (m_socksv) { @@ -333,7 +347,7 @@ namespace proxy void SOCKSHandler::SocksRequestSuccess() { - boost::asio::const_buffer response(nullptr,0); + boost::asio::const_buffers_1 response(nullptr,0); // TODO: this should depend on things like the command type and callbacks may change switch (m_socksv) { @@ -424,15 +438,10 @@ 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; - if (m_authchosen == AUTH_USERPASSWD) - EnterState(GET5_USERPASSWD); - else - EnterState(GET5_REQUESTV); + EnterState(GET5_REQUESTV); } break; case GET_COMMAND: @@ -443,7 +452,9 @@ 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); @@ -546,44 +557,6 @@ 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(); @@ -687,47 +660,42 @@ namespace proxy void SOCKSHandler::ForwardSOCKS() { LogPrint(eLogInfo, "SOCKS: Forwarding to upstream"); - 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); - } + 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)); } - template - void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr& upstreamSock) + 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); + } + } + + 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() { LogPrint(eLogInfo, "SOCKS: Upstream success"); - boost::asio::const_buffer response(nullptr, 0); + boost::asio::const_buffers_1 response(nullptr, 0); switch (m_socksv) { case SOCKS4: @@ -741,40 +709,58 @@ namespace proxy break; } m_sock->send(response); - auto forwarder = CreateSocketsPipe (GetOwner(), m_sock, upstreamSock); - upstreamSock = nullptr; + auto forwarder = std::make_shared(GetOwner(), m_sock, m_upstreamSock); + m_upstreamSock = nullptr; m_sock = nullptr; GetOwner()->AddHandler(forwarder); forwarder->Start(); Terminate(); + } - template - void SOCKSHandler::SendUpstreamRequest(std::shared_ptr& upstreamSock) + 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() { LogPrint(eLogInfo, "SOCKS: Negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); - 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 + if (m_upstreamSock) { + boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); + AsyncUpstreamSockRead(); + } else { LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to"); + } } - void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, - const boost::asio::ip::tcp::endpoint& ep) + void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message()); @@ -782,11 +768,10 @@ namespace proxy return; } LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy"); - SendUpstreamRequest(m_upstreamSock); + SendUpstreamRequest(); } - void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::results_type endpoints) + void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { // error resolving @@ -798,12 +783,12 @@ namespace proxy EnterState(UPSTREAM_CONNECT); auto & service = GetOwner()->GetService(); m_upstreamSock = std::make_shared(service); - boost::asio::async_connect(*m_upstreamSock, endpoints, + boost::asio::async_connect(*m_upstreamSock, itr, std::bind(&SOCKSHandler::HandleUpstreamConnected, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, uint16_t port, + SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int 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 bd88d6e6..f41cfd72 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2020, 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, uint16_t port, bool outEnable, const std::string& outAddress, uint16_t outPort, + SOCKSServer(const std::string& name, const std::string& address, int 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 deleted file mode 100644 index 54c4a21d..00000000 --- a/libi2pd_client/UDPTunnel.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/* -* 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 "Log.h" -#include "util.h" -#include "ClientContext.h" -#include "I2PTunnel.h" // for GetLoopbackAddressFor -#include "UDPTunnel.h" - -namespace i2p -{ -namespace client -{ - void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) - m_LastSession = ObtainUDPSession(from, toPort, fromPort); - 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 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) - { - 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::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - auto itr = m_Sessions.begin(); - while(itr != m_Sessions.end()) - { - if(now - itr->second->LastActivity >= delta ) - itr = m_Sessions.erase(itr); - else - itr++; - } - } - - void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - std::vector removePorts; - for (const auto & s : m_Sessions) { - if (now - s.second->second >= delta) - removePorts.push_back(s.first); - } - for(auto port : removePorts) { - m_Sessions.erase(port); - } - } - - UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) - { - auto ih = from.GetIdentHash(); - auto idx = GetSessionIndex (remotePort, localPort); - { - std::lock_guard lock(m_SessionsMutex); - auto it = m_Sessions.find (idx); - if (it != m_Sessions.end ()) - { - if (it->second->Identity.GetLL()[0] == ih.GetLL()[0]) - { - LogPrint(eLogDebug, "UDPServer: Found session ", it->second->IPSocket.local_endpoint(), " ", ih.ToBase32()); - return it->second; - } - else - { - LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from different address. Removed"); - m_Sessions.erase (it); - } - } - } - - boost::asio::ip::address addr; - /** create new udp session */ - if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) - { - auto ident = from.GetIdentHash(); - addr = GetLoopbackAddressFor(ident); - } - else - addr = m_LocalAddress; - - auto s = std::make_shared(boost::asio::ip::udp::endpoint(addr, 0), - m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort); - std::lock_guard lock(m_SessionsMutex); - m_Sessions.emplace (idx, s); - return s; - } - - UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - const boost::asio::ip::udp::endpoint& endpoint, const i2p::data::IdentHash& to, - uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination->GetDatagramDestination()), - IPSocket(localDestination->GetService(), localEndpoint), - Identity (to), SendEndpoint(endpoint), - LastActivity(i2p::util::GetMillisecondsSinceEpoch()), - LocalPort(ourPort), - RemotePort(theirPort) - { - IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); - IPSocket.non_blocking (true); - Receive(); - } - - void UDPSession::Receive() - { - LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), - FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); - } - - void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) - { - if(!ecode) - { - LogPrint(eLogDebug, "UDPSession: Forward ", len, "B from ", FromEndpoint); - auto ts = i2p::util::GetMillisecondsSinceEpoch(); - auto session = m_Destination->GetSession (Identity); - if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); - else - m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = IPSocket.available(ec); - if (ec || !moreBytes) break; - len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); - m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); - numPackets++; - } - if (numPackets > 0) - LogPrint(eLogDebug, "UDPSession: Forward more ", numPackets, "packets B from ", FromEndpoint); - m_Destination->FlushSendQueue (session); - LastActivity = ts; - Receive(); - } - else - LogPrint(eLogError, "UDPSession: ", ecode.message()); - } - - I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr localDestination, - const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t inPort, bool gzip) : - m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), - m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip) - { - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel () - { - Stop (); - } - - void I2PUDPServerTunnel::Start () - { - m_LocalDest->Start (); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver ( - std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - m_inPort - ); - dgram->SetRawReceiver ( - std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - m_inPort - ); - } - - void I2PUDPServerTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) { - dgram->ResetReceiver (m_inPort); - dgram->ResetRawReceiver (m_inPort); - } - } - - std::vector > I2PUDPServerTunnel::GetSessions () - { - std::vector > sessions; - std::lock_guard lock (m_SessionsMutex); - - for (const auto &it: m_Sessions) - { - auto s = it.second; - if (!s->m_Destination) continue; - auto info = s->m_Destination->GetInfoForRemote (s->Identity); - if (!info) continue; - - auto sinfo = std::make_shared (); - sinfo->Name = m_Name; - sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); - sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); - sinfo->CurrentIBGW = info->IBGW; - sinfo->CurrentOBEP = info->OBEP; - sessions.push_back (sinfo); - } - return sessions; - } - - I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - const boost::asio::ip::udp::endpoint& localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort, bool gzip, 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_DatagramVersion (datagramVersion) - { - } - - I2PUDPClientTunnel::~I2PUDPClientTunnel () - { - Stop (); - } - - void I2PUDPClientTunnel::Start () - { - // Reset flag in case of tunnel reload - if (m_cancel_resolve) m_cancel_resolve = false; - - m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); - m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); - m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); - m_LocalSocket->non_blocking (true); - - 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), - RemotePort - ); - dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - RemotePort - ); - - m_LocalDest->Start (); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); - RecvFromLocal (); - } - - void I2PUDPClientTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) { - dgram->ResetReceiver (RemotePort); - dgram->ResetRawReceiver (RemotePort); - } - m_cancel_resolve = true; - - m_Sessions.clear(); - - if(m_LocalSocket && m_LocalSocket->is_open ()) - m_LocalSocket->close (); - - if(m_ResolveThread) - { - m_ResolveThread->join (); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - m_RemoteAddr = nullptr; - } - - void I2PUDPClientTunnel::RecvFromLocal () - { - m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), - m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); - } - - void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) - { - if (m_cancel_resolve) { - LogPrint (eLogDebug, "UDP Client: Ignoring incoming data: stopping"); - return; - } - if (ec) { - LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); - RecvFromLocal (); // Restart listener and continue work - return; - } - if (!m_RemoteAddr || !m_RemoteAddr->IsIdentHash ()) // TODO: handle B33 - { - LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); - RecvFromLocal (); - return; // drop, remote not resolved - } - auto remotePort = m_RecvEndpoint.port (); - if (!m_LastPort || m_LastPort != remotePort) - { - auto itr = m_Sessions.find (remotePort); - if (itr != m_Sessions.end ()) - m_LastSession = itr->second; - else - { - m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); - m_Sessions.emplace (remotePort, m_LastSession); - } - m_LastPort = remotePort; - } - // send off to remote i2p destination - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteAddr->identHash.ToBase32 (), ":", RemotePort); - auto session = m_LocalDest->GetDatagramDestination ()->GetSession (m_RemoteAddr->identHash); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - else - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = m_LocalSocket->available (ec); - if (ec || !moreBytes) break; - transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); - remotePort = m_RecvEndpoint.port (); - // TODO: check remotePort - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - numPackets++; - } - if (numPackets) - LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteAddr->identHash.ToBase32 ()); - m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); - - // mark convo as active - if (m_LastSession) - m_LastSession->second = ts; - RecvFromLocal (); - } - - std::vector > I2PUDPClientTunnel::GetSessions () - { - // TODO: implement - std::vector > infos; - return infos; - } - - void I2PUDPClientTunnel::TryResolving () - { - i2p::util::SetThreadName ("UDP Resolver"); - LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - - while (!(m_RemoteAddr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) - { - LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for (std::chrono::seconds (1)); - } - if (m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); - return; - } - if (!m_RemoteAddr) - { - LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); - return; - } - LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteAddr->identHash.ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (m_RemoteAddr && from.GetIdentHash() == m_RemoteAddr->identHash) - HandleRecvFromI2PRaw (fromPort, toPort, buf, len); - else - LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - auto itr = m_Sessions.find (toPort); - // found convo ? - if (itr != m_Sessions.end ()) - { - // found convo - if (len > 0) - { - LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteAddr ? m_RemoteAddr->identHash.ToBase32 () : ""); - 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 - LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); - } - -} -} diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h deleted file mode 100644 index 7303aa9a..00000000 --- a/libi2pd_client/UDPTunnel.h +++ /dev/null @@ -1,190 +0,0 @@ -/* -* 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 -*/ - -#ifndef UDPTUNNEL_H__ -#define UDPTUNNEL_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "Identity.h" -#include "Destination.h" -#include "Datagram.h" -#include "AddressBook.h" - -namespace i2p -{ -namespace client -{ - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds - - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 64*1024; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; - - uint16_t LocalPort; - uint16_t RemotePort; - - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - const boost::asio::ip::udp::endpoint& remote, const i2p::data::IdentHash& ident, - uint16_t ourPort, uint16_t theirPort); - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; - - - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - std::string Name; - /** ident hash of local destination */ - std::shared_ptr LocalIdent; - /** ident hash of remote destination */ - std::shared_ptr RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this conversation been idle in ms */ - uint64_t idle; - }; - - typedef std::shared_ptr UDPSessionPtr; - - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - - I2PUDPServerTunnel (const std::string & name, - std::shared_ptr localDestination, - const boost::asio::ip::address& localAddress, - const boost::asio::ip::udp::endpoint& forwardTo, uint16_t port, bool gzip); - ~I2PUDPServerTunnel (); - - /** expire stale udp conversations */ - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - - void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } - - private: - - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - uint32_t GetSessionIndex (uint16_t fromPort, uint16_t toPort) const { return ((uint32_t)fromPort << 16) + toPort; } - - private: - - bool m_IsUniqueLocal; - const std::string m_Name; - boost::asio::ip::address m_LocalAddress; - boost::asio::ip::udp::endpoint m_RemoteEndpoint; - std::mutex m_SessionsMutex; - std::unordered_map m_Sessions; // (from port, to port)->session - std::shared_ptr m_LocalDest; - UDPSessionPtr m_LastSession; - uint16_t m_inPort; - bool m_Gzip; - - public: - - bool isUpdated; // transient, used during reload only - }; - - class I2PUDPClientTunnel - { - public: - - I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - const boost::asio::ip::udp::endpoint& localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort, bool gzip, i2p::datagram::DatagramVersion datagramVersion); - ~I2PUDPClientTunnel (); - - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - - bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } - - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - inline void SetLocalDestination (std::shared_ptr dest) - { - if (m_LocalDest) m_LocalDest->Release (); - if (dest) dest->Acquire (); - m_LocalDest = dest; - } - - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - - private: - - typedef std::pair UDPConvo; - void RecvFromLocal (); - void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving (); - - private: - - const std::string m_Name; - std::mutex m_SessionsMutex; - std::unordered_map > m_Sessions; // maps i2p port -> local udp convo - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - std::shared_ptr m_RemoteAddr; - std::thread * m_ResolveThread; - std::unique_ptr m_LocalSocket; - boost::asio::ip::udp::endpoint m_RecvEndpoint; - uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort, m_LastPort; - bool m_cancel_resolve; - bool m_Gzip; - i2p::datagram::DatagramVersion m_DatagramVersion; - std::shared_ptr m_LastSession; - - public: - - bool isUpdated; // transient, used during reload only - }; - - -} -} - -#endif diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 90457c23..00000000 --- a/tests/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/test-http-merge_chunked -/test-http-req -/test-http-res -/test-http-url -/test-http-url_decode -/test-gost -/test-gost-sig -/test-base-64 -/test-x25519 -/test-aeadchacha20poly1305 -/test-blinding -/test-elligator diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index fb03d434..00000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,123 +0,0 @@ -enable_testing() -find_package(Check 0.9.10 REQUIRED) -include_directories(${CHECK_INCLUDE_DIRS}) - -# Compiler flags: -if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,-undefined,dynamic_lookup") -else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,--unresolved-symbols=ignore-in-object-files") -endif() - -set(TEST_PATH ${CMAKE_CURRENT_BINARY_DIR}) - -include_directories( - ../libi2pd - ${Boost_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIR} -) - -set(test-http-merge_chunked_SRCS - test-http-merge_chunked.cpp -) - -set(test-http-req_SRCS - test-http-req.cpp -) - -set(test-http-res_SRCS - test-http-res.cpp -) - -set(test-http-url_decode_SRCS - test-http-url_decode.cpp -) - -set(test-http-url_SRCS - test-http-url.cpp -) - -set(test-base-64_SRCS - test-base-64.cpp -) - -set(test-gost_SRCS - test-gost.cpp -) - -set(test-gost-sig_SRCS - test-gost-sig.cpp -) - -set(test-aeadchacha20poly1305_SRCS - test-aeadchacha20poly1305.cpp -) - -set(test-blinding_SRCS - test-blinding.cpp -) - -SET(test-elligator_SRCS - test-elligator.cpp -) - -set(test-eddsa_SRCS - test-eddsa.cpp -) - -set(test-aes_SRCS - test-aes.cpp -) - -add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS}) -add_executable(test-http-req ${test-http-req_SRCS}) -add_executable(test-http-res ${test-http-res_SRCS}) -add_executable(test-http-url_decode ${test-http-url_decode_SRCS}) -add_executable(test-http-url ${test-http-url_SRCS}) -add_executable(test-base-64 ${test-base-64_SRCS}) -add_executable(test-gost ${test-gost_SRCS}) -add_executable(test-gost-sig ${test-gost-sig_SRCS}) -add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) -add_executable(test-blinding ${test-blinding_SRCS}) -add_executable(test-elligator ${test-elligator_SRCS}) -add_executable(test-eddsa ${test-eddsa_SRCS}) -add_executable(test-aes ${test-aes_SRCS}) - -set(LIBS - libi2pd - ${Boost_LIBRARIES} - OpenSSL::SSL - OpenSSL::Crypto - ZLIB::ZLIB - Threads::Threads - ${CHECK_LDFLAGS} - ${CMAKE_REQUIRED_LIBRARIES} -) - -target_link_libraries(test-http-merge_chunked ${LIBS}) -target_link_libraries(test-http-req ${LIBS}) -target_link_libraries(test-http-res ${LIBS}) -target_link_libraries(test-http-url_decode ${LIBS}) -target_link_libraries(test-http-url ${LIBS}) -target_link_libraries(test-base-64 ${LIBS}) -target_link_libraries(test-gost ${LIBS}) -target_link_libraries(test-gost-sig ${LIBS}) -target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) -target_link_libraries(test-blinding ${LIBS}) -target_link_libraries(test-elligator ${LIBS}) -target_link_libraries(test-eddsa ${LIBS}) -target_link_libraries(test-aes ${LIBS}) - -add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked) -add_test(test-http-req ${TEST_PATH}/test-http-req) -add_test(test-http-res ${TEST_PATH}/test-http-res) -add_test(test-http-url_decode ${TEST_PATH}/test-http-url_decode) -add_test(test-http-url ${TEST_PATH}/test-http-url) -add_test(test-base-64 ${TEST_PATH}/test-base-64) -add_test(test-gost ${TEST_PATH}/test-gost) -add_test(test-gost-sig ${TEST_PATH}/test-gost-sig) -add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) -add_test(test-blinding ${TEST_PATH}/test-blinding) -add_test(test-elligator ${TEST_PATH}/test-elligator) -add_test(test-eddsa ${TEST_PATH}/test-eddsa) -add_test(test-aes ${TEST_PATH}/test-aes) diff --git a/tests/Makefile b/tests/Makefile index b020427d..8eb52fde 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,66 +1,36 @@ -SYS := $(shell $(CXX) -dumpmachine) - -CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++17 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -pthread -Wl,--unresolved-symbols=ignore-in-object-files 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-aeadchacha20poly1305 test-blinding \ - test-elligator test-eddsa test-aes - -ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) - CXXFLAGS += -DWIN32_LEAN_AND_MEAN - LDFLAGS += -mwindows -static - BOOST_SUFFIX = -mt - NEEDED_LDLIBS = -lwsock32 -lws2_32 -lgdi32 -liphlpapi -lole32 -endif - -LDLIBS = \ - -lboost_system$(BOOST_SUFFIX) \ - -lboost_program_options$(BOOST_SUFFIX) \ - -lssl \ - -lcrypto \ - -lz \ - $(NEEDED_LDLIBS) \ - -lpthread - +TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator all: $(TESTS) run -$(LIBI2PD): - @echo "Building libi2pd.a ..." && cd .. && $(MAKE) libi2pd.a +test-http-%: ../libi2pd/HTTP.cpp test-http-%.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -test-http-%: test-http-%.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-base-%: ../libi2pd/Base.cpp test-base-%.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -test-base-%: test-base-%.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-gost: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp test-gost.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -test-gost: test-gost.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-gost-sig: test-gost-sig.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/Crypto.cpp test-x25519.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-blinding: test-blinding.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-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) +test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system run: $(TESTS) - @for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done + @for TEST in $(TESTS); do ./$$TEST ; done clean: rm -f $(TESTS) diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp index 2ba6a253..de9f1db2 100644 --- a/tests/test-aeadchacha20poly1305.cpp +++ b/tests/test-aeadchacha20poly1305.cpp @@ -7,28 +7,28 @@ char text[] = "Ladies and Gentlemen of the class of '99: If I could offer you " "only one tip for the future, sunscreen would be it."; // 114 bytes -uint8_t key[32] = +uint8_t key[32] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f }; -uint8_t ad[12] = +uint8_t ad[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; -uint8_t nonce[12] = +uint8_t nonce[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 }; -uint8_t tag[16] = +uint8_t tag[16] = { 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 }; -uint8_t encrypted[114] = +uint8_t encrypted[114] = { 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, @@ -43,20 +43,18 @@ uint8_t encrypted[114] = int main () { uint8_t buf[114+16]; - i2p::crypto::AEADChaCha20Poly1305Encryptor encryptor; // test encryption - encryptor.Encrypt ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16); + i2p::crypto::AEADChaCha20Poly1305 ((uint8_t *)text, 114, ad, 12, key, nonce, buf, 114 + 16, true); assert (memcmp (buf, encrypted, 114) == 0); assert (memcmp (buf + 114, tag, 16) == 0); // test decryption uint8_t buf1[114]; - i2p::crypto::AEADChaCha20Poly1305Decryptor decryptor; - assert (decryptor.Decrypt (buf, 114, ad, 12, key, nonce, buf1, 114)); + assert (i2p::crypto::AEADChaCha20Poly1305 (buf, 114, ad, 12, key, nonce, buf1, 114, false)); 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) }; - encryptor.Encrypt (bufs, key, nonce, buf + 114); - decryptor.Decrypt (buf, 114, nullptr, 0, key, nonce, buf1, 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); assert (memcmp (buf1, text, 114) == 0); } diff --git a/tests/test-aes.cpp b/tests/test-aes.cpp deleted file mode 100644 index 15f4de1e..00000000 --- a/tests/test-aes.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#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 63817bf4..0ab46c06 100644 --- a/tests/test-base-64.cpp +++ b/tests/test-base-64.cpp @@ -11,7 +11,8 @@ int main() { char out[16]; /* bytes -> b64 */ - assert(ByteStreamToBase64(NULL, 0) == ""); + assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); + assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); assert(Base64EncodingBufferSize(2) == 4); assert(Base64EncodingBufferSize(4) == 8); @@ -22,20 +23,19 @@ int main() { assert(Base64EncodingBufferSize(12) == 16); assert(Base64EncodingBufferSize(13) == 20); - const std::string out_str(ByteStreamToBase64((uint8_t *) in, in_len)); - assert(out_str.size() == 8); - assert(out_str == "dGVzdA=="); + assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); + assert(memcmp(out, "dGVzdA==", 8) == 0); /* b64 -> bytes */ - assert(Base64ToByteStream("", NULL, 0) == 0); - assert(Base64ToByteStream("", (uint8_t *) out, sizeof(out)) == 0); + assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); + assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); in = "dGVzdA=="; /* valid b64 */ - assert(Base64ToByteStream(in, (uint8_t *) out, sizeof(out)) == 4); + assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); assert(memcmp(out, "test", 4) == 0); in = "dGVzdA="; /* invalid b64 : not padded */ - assert(Base64ToByteStream(in, (uint8_t *) out, sizeof(out)) == 0); + assert(Base64ToByteStream(in, strlen(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-blinding.cpp b/tests/test-blinding.cpp index 10b72e4f..5490acd4 100644 --- a/tests/test-blinding.cpp +++ b/tests/test-blinding.cpp @@ -13,29 +13,31 @@ void BlindTest (SigningKeyType sigType) { auto keys = PrivateKeys::CreateRandomKeys (sigType); BlindedPublicKey blindedKey (keys.GetPublic ()); - auto timestamp = GetSecondsSinceEpoch (); + auto timestamp = GetSecondsSinceEpoch (); char date[9]; GetDateString (timestamp, date); - uint8_t blindedPriv[32], blindedPub[32]; + uint8_t blindedPriv[64], blindedPub[128]; auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - uint8_t blindedPub1[32]; + uint8_t blindedPub1[128]; blindedKey.GetBlindedKey (date, blindedPub1); // check if public key produced from private blinded key matches blided public key assert (!memcmp (blindedPub, blindedPub1, publicKeyLen)); // try to sign and verify - std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); - uint8_t buf[100], signature[64]; + std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (sigType, blindedPriv)); + uint8_t buf[100], signature[128]; memset (buf, 1, 100); - blindedSigner->Sign (buf, 100, signature); - std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (blindedKey.GetBlindedSigType ())); - blindedVerifier->SetPublicKey (blindedPub); - assert (blindedVerifier->Verify (buf, 100, signature)); + blindedSigner->Sign (buf, 100, signature); + std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (sigType)); + blindedVerifier->SetPublicKey (blindedPub1); + assert (blindedVerifier->Verify (buf, 100, signature)); } int main () { - // EdDSA test - BlindTest (SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); - // RedDSA test + // RedDSA test BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); + // P256 test + BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + // P384 test + BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384); } diff --git a/tests/test-eddsa.cpp b/tests/test-eddsa.cpp deleted file mode 100644 index 9de2c088..00000000 --- a/tests/test-eddsa.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#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-elligator.cpp b/tests/test-elligator.cpp index 359c71c5..48c9e31a 100644 --- a/tests/test-elligator.cpp +++ b/tests/test-elligator.cpp @@ -4,19 +4,19 @@ #include "Elligator.h" -const uint8_t key[32] = +const uint8_t key[32] = { 0x33, 0x95, 0x19, 0x64, 0x00, 0x3c, 0x94, 0x08, 0x78, 0x06, 0x3c, 0xcf, 0xd0, 0x34, 0x8a, 0xf4, 0x21, 0x50, 0xca, 0x16, 0xd2, 0x64, 0x6f, 0x2c, 0x58, 0x56, 0xe8, 0x33, 0x83, 0x77, 0xd8, 0x80 }; -const uint8_t encoded_key[32] = +const uint8_t encoded_key[32] = { 0x28, 0x20, 0xb6, 0xb2, 0x41, 0xe0, 0xf6, 0x8a, 0x6c, 0x4a, 0x7f, 0xee, 0x3d, 0x97, 0x82, 0x28, 0xef, 0x3a, 0xe4, 0x55, 0x33, 0xcd, 0x41, 0x0a, 0xa9, 0x1a, 0x41, 0x53, 0x31, 0xd8, 0x61, 0x2d }; -const uint8_t encoded_key_high_y[32] = +const uint8_t encoded_key_high_y[32] = { 0x3c, 0xfb, 0x87, 0xc4, 0x6c, 0x0b, 0x45, 0x75, 0xca, 0x81, 0x75, 0xe0, 0xed, 0x1c, 0x0a, 0xe9, 0xda, 0xe7, 0x9d, 0xb7, 0x8d, 0xf8, 0x69, 0x97, 0xc4, 0x84, 0x7b, 0x9f, 0x20, 0xb2, 0x77, 0x18 @@ -28,7 +28,7 @@ const uint8_t encoded1[32] = 0x14, 0x50, 0x95, 0x89, 0x28, 0x84, 0x57, 0x99, 0x5a, 0x2b, 0x4c, 0xa3, 0x49, 0x0a, 0xa2, 0x07 }; -const uint8_t key1[32] = +const uint8_t key1[32] = { 0x1e, 0x8a, 0xff, 0xfe, 0xd6, 0xbf, 0x53, 0xfe, 0x27, 0x1a, 0xd5, 0x72, 0x47, 0x32, 0x62, 0xde, 0xd8, 0xfa, 0xec, 0x68, 0xe5, 0xe6, 0x7e, 0xf4, 0x5e, 0xbb, 0x82, 0xee, 0xba, 0x52, 0x60, 0x4f @@ -40,7 +40,7 @@ const uint8_t encoded2[32] = 0xd9, 0x03, 0x65, 0xf2, 0x4a, 0x38, 0xaa, 0x7a, 0xef, 0x1b, 0x97, 0xe2, 0x39, 0x54, 0x10, 0x1b }; -const uint8_t key2[32] = +const uint8_t key2[32] = { 0x79, 0x4f, 0x05, 0xba, 0x3e, 0x3a, 0x72, 0x95, 0x80, 0x22, 0x46, 0x8c, 0x88, 0x98, 0x1e, 0x0b, 0xe5, 0x78, 0x2b, 0xe1, 0xe1, 0x14, 0x5c, 0xe2, 0xc3, 0xc6, 0xfd, 0xe1, 0x6d, 0xed, 0x53, 0x63 @@ -52,7 +52,7 @@ const uint8_t encoded3[32] = 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f }; -const uint8_t key3[32] = +const uint8_t key3[32] = { 0x9c, 0xdb, 0x52, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp index 31b6a298..ba587a45 100644 --- a/tests/test-http-merge_chunked.cpp +++ b/tests/test-http-merge_chunked.cpp @@ -1,5 +1,5 @@ #include -#include "HTTP.h" +#include "../HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index db973a2e..c857ca24 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -1,5 +1,5 @@ #include -#include "HTTP.h" +#include "../HTTP.h" using namespace i2p::http; @@ -22,13 +22,13 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->GetNumHeaders () == 3); - assert(req->GetNumHeaders("Host") == 1); - assert(req->GetNumHeaders("Accept") == 1); - assert(req->GetNumHeaders("User-Agent") == 1); - assert(req->GetHeader("Host") == "inr.i2p"); - assert(req->GetHeader("Accept") == "*/*"); - assert(req->GetHeader("User-Agent") == "curl/7.26.0"); + assert(req->headers.size() == 3); + assert(req->headers.count("Host") == 1); + assert(req->headers.count("Accept") == 1); + assert(req->headers.count("User-Agent") == 1); + assert(req->headers.find("Host")->second == "inr.i2p"); + assert(req->headers.find("Accept")->second == "*/*"); + assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); delete req; /* test: parsing request without body */ @@ -41,7 +41,7 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->GetNumHeaders () == 0); + assert(req->headers.size() == 0); delete req; /* test: parsing request without body */ @@ -74,13 +74,13 @@ int main() { assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); - assert(req->GetNumHeaders () == 3); - assert(req->GetNumHeaders("Host") == 1); - assert(req->GetNumHeaders("Accept") == 1); - assert(req->GetNumHeaders("Accept-Encoding") == 1); - assert(req->GetHeader("Host") == "stats.i2p"); - assert(req->GetHeader("Accept") == "*/*"); - assert(req->GetHeader("Accept-Encoding") == ""); + assert(req->headers.size() == 3); + assert(req->headers.count("Host") == 1); + assert(req->headers.count("Accept") == 1); + assert(req->headers.count("Accept-Encoding") == 1); + assert(req->headers["Host"] == "stats.i2p"); + assert(req->headers["Accept"] == "*/*"); + assert(req->headers["Accept-Encoding"] == ""); delete req; return 0; diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 270f32a3..896a4403 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -1,5 +1,5 @@ #include -#include "HTTP.h" +#include "../HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index a5021c43..37e9c45e 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -1,5 +1,5 @@ #include -#include "HTTP.h" +#include "../HTTP.h" using namespace i2p::http; @@ -15,7 +15,6 @@ int main() { assert(url->host == "127.0.0.1"); assert(url->port == 7070); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == "12345"); assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); delete url; @@ -28,7 +27,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == "123456"); delete url; @@ -40,7 +38,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == "name=value"); delete url; @@ -52,7 +49,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == "name=value1&name=value2"); delete url; @@ -64,7 +60,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == "name1=value1&name2&name3=value2"); assert(url->parse_query(params)); assert(params.size() == 3); @@ -84,7 +79,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); - assert(url->hasquery == true); assert(url->query == ""); delete url; @@ -96,7 +90,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); - assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -108,7 +101,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); - assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -120,7 +112,6 @@ int main() { assert(url->host == "site.com"); assert(url->port == 84); assert(url->path == "/asdasd/@17"); - assert(url->hasquery == false); assert(url->query == ""); assert(url->frag == "frag"); delete url; diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp index 7f08bbc6..f72b2c50 100644 --- a/tests/test-http-url_decode.cpp +++ b/tests/test-http-url_decode.cpp @@ -1,5 +1,5 @@ #include -#include "HTTP.h" +#include "../HTTP.h" using namespace i2p::http; diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp new file mode 100644 index 00000000..2ab8ad6a --- /dev/null +++ b/tests/test-x25519.cpp @@ -0,0 +1,39 @@ +#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 +} +