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/.gitattributes b/.gitattributes deleted file mode 100644 index 488400a3..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/build/build_mingw.cmd eol=crlf \ No newline at end of file diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml deleted file mode 100644 index 597dc211..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: ['buster', 'bullseye', 'bookworm'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Commit Hash - id: commit - uses: prompt/actions-commit-hash@v3.0.0 - - - name: Build package - uses: jtdor/build-deb-action@v1 - with: - docker-image: debian:${{ matrix.dist }}-slim - buildpackage-opts: --build=binary --no-sign - before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build" - extra-build-deps: devscripts git - - - name: Upload package - uses: actions/upload-artifact@v4 - with: - name: i2pd_${{ matrix.dist }} - path: debian/artifacts/i2pd_*.deb - - - name: Upload debugging symbols - uses: actions/upload-artifact@v4 - with: - name: i2pd-dbgsym_${{ matrix.dist }} - path: debian/artifacts/i2pd-dbgsym_*.deb diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml deleted file mode 100644 index a4a7566a..00000000 --- a/.github/workflows/build-freebsd.yml +++ /dev/null @@ -1,50 +0,0 @@ -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: - - '*' - -jobs: - build: - runs-on: ubuntu-latest - name: with UPnP - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Test in FreeBSD - id: test - uses: vmactions/freebsd-vm@v1 - with: - usesh: true - mem: 2048 - sync: rsync - copyback: true - prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc - run: | - cd build - cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . - gmake -j2 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: i2pd-freebsd - path: build/i2pd diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml deleted file mode 100644 index 31f0b90d..00000000 --- a/.github/workflows/build-osx.yml +++ /dev/null @@ -1,45 +0,0 @@ -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: - - '*' - -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 - 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 - 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 deleted file mode 100644 index 6f10e62b..00000000 --- a/.github/workflows/build-windows.yml +++ /dev/null @@ -1,250 +0,0 @@ -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: - - '*' - -defaults: - run: - shell: msys2 {0} - -jobs: - build: - name: ${{ 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 }}-${{ matrix.compiler }} mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc - update: true - - - name: Install additional clang packages - if: ${{ matrix.msystem == 'CLANG64' }} - run: pacman --noconfirm -S mingw-w64-${{ matrix.arch }}-gcc-compat - - - name: Build application - run: | - mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon - make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes -j3 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - 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 - runs-on: windows-latest - - strategy: - fail-fast: false - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW32 - install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-miniupnpc - cache: true - update: true - - - name: Clone MinGW packages repository and revert boost to 1.85.0 - run: | - git clone https://github.com/msys2/MINGW-packages - cd MINGW-packages - git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost - cd mingw-w64-boost - sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD - - # headers - - name: Get headers package version - id: version-headers - run: | - echo "version=$(pacman -Si mingw-w64-i686-headers-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - - name: Cache headers package - uses: actions/cache@v4 - id: cache-headers - with: - path: MINGW-packages/mingw-w64-headers-git/*.zst - key: winxp-headers-${{ steps.version-headers.outputs.version }} - - name: Build WinXP-capable headers package - if: steps.cache-headers.outputs.cache-hit != 'true' - run: | - cd MINGW-packages/mingw-w64-headers-git - sed -i 's/0x601/0x501/' PKGBUILD - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - - name: Install headers package - run: pacman --noconfirm -U MINGW-packages/mingw-w64-headers-git/mingw-w64-i686-*-any.pkg.tar.zst - - # CRT - - name: Get crt package version - id: version-crt - run: | - echo "version=$(pacman -Si mingw-w64-i686-crt-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - - name: Cache crt package - uses: actions/cache@v4 - id: cache-crt - with: - path: MINGW-packages/mingw-w64-crt-git/*.zst - key: winxp-crt-${{ steps.version-crt.outputs.version }} - - name: Build WinXP-capable crt package - if: steps.cache-crt.outputs.cache-hit != 'true' - run: | - cd MINGW-packages/mingw-w64-crt-git - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - - name: Install crt package - run: pacman --noconfirm -U MINGW-packages/mingw-w64-crt-git/mingw-w64-i686-*-any.pkg.tar.zst - - # winpthreads - - name: Get winpthreads package version - id: version-winpthreads - run: | - echo "version=$(pacman -Si mingw-w64-i686-winpthreads-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - - name: Cache winpthreads package - uses: actions/cache@v4 - id: cache-winpthreads - with: - path: MINGW-packages/mingw-w64-winpthreads-git/*.zst - key: winxp-winpthreads-${{ steps.version-winpthreads.outputs.version }} - - name: Build WinXP-capable winpthreads package - if: steps.cache-winpthreads.outputs.cache-hit != 'true' - run: | - cd MINGW-packages/mingw-w64-winpthreads-git - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - - name: Install winpthreads package - run: pacman --noconfirm -U MINGW-packages/mingw-w64-winpthreads-git/mingw-w64-i686-*-any.pkg.tar.zst - - # OpenSSL - - name: Get openssl package version - id: version-openssl - run: | - echo "version=$(pacman -Si mingw-w64-i686-openssl | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - - name: Cache openssl package - uses: actions/cache@v4 - id: cache-openssl - with: - path: MINGW-packages/mingw-w64-openssl/*.zst - key: winxp-openssl-${{ steps.version-openssl.outputs.version }} - - name: Build WinXP-capable openssl package - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: | - cd MINGW-packages/mingw-w64-openssl - gpg --recv-keys D894E2CE8B3D79F5 - gpg --recv-keys 216094DFD0CB81EF - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - - name: Install openssl package - run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst - - # Boost - #- name: Get boost package version - # id: version-boost - # run: | - # echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT - - name: Cache boost package - uses: actions/cache@v4 - id: cache-boost - with: - path: MINGW-packages/mingw-w64-boost/*.zst - key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }} - # Rebuild package if packages above has changed - - name: Build WinXP-capable boost package - if: steps.cache-boost.outputs.cache-hit != 'true' - run: | - cd MINGW-packages/mingw-w64-boost - MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck - - name: Remove boost packages - run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs - - name: Install boost package - run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst - - # Building i2pd - - name: Build application - run: | - mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon - make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes USE_WINXP_FLAGS=yes -j3 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: i2pd-xp.exe - path: i2pd.exe diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 0b65ec9d..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,67 +0,0 @@ -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: - - '*' - -jobs: - build-make: - name: Make with USE_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-latest - - strategy: - fail-fast: true - matrix: - with_upnp: ['yes', 'no'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: install packages - run: | - sudo apt-get update - sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev - - - name: build application - run: make USE_UPNP=${{ matrix.with_upnp }} -j3 - - build-cmake: - name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-latest - - strategy: - fail-fast: true - matrix: - with_upnp: ['ON', 'OFF'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: install packages - run: | - sudo apt-get update - sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev - - - name: build application - run: | - cd build - cmake -DWITH_UPNP=${{ matrix.with_upnp }} . - make -j3 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index c6d55664..00000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: Build containers - -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 }} - 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: 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: Build container for ${{ matrix.archname }} - uses: docker/build-push-action@v5 - with: - context: ./contrib/docker - file: ./contrib/docker/Dockerfile - platforms: ${{ matrix.platform }} - push: true - tags: | - purplei2p/i2pd:latest-${{ matrix.archname }} - ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} - provenance: false - - push: - name: Pushing merged manifest - runs-on: ubuntu-latest - - permissions: - packages: write - contents: read - - needs: build - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create and push latest manifest image to Docker Hub - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: purplei2p/i2pd:latest - tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push latest manifest image to GHCR - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: ghcr.io/purplei2p/i2pd:latest - tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true - - - name: Store release version to env - if: ${{ startsWith(github.ref, 'refs/tags/') }} - run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - - - name: Create and push release manifest to Docker Hub - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 - push: true - - - name: Create and push release manifest to GHCR - if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: Noelware/docker-manifest-action@master - with: - inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 - push: true diff --git a/.gitignore b/.gitignore index 75bd6abb..c2db70e0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,17 +3,11 @@ router.info router.keys i2p +libi2pd.so netDb /i2pd /libi2pd.a /libi2pdclient.a -/libi2pdlang.a -/libi2pd.so -/libi2pdclient.so -/libi2pdlang.so -/libi2pd.dll -/libi2pdclient.dll -/libi2pdlang.dll *.exe @@ -260,20 +254,13 @@ docs/generated build/Makefile # debian stuff -debian/i2pd.1.gz .pc/ # qt -qt/i2pd_qt/*.autosave +qt/i2pd_qt/*.ui.autosave qt/i2pd_qt/*.ui.bk* qt/i2pd_qt/*.ui_* #unknown android stuff android/libs/ - -#various logs -*LOGS/ - -qt/build-*.sh* - diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..c55f1885 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,54 @@ +language: cpp +cache: + apt: true +os: +- linux +#- osx +dist: trusty +sudo: required +compiler: +- g++ +- clang++ +env: + global: + - MAKEFLAGS="-j 2" + matrix: + - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes + - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no + - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes + - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no +matrix: + exclude: + - os: osx + env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes + - os: osx + env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no + - os: linux + compiler: clang++ + env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes + - os: linux + compiler: clang++ + env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no +addons: + apt: + packages: + - build-essential + - cmake + - g++ + - clang + - libboost-chrono-dev + - libboost-date-time-dev + - libboost-filesystem-dev + - libboost-program-options-dev + - libboost-system-dev + - libboost-thread-dev + - libminiupnpc-dev + - libssl-dev +before_install: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi +script: +- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi +- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi diff --git a/ChangeLog b/ChangeLog index 23864c0e..08f78224 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,895 +1,10 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog -## [2.56.0] - 2025-02-11 -### Added -- Config params for shared local destination -- AddressBook full addresses cache -- Decline transit tunnel to duplicated router -- Recreate tunnels in random order -### Changed -- Exclude disk operations from SSU2 and NTCP2 threads -- Set minimal version for peer test to 0.9.62 -- Send ack requested flag after second SSU2 resend attempt -- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions -- Don't change datagram routing path too often if unidirectional data stream -- Reduce LeaseSet and local RouterInfo publishing confirmation intervals -- Don't delete buffer of connected routers or if an update received -- Smaller RouterInfo request timeout if sent directly -- Persist local RouterInfo in separate thread -- Don't recalculate and process ranges for every SSU2 Ack block -- Reseeds list -### Fixed -- Termination deadlock if SAM session is active -- Race condition at tunnel endpoint -- Inbound tunnel build encryption - -## [2.55.0] - 2024-12-30 -### Added -- Support boost 1.87 -- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams -- Separate thread for tunnel build requests -- Show next peer and connectivity on "Transit tunnels" page -- Tunnel name for local destination thread -- Throttle incoming ECIESx25519 sessions -- Send tunnel data to transport session directly if possible -- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy -- Random tunnel rejection when medium congestion -- Save unreachable router's endpoint to use it next time without introducers -- Recognize symmetric NAT from peer test message 7 -- Resend HolePunch and RelayResponse messages -### Changed -- Removed own implementation of AESNI and always use one from openssl -- Renamed main thread to i2pd-daemon -- Set i2p.streaming.profile=2 for shared local destination -- Reduced LeaseSet and RouterInfo lookup timeouts -- Cleanup ECIES sessions and tags more often -- Check LeaseSet expiration time -- Handle NTCP2 session handshakes in separate thread -- Limit last decline time by 1.5 hours in router's profile -- Don't handle RelayRequest and RelayIntro with same nonce twice -- Increased hole punch expiration interval -- Send peer test message 6 with delay if message 4 was received before message 5 -- Pre-calculate more x25519 keys for transports in runtime -- Don't request LeaseSet for incoming stream -- Terminate incoming stream right away if no remote LeaseSet -- Handle choked, new RTO and window size calculation and resetting algorithm for streams -### Fixed -- Empty string in addressbook subscriptions -- ECIESx25519 sessions without destination -- Missing RouterInfo buffer in NetDb -- Invalid I2PControl certificate -- Routers disappear from NetDb when offline -- Peer test message 6 sent to unknown endpoint -- Race condition with LeaseSet update -- Excessive CPU usage by streams -- Crash on shutdown - -## [2.54.0] - 2024-10-06 -### Added -- Maintain recently connected routers list to avoid false-positive peer test -- Limited connectivity mode(through proxy) -- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers -- Limit stream's inbound speed -- Periodic ack requests in ratchets session -- Set congestion cap G immediately if through proxy -- Show tunnel's routers bandwidth caps in web console -- Handle immediate ack requested flag in SSU2 data packets -- Resend and ack peer test and relay messages -- "senduseragent" HTTP proxy's param to pass through user's User-Agent -### Changed -- Exclude 'N' routers from high-bandwidth routers for client tunnels -- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers -- Removed dependency from boost::date_time and boost::filesystem -- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels -- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP -- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second -- Increase max number of generated tags to 800 per tagset -- Routing path expiration by time instead num attempts -- Save timestamp from epoch instead local time to profiles -- Update introducer's iTag if session to introducer was replaced to new one -- RTT, window size and number of NACKs calculation for streaming -- Don't select same peer for tunnel too often -- Use WinApi for data path UTF-8 conversion for Windows -### Fixed -- Jump link crash if address book is disabled -- Race condition if connect through an introducer -- "Date" header in I2PControl response -- Incomplete response from web console -- AEAD verification with LibreSSL -- Number of generated tags and new keys for follow-on tagsets -- Expired leases in LeaseSet -- Attempts to send HolePunch to 0.0.0.0 -- Incorrect options size in quick ack streaming packet -- Low bandwidth router appeared as first peer in high-bandwidth client tunnel - -## [2.53.1] - 2024-07-29 -### Changed -- I2CP performance improvement -### Fixed -- 100% CPU usage after I2CP/SAM/BOB session termination -- Incorrect client limits returned through I2CP -- Build with LibreSSL - -## [2.53.0] - 2024-07-19 -### Added -- New congestion control algorithm for streaming -- Support miniupnp-2.2.8 -- Limit stream's outbound speed -- Flood to next day closest floodfills before UTC midnight -- Recognize duplicated routers and bypass them -- Random SSU2 resend interval -### Changed -- Set minimal version to 0.9.69 for floodfills and 0.9.58 for client tunnels -- Removed openssl 1.0.2 support -- Move unsent I2NP messages to the new session if replaced -- Use mt19937 RNG instead rand() -- Update router's congestion caps before initial publishing -- Don't try introducer with invalid address -- Select newest introducers to publish -- Don't request relay tag for every session if we have enough introducers -- Update timestamp for non-reachable or hidden router -- Reset streaming routing path if duplicated SYN received -- Update LeaseSet if inbound tunnel failed -- Reseeds list -### Fixed -- Crash when a destination gets terminated -- Expired offline signature upon destination creation -- Race condition between local RouterInfo buffer creation and sending it through the transports - -## [2.52.0] - 2024-05-12 -### Added -- Separate threads for persisting RouterInfos and profiles to disk -- Give preference to address with direct connection -- Exclude addresses with incorrect static or intro key -- Avoid two firewalled routers in the row in tunnel -- Drop unsolicited database search replies -### Changed -- Increase number of hashes to 16 in exploratory lookup reply -- Reduce number of a RouterInfo lookup attempts to 5 -- Reset stream RTO if outbound tunnel was changed -- Insert previously excluded floodfill back when successfully connected -- Increase maximum stream resend attempts to 9 -- Reply to exploratory lookups with only confirmed routers if low tunnel build rate -- Don't accept too old RouterInfo -- Build client tunnels through confirmed routers only if low tunnel build rate -- Manage netDb requests more frequently -- Don't reply with closer than us only floodfills for lookup -### Fixed -- Crash on router lookup if exploratory pool is not ready -- Race condition in excluded peers for next lookup -- Excessive number of lookups for same destination -- Race condition with transport peers during shutdown -- Corrupted RouterInfo files - -## [2.51.0] - 2024-04-06 -### Added -- Non-blocking mode for UDP sockets -- Set SSU2 socket buffer size based on bandwidth limit -- Encrypted tunnel tests -- Support for multiple UDP server tunnels on one destination -- Publish medium congestion indication -- Local domain sockets for SOCKS proxy upstream -- Tunnel status "declined" in web console -- SAM error reply "Incompatible crypto" if remote destination has incompatible crypto -- Reduce amount of traffic by handling local message drops -- Keep SSU2 socket open even if it fails to bind -- Lower SSU2 resend traffic spikes -- Expiration for messages in SSU2 send queue -- Use EWMA for stream RTT estimation -- Request choking delay if too many NACKs in stream -- Allow 0ms latency for tunnel -- Randomize tunnels selection for tests -### Changed -- Upstream SOCKS proxy from SOCKS4 to SOCKS5 -- Transit tunnels limit to 4 bytes. Default value to 10K -- Reply CANT_REACH_PEER if connect to ourselves in SAM -- Don't send already expired I2NP messages -- Use monotonic timer to measure tunnel test latency -- Standard NTCP2 frame doesn't exceed 16K -- Always send request through tunnels in case of restricted routes -- Don't delete connected routers from NetDb -- Send lookup reply directly to reply tunnel gateway if possible -- Reduce unreachable router ban interval to 8 minutes -- Don't request banned routers / don't try to connect to unreachable router -- Consider 'M' routers as low bandwidth -- Limit minimal received SSU2 packet size to 40 bytes -- Bob picks peer test session only if Charlie's address supports peer testing -- Reject peer test msg 2 if peer testing is not supported -- Don't request termination if SSU2 session was not established -- Set maximum SSU2 queue size depending on RTT value -- New streaming RTT calculation algorithm -- Don't double initial RTO for streams when changing tunnels -- Restore failed tunnel if test or data for inbound tunnel received -- Don't fail last remaining tunnel in pool -- Publish LeasetSet again if local destination was not ready or no tunnels -- Make more attempts to pick high bandwidth hop for client tunnel -- Reduced SSU2 session termination timeout to 165 seconds -- Reseeds list -### Fixed -- ECIESx25519 symmetric key tagset early expiration -- Encrypted LeaseSet lookup -- Outbound tunnel build fails if it's endpoint is the same as reply tunnel gateway -- I2PControl RouterManager returns invalid JSON when unknown params are passed -- Mix of data between different UDP sessions on the same server -- TARGET_OS_SIMULATOR check -- Handling of "reservedrange" param -- New NTCP2 session gets teminated upon termination of old one -- New SSU2 session gets teminated upon termination of old one -- Peer test to non-supporting router -- Streaming ackThrough off 1 if number of NACKs exceeds 255 -- Race condition in ECIESx25519 tags table -- Good tunnel becomes failed -- Crash when packet comes to terminated stream -- Stream hangs during LeaseSet update - -## [2.50.2] - 2024-01-06 -###Fixed -- Crash with OpenSSL 3.2.0 -- False positive clock skew detection - -## [2.50.1] - 2023-12-23 -###Fixed -- Support for new EdDSA usage behavior in OpenSSL 3.2.0 - -## [2.50.0] - 2023-12-18 -### Added -- Support of concurrent ACCEPTs on SAM 3.1 -- Haiku OS support -- Low bandwidth and far routers can expire before 1 hour -### Changed -- Don't pick too active peer for first hop -- Try peer test again if status is Unknown -- Send peer tests with random delay -- Reseeds list -### Fixed -- XSS vulnerability in addresshelper -- Publishing NAT64 ipv6 addresses -- Deadlock in AsyncSend callback - -## [2.49.0] - 2023-09-18 -### Added -- Handle SOCK5 authorization with empty user/password -- Drop incoming transport sessions from too old or from future routers -- Memory pool for router profiles -- Allow 0 hops in explicitPeers -### Changed -- Separate network and testing status -- Remove AVX code -- Improve NTCP2 transport session logging -- Select router with ipv4 for tunnel endpoint -- Consider all addresses non-published for U and H routers even if they have host/port -- Don't pick completely unreachable routers for tunnels -- Exclude SSU1 introducers from SSU2 addresses -- Don't create paired inbound tunnel if length is different -- Remove introducer from RouterInfo after 60 minutes -- Reduce SSU2 keep alive interval and add keep alive interval variance -- Don't pick too old sessions for introducer -### Fixed -- Version of the subnegotiation in user/password SOCKS5 response -- Send keepalive for existing session with introducer -- Buffer offset for EVP_EncryptFinal_ex() to include outlen -- Termination block size processing for transport sessions -- Crash if deleted BOB destination was shared between few BOB sessions -- Introducers with zero tag -- Padding for SSU2 path response - -## [2.48.0] - 2023-06-12 -### Added -- Allow user/password authentication method for SOCK5 proxy -- Publish reject all congestion cap 'G' if transit is not accepted -- 'critical' log level -- Print b32 on webconsole destination page -- Webconsole button to drop a remote LeaseSet -- limits.zombies param - minimum percentage of successfully created tunnels for routers cleanup -- Recognize real routers if successfully connected or responded to tunnel build request -### Changed -- Bypass slow transport sessions for first hop selection -- Limit AESNI inline asm to x86/x64 -- Create smaller I2NP packets if possible -- Make router unreachable if AEAD tag verification fails in SessionCreated -- Don't include a router to floodfills list until it's confirmed as real -- Drop LeaseSet store request if not floodfill -- Bypass medium congestion('D') routers for client tunnels -- Publish encrypted RouterInfo through tunnels -- Check if s is valid x25519 public key -- Check if socket is open before sending data in SSU2 -### Fixed -- Webconsole empty page if destination is not found -- i2p.streaming.answerPings param -- Reload tunnels -- Address caps for unspecified ipv6 address -- Incomplete HTTP headers in I2P tunnels -- SSU2 socket network exceptions on Windows -- Use of 'server' type tunnel port as inport (#1936) - -## [2.47.0] - 2023-03-11 -### Added -- Congestion caps -- SAM UDP port parameter -- Support domain addresses for yggdrasil reseeds -### Changed -- DHT for floodfills instead plain list -- Process router's messages in separate thread -- Don't publish non-reachable router -- Send and check target destination in first streaming SYN packet -- Reseeds list -### Fixed -- Memory leak in windows network state detection -- Reseed attempts from invalid address - -## [2.46.1] - 2023-02-20 -### Fixed -- Race condition while getting router's peer profile -- Creation of new router.info -- Displaying LeaseSets in the webconsole -- Crash when processing ACK request - -## [2.46.0] - 2023-02-15 -### Added -- Limit number of acked SSU2 packets to 511 -- Localization to Swedish, Portuguese, Turkish, Polish -- Periodically send Datetime block in NTCP2 and SSU2 -- Don't select random port from reserved -- In memory table for peer profiles -- Store if router was unreachable in it's peer profile -- Show IPv6 addresses in square brackets in webconsole -- Check referer when processing Addresshelper -### Changed -- Algorithm for tunnel creation success rate calculation -- Drop incoming NTCP2 and SSU2 connection if published IP doesn't match actual endpoint -- Exclude actually unreachable router from netdb for 2 hours -- Select first hop from high bandwidth peers for client tunnels -- Drop too long or too short LeaseSet -- Delete router from netdb if became invalid after update -- Terminate existing session if clock skew detected -- Close previous UDP socket if open before reopening -- Minimal version for floodfill is 0.9.51 -- Sort transports by endpoints in webconsole -### Fixed -- Deadlock during processing I2NP block with Garlic in ECIES encrypted message to router -- Race condition with encrypted LeaseSets -- HTTP query detection -- Connection attempts to IPs from invalid ranges -- Publish "0.0.0.0" in RouterInfo -- Crash upon receiving PeerTest 7 -- Tunnels for closed SAM session socket -- Missing NTCP2 address in RouterInfo if enabled back - -## [2.45.1] - 2023-01-11 -### Added -- Full Cone NAT status error -### Changed -- Drop duplicated I2NP messages in SSU2 -- Set rejection code 30 if tunnel with id already exists -- Network status is always OK if peer test msg 5 received -### Fixed -- UPnP crash if SSU2 or NTCP2 is disabled -- Crash on termination for some platforms - -## [2.45.0] - 2023-01-03 -### Added -- Test for Symmetric NAT with peer test msgs 6 and 7 -- Webconsole "No Descriptors" router error state -- 1 and 15 seconds bandwidth calculation for i2pcontrol -- Show non-zero send queue size for transports in web console -- Compressible padding for I2P addresses -- Localization to Czech -- Don't accept incoming session from invalid/reserved addresses for NTCP2 and SSU2 -- Limit simultaneous tunnel build requests by 4 per pool -### Changed -- Removed SSU support -- Reduced bandwidth calculation interval from 60 to 15 seconds -- Increased default max transit tunnels number from 2500 to 5000 or 10000 for floodfill -- Transit tunnels limit is doubled if floodfill mode is enabled -- NTCP2 and SSU2 timestamps are rounded to seconds -- Drop RouterInfos and LeaseSets with timestamp from future -- Don't delete unreachable routers if tunnel creation success rate is too low -- Refuse duplicated incoming pending NTCP2 session from same IP -- Don't send SSU2 termination again if termination received block received -- Handle standard network error for SSU2 without throwing an exception -- Don't select overloaded peer for next tunnel -- Remove "X-Requested-With" in HTTP Proxy for non-AJAX requests -### Fixed -- File descriptors leak -- Random crash on AddressBook update -- Crash if incorrect LeaseSet size -- Spamming to log if no descriptors -- ::1 address in RouterInfo -- SSU2 network error handling (especially for Windows) -- Race condition with pending outgoing SSU2 sessions -- RTT self-reduction for long-live streams - -## [2.44.0] - 2022-11-20 -### Added -- SSL connection for server I2P tunnels -- Localization to Italian and Spanish -- SSU2 through SOCKS5 UDP proxy -- Reload tunnels through web console -- SSU2 send immediate ack request flag -- SSU2 send and verify path challenge -- Configurable ssu2.mtu4 and ssu2.mtu6 -### Changed -- SSU2 is enabled and SSU is disabled by default -- Separate network status and error -- Random selection between NTCP2 and SSU2 priority -- Added notbob.i2p to jump services -- Remove DoNotTrack flag from HTTP Request header -- Skip addresshelper page if destination was not changed -- SSU2 allow different ports from RelayReponse and HolePunch -- SSU2 resend PeerTest msg 1 and msg 2 -- SSU2 Send Retry instead SessionCreated if clock skew detected -### Fixed -- Long HTTP headers for HTTP proxy and HTTP server tunnel -- SSU2 resends and resend limits -- Crash at startup if addressbook is disabled -- NTCP2 ipv6 connection through SOCKS5 proxy -- SSU2 SessionRequest with zero token -- SSU2 MTU less than 1280 -- SSU2 port=1 -- Incorrect addresses from network interfaces -- Definitions for Darwin PPC; do not use pthread_setname_np - -## [2.43.0] - 2022-08-22 -### Added -- Complete SSU2 implementation -- 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 -- Tunnel length variance -- Localization to French -- Daily cleanup of obsolete peer profiles -- Ordered jump services list in HTTP proxy -- Win32 service -- Show port for local non-published SSU addresses in web console -### Changed -- Maximum RouterInfo length increased to 3K -- Skip unknown addresses in RouterInfo -- 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 -- 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 -### Fixed -- Encrypted LeaseSet for EdDSA signature -- Clients tunnels are not built if clock is not synced on start -- Incorrect processing of i2cp.dontPublishLeaseSet param -- UDP tunnels reload -- Build for LibreSSL 3.5.2 -- Race condition in short tunnel build message -- Race condition in local RouterInfo buffer allocation - -## [2.41.0] - 2022-02-20 -### Added -- Clock syncronization through SSU -- Drop routers older than 6 months on start -- Localization to German -- Don't send streaming ack too frequently -- Select compatible outbound tunnel for I2CP messages -- Restart webconsole's acceptor in case of exception -### Changed -- Use builtin bitswap for endian on windows -- Send SessionCreated before connection close if clock skew -- Try another floodfill for publishing if no compatible tunnels found -- Reduce memory usage for RouterInfo structures -- Avoid duplicated addresses in RouterInfo. Check presence of netId and version -- Use TCP/IP sockets for I2CP on Android instead local sockets -- Return uptime as integer in I2PControl -- Reseed servers list/cerificates -- Webconsole's dark style colors -### Fixed -- Attempt to use Yggdrasil on start on Android -- Attempts to send peer tests to itself -- Severe packets drop in SSU -- Crash on tunnel tests -- Loading addressbook subscriptions from config -- Multiple I2CP session to the same destination -- Build on Apple Silicon - -## [2.40.0] - 2021-11-29 -### Added -- Keep alive parameter for client tunnels -- Support openssl 3.0.0 -- Localization to Armenian -- Show git commit info in version -- Windows menu item for opening datadir -- Reseed if too few floodfills -- Don't publish old and replacing tunnel in LeaseSet -- Webconsole light/dark theme depending on system settings (via CSS) -### Changed -- Set gzip compression to false by default -- Build tunnel through ECIES routers only -- Removed ElGamal support for tunnels -- Moved webconsole resources to separate file -- Pick tunnels with compatible transport with another tunnel of floodfill -- Use common cleanup timer for all SSU sessions -- Reduced memory usage -- Reseed servers list -- i18n code called from ClientContext -### Fixed -- Tunnels reload -- Some typos in log messages -- Cleanup relay requests table -- Server tunnel is not published -- Build on GNU/Hurd. Disable pthread_setname_np -- Crash when incorrect sigtype used with blinding - -## [2.39.0] - 2021-08-23 -### Added -- Short tunnel build messages -- Localization. To: Russian, Ukrainian, Turkmen, Uzbek and Afrikaans -- Custom CSS styles for webconsole -- Avoid slow tunnels with more than 250 ms per hop -- Process DELAY_REQUESTED streaming option -- "certsdir" options for certificates location -- Keep own RouterInfo in NetBb -- Pick ECIES routers only for tunnels on non-x64 -- NTP sync through ipv6 -- Allow ipv6 addresses for UDP server tunnels -### Changed -- Rekey of all routers to ECIES -- Better distribution for random tunnel's peer selection -- Yggdrasil reseed for v0.4, added two more -- Encryption type 0,4 by default for server tunnels -- Handle i2cp.dontPublishLeaseSet param for all destinations -- reg.i2p for subscriptions -- LeaseSet type 3 by default -- Don't allocate payload buffer for every single ECIESx25519 message -- Prefer public ipv6 instead rfc4941 -- Optimal padding for one-time ECIESx25519 message -- Don't send datetime block for one-time ECIESx25519 message with one-time key -- Router with expired introducer is still valid -- Don't disable floodfill if still reachable by ipv6 -- Set minimal version for floodfill to 0.9.38 -- Eliminate extra lookups for sequential fragments on tunnel endpoint -- Consistent path for explicit peers -- Always create new tunnel from exploratory pool -- Don't try to connect to a router not reachable from us -- Mark additional ipv6 addresses/nets as reserved (#1679) -### Fixed -- Zero-hop tunnels -- Crash upon SAM session termination -- Build with boost < 1.55.0 -- Address type for NTCP2 acceptors -- Check of ipv4/ipv6 address -- Request router to send to if not in NetDb -- Count outbound traffic for zero-hop tunnels -- URLdecode domain for registration string generator in webconsole - -## [2.38.0] - 2021-05-17 -### Added -- Publish ipv6 introducers -- Bind ipv6 or yggdrasil NTCP2 acceptor to specified address -- Support .b32.i2p addresses and hostnames for SAM STREAM CREATE -- ipv6 peer tests -- Publish iexp param for introducers -- Show ipv6 network status on the webconsole -- EdDSA signing keys can also be blinded -- Show router version on the webconsole -### Changed -- Rekey of all routers but floodfills to ECIES -- Increased number of precalculated x25519 keys to 15 -- Don't publish LeaseSet without inbound tunnels -- Reseed from compatible address(ipv4 or ipv6) -- Recongnize v4 and v6 SSU addresses without host -- Inbound tunnel gateway must be ipv4 compatible -- Don't select next introducers from existing sessions -- Set X bandwidth for floodfill by default -### Fixed -- Incoming ECIES-x25519 session doesn't send updated LeaseSet -- Unique local address for server tunnels -- Race condition for LeaseSet creation in I2CP -- Relay tag for ipv6 introducer -- Already expired introducers -- Find connected router for first peer in tunnel -- Failed outgoing ECIES-x25519 session's tagset stays forever -- Yggdrasil address disappears if router becomes unreachable through ipv6 -- Ignore SSU address/introducers if port is not specified -- Check identity and signature length for SSU SessionConfirmed - -## [2.37.0] - 2021-03-15 -### Added -- Address registration line for reg.i2p and stats.i2p through the web console -- "4" and "6" caps for addresses without published IP address -- Mesh and Proxy network statuses -- Symmetric NAT network status error -- Bind server tunnel connection to specified address -- lookuplocal BOB extended command -- address4 and address6 parameters to bind outgoing connections to -- Rekey of low-bandwidth routers to ECIES -- Popup notification windows when unable to parse config for Windows -### Changed -- Floodfills with "U" cap are not ignored anymore -- Check transports reachability between tunnel peers and between router and floodfill -- NTCP2 and reseed HTTP proxy support authorization now -- Show actual IP addresses for proxy connections -- Publish and handle SSU addreses without host -- Outbound tunnel endpoint must be ipv4 compatible -- Logging optimization -- Removed Windows service -### Fixed -- Incoming SSU session terminates after 5 seconds -- Outgoing NTCP2 ipv4 session even if ipv4 is disabled -- No incoming Yggdrasil connection if connected through NTCP2 proxy -- Race condition between tunnel build and floodfill requests decryption for ECIES routers -- Numeric bandwidth limitation -- Yggdrasil for Android - -## [2.36.0] - 2021-02-15 -### Added -- Encrypted lookup and publications to ECIES-x25519 floodfiils -- Yggdrasil transports and reseeds -- Dump addressbook in hosts.txt format -- Request RouterInfo through exploratory tunnels if direct connection to fllodfill is not possible -- Threads naming -- Check if public x25519 key is valid -- ECIES-X25519-AEAD-Ratchet for shared local destination -- LeaseSet creation timeout for I2CP session -- Resend RouterInfo after some interval for longer NTCP2 sessions -- Select reachable router of inbound tunnel gateway -- Reseed if no compatible routers in netdb -- Refresh on swipe in Android webconsole -### Changed -- reg.i2p for default addressbook instead inr.i2p -- ECIES-x25519 (crypto type 4) for new routers -- Try to connect to all compatible addresses from peer's RouterInfo -- Replace LeaseSet completely if store type changes -- Try ECIES-X25519-AEAD-Ratchet tag before ElGamal -- Don't detach ECIES-X25519-AEAD-Ratchet session from destination immediately -- Viewport and styles on error in HTTP proxy -- Don't create notification when Windows taskbar restarted -- Cumulative SSU ACK bitfields -- limit tunnel length to 8 hops -- Limit tunnels quantity to 16 -### Fixed -- Handling chunked HTTP response in addressbook -- Missing ECIES-X25519-AEAD-Ratchet tags for multiple streams with the same destination -- Correct NAME for NAMING REPLY in SAM -- SSU crash on termination -- Offline signature length for stream close packet -- Don't send updated LeaseSet through a terminated session -- Decryption of follow-on ECIES-X25519-AEAD-Ratchet NSR messages -- Non-confirmed LeaseSet is resent too late for ECIES-X25519-AEAD-Ratchet session - -## [2.35.0] - 2020-11-30 -### Added -- ECIES-x25519 routers -- Random intro keys for SSU -- Graceful shutdown timer for windows -- Send queue for I2CP messages -- Update DSA router keys to EdDSA -- TCP_QUICKACK for NTCP2 sockets on Linux -### Changed -- Exclude floodfills with DSA signatures and < 0.9.28 -- Random intervals between tunnel tests and manage for tunnel pools -- Don't replace an addressbook record by one with DSA signature -- Publish RouterInfo after update -- Create paired inbound tunnels if no inbound tunnels yet -- Reseed servers list -### Fixed -- Transient signature length, if different from identity -- Terminate I2CP session if destroyed -- RouterInfo publishing confirmation -- Check if ECIES-X25519-AEAD-Ratchet session expired before generating more tags -- Correct block size for delivery type local for ECIES-X25519-AEAD-Ratchet - -## [2.34.0] - 2020-10-27 -### Added -- Ping responses for streaming -- STREAM FORWARD for SAM -- Tunnels through ECIES-x25519 routers -- Single thread for I2CP -- Shared transient destination between proxies -- Database lookups from ECIES destinations with ratchets response -- Handle WebDAV HTTP methods -- Don't try to connect or build tunnels if offline -- Validate IP when trying connect to remote peer -- Handle ICMP responses and WinAPI errors for SSU -### Changed -- Removed NTCP -- Dropped gcc 4.7 support -- Encyption type 0,4 by default for client tunnels -- Stripped out some HTTP header for HTTP server response -- HTTP 1.1 addressbook requests -- Set LeaseSet type to 3 for ratchets if not specified -- Handle SSU v4 and v6 messages in one thread -- Eliminate DH keys thread -### Fixed -- Random crashes on I2CP session disconnect -- Stream through racthets hangs if first SYN was not acked -- Check "Last-Modified" instead "If-Modified-Since" for addressbook reponse -- Trim behind ECIESx25519 tags -- Few bugs with Android main activity -- QT visual and layout issues - -## [2.33.0] - 2020-08-24 -### Added -- Shared transient addresses -- crypto.ratchet.inboundTags paramater -- Multiple encryption keys through I2CP -- Pre-calculated x25519 ephemeral keys -- Change datagram routing path if nothing comes back in 10 seconds -- Shared routing path for datagram session -### Changed -- UDP tunnels send mix of repliable and raw datagrams in bulk -- Encrypt SSU packet again upon resend -- Start new tunnel message if remaining buffer is too small -- Use LeaseSet2 for ECIES-X25519-AEAD-Ratchet automatically -- Save new ECIES-X25519-AEAD-Ratchet session with NSR tagset -- Generate random padding lengths for ECIES-X25519-AEAD-Ratchet in bulk -- Webconsole layout -- Reseed servers list -### Fixed -- Don't connect through terminated SAM destination -- Differentiate UDP server sessions by port -- ECIES-X25519-AEAD-Ratchet through I2CP -- Don't save invalid address to AddressBook -- ECDSA signatures names in SAM -- AppArmor profile - -## [2.32.1] - 2020-06-02 -### Added -- Read explicit peers in tunnels config -### Fixed -- Generation of tags for detached sessions -- Non-updating LeaseSet1 -- Start when deprecated websocket options present in i2pd.conf - -## [2.32.0] - 2020-05-25 -### Added -- Multiple encryption types for local destinations -- Next key and tagset for ECIES-X25519-AEAD-Ratchet -- NTCP2 through SOCKS proxy -- Throw error message if any port to bind is occupied -- gzip parameter for UDP tunnels -- Show ECIES-X25519-AEAD-Ratchet sessions and tags on the web console -- Simplified implementation of gzip for no compression mode -- Allow ECIES-X25519-AEAD-Ratchet session restart after 2 minutes -- Added logrotate config for rpm package -### Changed -- Select peers for client tunnels among routers >= 0.9.36 -- Check ECIES flag for encrypted lookup reply -- Streaming MTU size 1812 for ECIES-X25519-AEAD-Ratchet -- Don't calculate checksum for Data message send through ECIES-X25519-AEAD-Ratchet -- Catch network connectivity status for Windows -- Stop as soon as no more transit tunnels during graceful shutdown for Android -- RouterInfo gzip compression level depends on size -- Send response to received datagram from ECIES-X25519-AEAD-Ratchet session -- Update webconsole functional -- Increased max transit tunnels limit -- Reseeds list -- Dropped windows support in cmake -### Fixed -- Correct timestamp check for LeaseSet2 -- Encrypted leaseset without authentication -- Change SOCKS proxy connection response for clients without socks5h support (#1336) - -## [2.31.0] - 2020-04-10 -### Added -- NTCP2 through HTTP proxy -- Publish LeaseSet2 for I2CP destinations -- Show status page on main activity for android -- Handle ECIESFlag in DatabaseLookup at floodfill -- C++17 features for eligible compilers -### Changed -- Droped Websockets and Lua support -- Send DeliveryStatusMsg for LeaseSet for ECIES-X25519-AEAD-Ratchet -- Keep sending new session reply until established for ECIES-X25519-AEAD-Ratchet -- Updated SSU log messages -- Reopen SSU socket on exception -- Security hardening headers in web console -- Various web console changes -- Various QT changes -### Fixed -- NTCP2 socket descriptors leak -- Race condition with router's identity in transport sessions -- Not terminated streams remain forever - -## [2.30.0] - 2020-02-25 -### Added -- Single threaded SAM -- Experimental support of ECIES-X25519-AEAD-Ratchet crypto type -### Changed -- Minimal MTU size is 1280 for ipv6 -- Use unordered_map instead map for destination's sessions and tags list -- Use std::shuffle instead std::random_shuffle -- SAM is single threaded by default -- Reseeds list -### Fixed -- Correct termination of streaming destination -- Extra ',' in RouterInfo response in I2PControl -- SAM crash on session termination -- Storage for Android 10 - -## [2.29.0] - 2019-10-21 -### Added -- Client auth flag for b33 address -### Changed -- Remove incoming NTCP2 session from pending list when established -- Handle errors for NTCP2 SessionConfrimed send -### Fixed -- Failure to start on Windows XP -- SAM crash if invalid lookup address -- Possible crash when UPnP enabled on shutdown - -## [2.28.0] - 2019-08-27 -### Added -- RAW datagrams in SAM -- Publishing encrypted LeaseSet2 with DH or PSH authentication -- Ability to disable battery optimization for Android -- Transport Network ID Check -### Changed -- Set and handle published encrypted flag for LeaseSet2 -### Fixed -- ReceiveID changes in the same stream -- "\r\n" command terminator in SAM -- Addressbook lines with signatures - -## [2.27.0] - 2019-07-03 -### Added -- Support of PSK and DH authentication for encrypted LeaseSet2 -### Changed -- Uptime is based on monotonic timer -### Fixed -- BOB status command response -- Correct NTCP2 port if NTCP is disabled -- Flood encrypted LeaseSet2 with store hash - -## [2.26.0] - 2019-06-07 -### Added -- HTTP method "PROPFIND" -- Detection of external ipv6 address through the SSU -- NTCP2 publishing depends on network status -### Changed -- ntcp is disabled by default, ntcp2 is published by default -- Response to BOB's "list" command -- ipv6 address is not longer NTCP's local endpoint's address -- Reseeds list -- HTTP_REFERER stripping in httpproxy (#823) -### Fixed -- Check and handle incorrect BOB input -- Ignore introducers for NTCP or NTCP2 addresses -- RouterInfo check from NTCP2 - ## [2.25.0] - 2019-05-09 ### Added - Create, publish and handle encrypted LeaseSet2 -- Support of b33 addresses +- Support of b33 addresses - RedDSA key blinding - .b32.i2p addresses in jump links - ntcp2.addressv6 parameter @@ -921,7 +36,7 @@ - Correct SAM response for invalid key - SAM crash on termination for Windows - Race condition for publishing - + ## [2.23.0] - 2019-01-21 ### Added - Standard LeaseSet2 support @@ -980,7 +95,7 @@ - NTCP2 is enabled by default - Show lease's expiration time in readable format in the web console ### Fixed -- Correct names for transports in the web console +- Correct names for transports in the web console ## [2.19.0] - 2018-06-26 ### Added @@ -1144,7 +259,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..2cb10225 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2025, The PurpleI2P Project +Copyright (c) 2013-2015, The PurpleI2P Project All rights reserved. diff --git a/Makefile b/Makefile index 0d4ca48c..3dfaaade 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,24 @@ -.DEFAULT_GOAL := all - SYS := $(shell $(CXX) -dumpmachine) - -ifneq (, $(findstring darwin, $(SYS))) - SHARED_SUFFIX = dylib -else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) - SHARED_SUFFIX = dll -else - SHARED_SUFFIX = so -endif - -SHLIB := libi2pd.$(SHARED_SUFFIX) +SHLIB := libi2pd.so ARLIB := libi2pd.a -SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX) -ARLIB_LANG := libi2pdlang.a -SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX) +SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a -SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX) -ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd +GREP := grep +DEPS := obj/make.dep LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client -WRAP_SRC_DIR := libi2pd_wrapper -LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon -# import source files lists include filelist.mk -USE_STATIC := $(or $(USE_STATIC),no) -USE_UPNP := $(or $(USE_UPNP),no) -DEBUG := $(or $(DEBUG),yes) - -# for debugging purposes only, when commit hash needed in trunk builds in i2pd version string -USE_GIT_VERSION := $(or $(USE_GIT_VERSION),no) - -# for MacOS only, waiting for "1", not "yes" -HOMEBREW := $(or $(HOMEBREW),0) +USE_AESNI := yes +USE_AVX := yes +USE_STATIC := no +USE_MESHNET := no +USE_UPNP := no +DEBUG := yes ifeq ($(DEBUG),yes) CXX_DEBUG = -g @@ -46,8 +27,8 @@ else LD_DEBUG = -s endif -ifneq (, $(DESTDIR)) - PREFIX = $(DESTDIR) +ifeq ($(WEBSOCKETS),1) + NEEDED_CXXFLAGS += -DWITH_EVENTS endif ifneq (, $(findstring darwin, $(SYS))) @@ -57,57 +38,36 @@ 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/Win32Service.cpp Win32/Win32App.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) -DEFINES += -DOPENSSL_SUPPRESS_DEPRECATED -NEEDED_CXXFLAGS += -MMD -MP - -ifeq ($(USE_GIT_VERSION),yes) - GIT_VERSION := $(shell git describe --tags) - DEFINES += -DGITVER=$(GIT_VERSION) +ifeq ($(USE_MESHNET),yes) + NEEDED_CXXFLAGS += -DMESHNET endif -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)) -DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) -DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) $(WRAP_LIB_OBJS:.o=.d) +NEEDED_CXXFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary -all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) +all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_obj_dir: + @mkdir -p obj + @mkdir -p obj/Win32 @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) - @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) - @mkdir -p obj/$(WRAP_SRC_DIR) - @mkdir -p obj/Win32 -api: $(SHLIB) $(ARLIB) -client: $(SHLIB_CLIENT) $(ARLIB_CLIENT) -lang: $(SHLIB_LANG) $(ARLIB_LANG) -api_client: api client lang -wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) +api: mk_obj_dir $(SHLIB) $(ARLIB) +api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -116,53 +76,40 @@ wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## -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. -obj/%.o: %.cpp | mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $< +deps: mk_obj_dir + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) + @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) + +obj/%.o: %.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) -$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) - $(CXX) $(DEFINES) $(LDFLAGS) -o $@ $^ $(LDLIBS) +DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) + $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ endif -$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) -ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) -endif +$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ -$(SHLIB_WRAP): $(WRAP_LIB_OBJS) -ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) -endif - -$(SHLIB_LANG): $(LANG_OBJS) -ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) -endif - -$(ARLIB): $(LIB_OBJS) +$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) $(AR) -r $@ $^ -$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) - $(AR) -r $@ $^ - -$(ARLIB_WRAP): $(WRAP_LIB_OBJS) - $(AR) -r $@ $^ - -$(ARLIB_LANG): $(LANG_OBJS) +$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) $(AR) -r $@ $^ clean: $(RM) -r obj $(RM) -r docs/generated - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) -strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) +strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) @@ -180,13 +127,11 @@ doxygen: .PHONY: all .PHONY: clean +.PHONY: deps .PHONY: doxygen .PHONY: dist .PHONY: last-dist .PHONY: api .PHONY: api_client -.PHONY: client -.PHONY: lang .PHONY: mk_obj_dir .PHONY: install -.PHONY: strip diff --git a/Makefile.bsd b/Makefile.bsd index 1c911802..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_system -lboost_program_options - ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -CXXVER := $(shell $(CXX) -dumpversion|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 706f9811..64301c02 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -1,49 +1,54 @@ # 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_system -lboost_filesystem -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 += -maes +endif +ifeq ($(USE_AVX),1) + CXXFLAGS += -mavx 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}/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 - @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}/etc/i2pd/tunnels.conf.d ${PREFIX}/var/lib/i2pd/tunnels.d + install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd + @gzip debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 + @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @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 - @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf + @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf \ No newline at end of file diff --git a/Makefile.linux b/Makefile.linux index 4ea39e22..d1ccf143 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,5 @@ # set defaults instead redefine -CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi +CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation LDFLAGS ?= ${LD_DEBUG} ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time @@ -9,17 +9,19 @@ 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++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 + 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\.[7-9]"),3) # >= 4.7 + NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 +else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 + NEEDED_CXXFLAGS += -std=c++0x +else ifeq ($(shell expr match ${CXXVER} "[5-9]"),1) # gcc >= 5 + NEEDED_CXXFLAGS += -std=c++11 + LDLIBS = -latomic else # not supported $(error Compiler too old) endif @@ -30,41 +32,45 @@ ifeq ($(USE_STATIC),yes) # NOTE: on glibc you will get this warning: # Using 'getaddrinfo' in statically linked applications requires at runtime # the shared libraries from the glibc version used for linking - LIBDIR := /usr/lib/$(SYS) + LIBDIR := /usr/lib + 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 LDLIBS += $(LIBDIR)/libz.a -ifeq ($(USE_UPNP),yes) - LDLIBS += $(LIBDIR)/libminiupnpc.a -endif - LDLIBS += -lpthread -ldl + LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt -ldl + USE_AESNI := no else - LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic -ifeq ($(USE_UPNP),yes) - LDLIBS += -lminiupnpc -endif + LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) - DEFINES += -DUSE_UPNP + CXXFLAGS += -DUSE_UPNP +ifeq ($(USE_STATIC),yes) + LDLIBS += $(LIBDIR)/libminiupnpc.a +else + LDLIBS += -lminiupnpc +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 -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 - @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 - @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf +ifeq ($(USE_AESNI),yes) +#check if AES-NI is supported by CPU +ifneq ($(shell $(GREP) -c aes /proc/cpuinfo),0) + machine := $(shell uname -m) + ifeq ($(machine), aarch64) + CXXFLAGS += -DARM64AES + else + CPU_FLAGS += -maes + endif +endif +endif + +ifeq ($(USE_AVX),yes) +#check if AVX supported by CPU +ifneq ($(shell $(GREP) -c avx /proc/cpuinfo),0) + CPU_FLAGS += -mavx +endif +endif diff --git a/Makefile.mingw b/Makefile.mingw index 32d60764..e2f9e857 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,50 +1,56 @@ -# Build application with GUI (tray, main window) -USE_WIN32_APP := yes - +USE_WIN32_APP=yes +CXX = g++ WINDRES = windres +CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +NEEDED_CXXFLAGS = -std=c++11 +INCFLAGS = -Idaemon -I. +LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ -CXXFLAGS := $(CXX_DEBUG) -fPIC -msse -INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -static -fPIC -msse - -NEEDED_CXXFLAGS += -std=c++20 -DEFINES += -DWIN32_LEAN_AND_MEAN +# 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 \ - -lole32 \ - -luuid \ + -liphlpapi \ + -lstdc++ \ -lpthread ifeq ($(USE_WIN32_APP), yes) - DEFINES += -DWIN32_APP + CXXFLAGS += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif +# don't change following line to ifeq ($(USE_AESNI),yes) !!! +ifeq ($(USE_AESNI),1) + CPU_FLAGS += -maes +else + CPU_FLAGS += -msse +endif + +ifeq ($(USE_AVX),1) + CPU_FLAGS += -mavx +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 $@ +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ diff --git a/Makefile.osx b/Makefile.osx index 52282307..d673d3ef 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,20 +1,17 @@ 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 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_system -lboost_filesystem -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 +19,12 @@ ifeq ($(USE_UPNP),yes) endif endif -OSARCH = $(shell uname -p) - -ifneq ($(OSARCH),powerpc) +ifeq ($(USE_AESNI),1) + CXXFLAGS += -maes +else CXXFLAGS += -msse endif + +ifeq ($(USE_AVX),1) + CXXFLAGS += -mavx +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 ce25c8f2..540e3bed 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ -[![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)](https://github.com/PurpleI2P/i2pd/releases/latest) -[![Snapcraft release](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) -[![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) -[![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* +![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release) +![GitHub](https://img.shields.io/github/license/PurpleI2P/i2pd.svg) i2pd ==== @@ -56,8 +50,6 @@ Building See [documentation](https://i2pd.readthedocs.io/en/latest/) for how to build i2pd from source on your OS. -note: i2pd with Qt GUI can be found in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository and for android in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository. - Build instructions: @@ -69,15 +61,13 @@ 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. -* 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) -* Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) -* FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) -* Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) +* GNU/Linux - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) +* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* 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/) +* Docker image - [![Build Status](https://dockerbuildbadges.quelltext.eu/status.svg?organization=meeh&repository=i2pd)](https://hub.docker.com/r/meeh/i2pd/builds/) +* FreeBSD +* Android * iOS Using i2pd @@ -86,36 +76,15 @@ Using i2pd See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf). -Localization ------------- - -You can help us with translation i2pd to your language using Crowdin platform! -Translation project can be found [here](https://crowdin.com/project/i2pd). - -New languages can be requested on project's [discussion page](https://crowdin.com/project/i2pd/discussions). - -Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) - 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 License ------- diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 48f65c27..9fffe7fb 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2023, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include "Config.h" @@ -14,10 +6,9 @@ #include "Log.h" #ifdef _WIN32 -#include "Win32Service.h" +#include "Win32/Win32Service.h" #ifdef WIN32_APP -#include -#include "Win32App.h" +#include "Win32/Win32App.h" #endif namespace i2p @@ -29,30 +20,46 @@ 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) - { - MessageBox(0, TEXT(s.c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); - } - ); - if (!Daemon_Singleton::init(argc, argv)) return false; + std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); + if (serviceControl == "install") + { + LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); + InstallService( + SERVICE_NAME, // Name of service + SERVICE_DISPLAY_NAME, // Name to display + SERVICE_START_TYPE, // Service start type + SERVICE_DEPENDENCIES, // Dependencies + SERVICE_ACCOUNT, // Service running account + SERVICE_PASSWORD // Password of the account + ); + return false; + } + else if (serviceControl == "remove") + { + LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); + UninstallService(SERVICE_NAME); + return false; + } + if (isDaemon) { LogPrint(eLogDebug, "Daemon: running as service"); 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; } - + else + LogPrint(eLogDebug, "Daemon: running as user"); return true; } @@ -61,10 +68,13 @@ 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; + + // override log + i2p::config::SetOption("log", std::string ("file")); #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..6a4f481d 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-2017, 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..4bf4ab56 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,502 +1,454 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include "ClientContext.h" -#include "Config.h" -#include "NetDb.hpp" -#include "RouterContext.h" -#include "Transports.h" -#include "Tunnel.h" -#include "version.h" -#include "resource.h" -#include "Daemon.h" -#include "Win32App.h" -#include "Win32NetState.h" - -#define ID_ABOUT 2000 -#define ID_EXIT 2001 -#define ID_CONSOLE 2002 -#define ID_APP 2003 -#define ID_GRACEFUL_SHUTDOWN 2004 -#define ID_STOP_GRACEFUL_SHUTDOWN 2005 -#define ID_RELOAD 2006 -#define ID_ACCEPT_TRANSIT 2007 -#define ID_DECLINE_TRANSIT 2008 -#define ID_DATADIR 2009 - -#define ID_TRAY_ICON 2050 -#define WM_TRAYICON (WM_USER + 1) - -#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 -#define FRAME_UPDATE_TIMER 2101 -#define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 - -namespace i2p -{ -namespace win32 -{ - DWORD g_GracefulShutdownEndtime = 0; - bool g_isWinService; - - static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) - { - HMENU hPopup = CreatePopupMenu(); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); - if(!i2p::context.AcceptsTunnels()) - InsertMenu (hPopup, -1, - i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, - ID_ACCEPT_TRANSIT, "Accept &transit"); - else - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); - if (!i2p::util::DaemonWin32::Instance ().isGraceful) - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); - else - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); - SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); - SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); - - POINT p; - if (!curpos) - { - GetCursorPos (&p); - curpos = &p; - } - - WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); - SendMessage (hWnd, WM_COMMAND, cmd, 0); - - DestroyMenu(hPopup); - } - - static void AddTrayIcon (HWND hWnd, bool notify = false) - { - NOTIFYICONDATA nid; - memset(&nid, 0, sizeof(nid)); - nid.cbSize = sizeof(nid); - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; - nid.uCallbackMessage = WM_TRAYICON; - nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); - strcpy (nid.szTip, "i2pd"); - if (notify) strcpy (nid.szInfo, "i2pd is starting"); - Shell_NotifyIcon(NIM_ADD, &nid ); - } - - static void RemoveTrayIcon (HWND hWnd) - { - NOTIFYICONDATA nid; - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - Shell_NotifyIcon (NIM_DELETE, &nid); - } - - static void ShowUptime (std::stringstream& s, int seconds) - { - int num; - - if ((num = seconds / 86400) > 0) { - s << num << " days, "; - seconds -= num * 86400; - } - if ((num = seconds / 3600) > 0) { - s << num << " hours, "; - seconds -= num * 3600; - } - if ((num = seconds / 60) > 0) { - s << num << " min, "; - seconds -= num * 60; - } - s << seconds << " seconds\n"; - } - - template static void ShowTransfered (std::stringstream& s, size transfer) - { - auto bytes = transfer & 0x03ff; - transfer >>= 10; - auto kbytes = transfer & 0x03ff; - transfer >>= 10; - auto mbytes = transfer & 0x03ff; - transfer >>= 10; - auto gbytes = transfer; - - if (gbytes) - s << gbytes << " GB, "; - if (mbytes) - s << mbytes << " MB, "; - if (kbytes) - s << kbytes << " KB, "; - s << bytes << " Bytes\n"; - } - - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) - { - switch (status) - { - case eRouterStatusOK: s << "OK"; 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 eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); - break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - case eRouterErrorFullConeNAT: - s << " - " << tr("Full cone NAT"); - break; - case eRouterErrorNoDescriptors: - s << " - " << tr("No Descriptors"); - break; - default: ; - } - } - } - - static void PrintMainWindowText (std::stringstream& s) - { - s << "\n"; - s << "Status: "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); - if (i2p::context.SupportsV6 ()) - { - s << " / "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); - } - s << "; "; - s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; - s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); - if (g_GracefulShutdownEndtime != 0) - { - DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; - s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); - } - else - s << "\n"; - s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; - s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; - s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); - s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); - s << "\n"; - s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; - s << "Tunnels: "; - s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; - s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; - s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; - s << "\n"; - } - - static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - static UINT s_uTaskbarRestart; - - switch (uMsg) - { - case WM_CREATE: - { - s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); - AddTrayIcon (hWnd, true); - break; - } - case WM_CLOSE: - { - RemoveTrayIcon (hWnd); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - PostQuitMessage (0); - break; - } - case WM_COMMAND: - { - switch (LOWORD(wParam)) - { - case ID_ABOUT: - { - std::stringstream text; - text << "Version: " << I2PD_VERSION << " " << CODENAME; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_EXIT: - { - PostMessage (hWnd, WM_CLOSE, 0, 0); - return 0; - } - case ID_ACCEPT_TRANSIT: - { - i2p::context.SetAcceptsTunnels (true); - std::stringstream text; - text << "I2Pd now accept transit tunnels"; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_DECLINE_TRANSIT: - { - i2p::context.SetAcceptsTunnels (false); - std::stringstream text; - text << "I2Pd now decline new transit tunnels"; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_GRACEFUL_SHUTDOWN: - { - i2p::context.SetAcceptsTunnels (false); - SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes - SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second - g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; - i2p::util::DaemonWin32::Instance ().isGraceful = true; - return 0; - } - case ID_STOP_GRACEFUL_SHUTDOWN: - { - i2p::context.SetAcceptsTunnels (true); - KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - g_GracefulShutdownEndtime = 0; - i2p::util::DaemonWin32::Instance ().isGraceful = false; - return 0; - } - case ID_RELOAD: - { - i2p::client::context.ReloadConfig(); - std::stringstream text; - text << "I2Pd reloading configs..."; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_CONSOLE: - { - char buf[30]; - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); - ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); - return 0; - } - case ID_APP: - { - ShowWindow(hWnd, SW_SHOW); - SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); - return 0; - } - case ID_DATADIR: - { - std::string datadir(i2p::fs::GetDataDir()); - ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); - return 0; - } - } - break; - } - case WM_SYSCOMMAND: - { - switch (wParam) - { - case SC_MINIMIZE: - { - ShowWindow(hWnd, SW_HIDE); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - return 0; - } - case SC_CLOSE: - { - std::string close; i2p::config::GetOption("close", close); - if (0 == close.compare("ask")) - switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" - " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", - "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) - { - case IDYES: close = "minimize"; break; - case IDNO: close = "exit"; break; - default: return 0; - } - if (0 == close.compare("minimize")) - { - ShowWindow(hWnd, SW_HIDE); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - return 0; - } - if (0 != close.compare("exit")) - { - ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); - return 0; - } - } - } - [[fallthrough]]; - } - case WM_TRAYICON: - { - switch (lParam) - { - case WM_LBUTTONUP: - case WM_RBUTTONUP: - { - SetForegroundWindow (hWnd); - ShowPopupMenu(hWnd, NULL, -1); - PostMessage (hWnd, WM_APP + 1, 0, 0); - break; - } - } - break; - } - case WM_TIMER: - { - switch(wParam) - { - case IDT_GRACEFUL_SHUTDOWN_TIMER: - { - g_GracefulShutdownEndtime = 0; - PostMessage (hWnd, WM_CLOSE, 0, 0); // exit - return 0; - } - case IDT_GRACEFUL_TUNNELCHECK_TIMER: - { - if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) - PostMessage (hWnd, WM_CLOSE, 0, 0); - else - SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); - return 0; - } - case FRAME_UPDATE_TIMER: - { - InvalidateRect(hWnd, NULL, TRUE); - return 0; - } - } - break; - } - case WM_PAINT: - { - HDC hDC; - PAINTSTRUCT ps; - RECT rp; - HFONT hFont; - std::stringstream s; PrintMainWindowText (s); - hDC = BeginPaint (hWnd, &ps); - GetClientRect(hWnd, &rp); - SetTextColor(hDC, 0x00D43B69); - hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); - SelectObject(hDC,hFont); - DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); - DeleteObject(hFont); - EndPaint(hWnd, &ps); - break; - } - default: - { - if (uMsg == s_uTaskbarRestart) - AddTrayIcon (hWnd, false); - break; - } - } - return DefWindowProc( hWnd, uMsg, wParam, lParam); - } - - bool StartWin32App (bool isWinService) - { - g_isWinService = isWinService; - if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) - { - MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); - return false; - } - // register main window - auto hInst = GetModuleHandle(NULL); - WNDCLASSEX wclx; - memset (&wclx, 0, sizeof(wclx)); - wclx.cbSize = sizeof(wclx); - wclx.style = 0; - wclx.lpfnWndProc = WndProc; - //wclx.cbClsExtra = 0; - //wclx.cbWndExtra = 0; - wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); - wclx.hCursor = LoadCursor (NULL, IDC_ARROW); - //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); - wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wclx.lpszMenuName = NULL; - wclx.lpszClassName = I2PD_WIN32_CLASSNAME; - RegisterClassEx (&wclx); - // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) - { - 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(); - return true; - } - - int RunWin32App () - { - MSG msg; - while (GetMessage (&msg, NULL, 0, 0 )) - { - TranslateMessage (&msg); - DispatchMessage (&msg); - } - return msg.wParam; - } - - void StopWin32App () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - else if(!g_isWinService) - UnSubscribeFromEvents(); - UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); - } - - bool GracefulShutdown () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); - return hWnd; - } - - bool StopGracefulShutdown () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); - return hWnd; - } -} -} +#include +#include +#include +#include "ClientContext.h" +#include "Config.h" +#include "NetDb.hpp" +#include "RouterContext.h" +#include "Transports.h" +#include "Tunnel.h" +#include "version.h" +#include "resource.h" +#include "Daemon.h" +#include "Win32App.h" +#include + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#endif + +#define ID_ABOUT 2000 +#define ID_EXIT 2001 +#define ID_CONSOLE 2002 +#define ID_APP 2003 +#define ID_GRACEFUL_SHUTDOWN 2004 +#define ID_STOP_GRACEFUL_SHUTDOWN 2005 +#define ID_RELOAD 2006 +#define ID_ACCEPT_TRANSIT 2007 +#define ID_DECLINE_TRANSIT 2008 + +#define ID_TRAY_ICON 2050 +#define WM_TRAYICON (WM_USER + 1) + +#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 +#define FRAME_UPDATE_TIMER 2101 +#define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 + +namespace i2p +{ +namespace win32 +{ + static DWORD GracefulShutdownEndtime = 0; + + static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) + { + HMENU hPopup = CreatePopupMenu(); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); + if(!i2p::context.AcceptsTunnels()) + InsertMenu (hPopup, -1, + i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, + ID_ACCEPT_TRANSIT, "Accept &transit"); + else + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload configs"); + if (!i2p::util::DaemonWin32::Instance ().isGraceful) + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); + else + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); + SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); + SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); + + POINT p; + if (!curpos) + { + GetCursorPos (&p); + curpos = &p; + } + + WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); + SendMessage (hWnd, WM_COMMAND, cmd, 0); + + DestroyMenu(hPopup); + } + + static void AddTrayIcon (HWND hWnd) + { + NOTIFYICONDATA nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; + nid.uCallbackMessage = WM_TRAYICON; + nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); + strcpy (nid.szTip, "i2pd"); + strcpy (nid.szInfo, "i2pd is starting"); + Shell_NotifyIcon(NIM_ADD, &nid ); + } + + static void RemoveTrayIcon (HWND hWnd) + { + NOTIFYICONDATA nid; + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + Shell_NotifyIcon (NIM_DELETE, &nid); + } + + static void ShowUptime (std::stringstream& s, int seconds) + { + int num; + + if ((num = seconds / 86400) > 0) { + s << num << " days, "; + seconds -= num * 86400; + } + if ((num = seconds / 3600) > 0) { + s << num << " hours, "; + seconds -= num * 3600; + } + if ((num = seconds / 60) > 0) { + s << num << " min, "; + seconds -= num * 60; + } + s << seconds << " seconds\n"; + } + + template static void ShowTransfered (std::stringstream& s, size transfer) + { + auto bytes = transfer & 0x03ff; + transfer >>= 10; + auto kbytes = transfer & 0x03ff; + transfer >>= 10; + auto mbytes = transfer & 0x03ff; + transfer >>= 10; + auto gbytes = transfer & 0x03ff; + + if (gbytes) + s << gbytes << " GB, "; + if (mbytes) + s << mbytes << " MB, "; + if (kbytes) + s << kbytes << " KB, "; + s << bytes << " Bytes\n"; + } + + static void PrintMainWindowText (std::stringstream& s) + { + s << "\n"; + s << "Status: "; + switch (i2p::context.GetStatus()) + { + case eRouterStatusOK: s << "OK"; break; + case eRouterStatusTesting: s << "Testing"; break; + case eRouterStatusFirewalled: s << "Firewalled"; break; + case eRouterStatusError: + { + switch (i2p::context.GetError()) + { + case eRouterErrorClockSkew: s << "Clock skew"; break; + default: s << "Error"; + } + break; + } + default: s << "Unknown"; + } + s << "; "; + s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; + s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); + if (GracefulShutdownEndtime != 0) + { + DWORD GracefulTimeLeft = (GracefulShutdownEndtime - GetTickCount()) / 1000; + s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); + } + else + s << "\n"; + s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; + s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; + s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); + s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); + s << "\n"; + s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; + s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; + s << "Tunnels: "; + s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; + s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; + s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; + s << "\n"; + } + + static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static UINT s_uTaskbarRestart; + + switch (uMsg) + { + case WM_CREATE: + { + s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + AddTrayIcon (hWnd); + break; + } + case WM_CLOSE: + { + RemoveTrayIcon (hWnd); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); + PostQuitMessage (0); + break; + } + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_ABOUT: + { + std::stringstream text; + text << "Version: " << I2PD_VERSION << " " << CODENAME; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_EXIT: + { + PostMessage (hWnd, WM_CLOSE, 0, 0); + return 0; + } + case ID_ACCEPT_TRANSIT: + { + i2p::context.SetAcceptsTunnels (true); + std::stringstream text; + text << "I2Pd now accept transit tunnels"; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_DECLINE_TRANSIT: + { + i2p::context.SetAcceptsTunnels (false); + std::stringstream text; + text << "I2Pd now decline new transit tunnels"; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (false); + SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes + SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second + GracefulShutdownEndtime = GetTickCount() + 10*60*1000; + i2p::util::DaemonWin32::Instance ().isGraceful = true; + return 0; + } + case ID_STOP_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (true); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); + GracefulShutdownEndtime = 0; + i2p::util::DaemonWin32::Instance ().isGraceful = false; + return 0; + } + case ID_RELOAD: + { + i2p::client::context.ReloadConfig(); + std::stringstream text; + text << "I2Pd reloading configs..."; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_CONSOLE: + { + char buf[30]; + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); + ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); + return 0; + } + case ID_APP: + { + ShowWindow(hWnd, SW_SHOW); + SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); + return 0; + } + } + break; + } + case WM_SYSCOMMAND: + { + switch (wParam) + { + case SC_MINIMIZE: + { + ShowWindow(hWnd, SW_HIDE); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + return 0; + } + case SC_CLOSE: + { + std::string close; i2p::config::GetOption("close", close); + if (0 == close.compare("ask")) + switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" + " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", + "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) + { + case IDYES: close = "minimize"; break; + case IDNO: close = "exit"; break; + default: return 0; + } + if (0 == close.compare("minimize")) + { + ShowWindow(hWnd, SW_HIDE); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + return 0; + } + if (0 != close.compare("exit")) + { + ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); + return 0; + } + } + } + } + case WM_TRAYICON: + { + switch (lParam) + { + case WM_LBUTTONUP: + case WM_RBUTTONUP: + { + SetForegroundWindow (hWnd); + ShowPopupMenu(hWnd, NULL, -1); + PostMessage (hWnd, WM_APP + 1, 0, 0); + break; + } + } + break; + } + case WM_TIMER: + { + switch(wParam) + { + case IDT_GRACEFUL_SHUTDOWN_TIMER: + { + GracefulShutdownEndtime = 0; + PostMessage (hWnd, WM_CLOSE, 0, 0); // exit + return 0; + } + case FRAME_UPDATE_TIMER: + { + InvalidateRect(hWnd, NULL, TRUE); + return 0; + } + case IDT_GRACEFUL_TUNNELCHECK_TIMER: + { + if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) + PostMessage (hWnd, WM_CLOSE, 0, 0); + else + SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); + return 0; + } + } + break; + } + case WM_PAINT: + { + HDC hDC; + PAINTSTRUCT ps; + RECT rp; + HFONT hFont; + std::stringstream s; PrintMainWindowText (s); + hDC = BeginPaint (hWnd, &ps); + GetClientRect(hWnd, &rp); + SetTextColor(hDC, 0x00D43B69); + hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); + SelectObject(hDC,hFont); + DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); + DeleteObject(hFont); + EndPaint(hWnd, &ps); + break; + } + default: + { + if (uMsg == s_uTaskbarRestart) + AddTrayIcon (hWnd); + break; + } + } + return DefWindowProc( hWnd, uMsg, wParam, lParam); + } + + bool StartWin32App () + { + if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) + { + MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); + return false; + } + // register main window + auto hInst = GetModuleHandle(NULL); + WNDCLASSEX wclx; + memset (&wclx, 0, sizeof(wclx)); + wclx.cbSize = sizeof(wclx); + wclx.style = 0; + wclx.lpfnWndProc = WndProc; + //wclx.cbClsExtra = 0; + //wclx.cbWndExtra = 0; + wclx.hInstance = hInst; + wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); + wclx.hCursor = LoadCursor (NULL, IDC_ARROW); + //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wclx.lpszMenuName = NULL; + wclx.lpszClassName = I2PD_WIN32_CLASSNAME; + RegisterClassEx (&wclx); + // create new window + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) + { + MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); + return false; + } + return true; + } + + int RunWin32App () + { + MSG msg; + while (GetMessage (&msg, NULL, 0, 0 )) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + return msg.wParam; + } + + void StopWin32App () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); + UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); + } + + bool GracefulShutdown () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); + return hWnd; + } + + bool StopGracefulShutdown () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); + return hWnd; + } + +} +} diff --git a/Win32/Win32App.h b/Win32/Win32App.h index 614de738..b230eb06 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -1,27 +1,17 @@ -/* -* 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 WIN32APP_H__ -#define WIN32APP_H__ - -#define I2PD_WIN32_CLASSNAME "i2pd main window" - -namespace i2p -{ -namespace win32 -{ - extern DWORD g_GracefulShutdownEndtime; - - bool StartWin32App (bool isWinService); - void StopWin32App (); - int RunWin32App (); - bool GracefulShutdown (); - bool StopGracefulShutdown (); -} -} -#endif // WIN32APP_H__ +#ifndef WIN32APP_H__ +#define WIN32APP_H__ + +#define I2PD_WIN32_CLASSNAME "i2pd main window" + +namespace i2p +{ +namespace win32 +{ + bool StartWin32App (); + void StopWin32App (); + int RunWin32App (); + bool GracefulShutdown (); + bool StopGracefulShutdown (); +} +} +#endif // WIN32APP_H__ diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp deleted file mode 100644 index 4ef768c8..00000000 --- a/Win32/Win32NetState.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* -* 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 -*/ - -#if WINVER != 0x0501 // supported since Vista -#include "Win32NetState.h" -#include -#include "Log.h" - -IUnknown *pUnknown = nullptr; -INetworkListManager *pNetworkListManager = nullptr; -IConnectionPointContainer *pCPContainer = nullptr; -IConnectionPoint *pConnectPoint = nullptr; -CNetworkListManagerEvent *pNetEvent = nullptr; -DWORD Cookie = 0; - -void SubscribeToEvents() -{ - LogPrint(eLogInfo, "NetState: Trying to subscribe to NetworkListManagerEvents"); - CoInitialize(NULL); - - HRESULT Result = CoCreateInstance(CLSID_NetworkListManager, NULL, CLSCTX_ALL, IID_IUnknown, (void **)&pUnknown); - if (SUCCEEDED(Result)) - { - Result = pUnknown->QueryInterface(IID_INetworkListManager, (void **)&pNetworkListManager); - 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"); - } - - Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPContainer); - if (SUCCEEDED(Result)) - { - Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); - if(SUCCEEDED(Result)) - { - pNetEvent = new CNetworkListManagerEvent; - Result = pConnectPoint->Advise((IUnknown *)pNetEvent, &Cookie); - if (SUCCEEDED(Result)) - LogPrint(eLogInfo, "NetState: Successfully subscribed to NetworkListManagerEvent messages"); - else - LogPrint(eLogError, "NetState: Unable to subscribe to NetworkListManagerEvent messages"); - } else - LogPrint(eLogError, "NetState: Unable to find interface connection point"); - } else - LogPrint(eLogError, "NetState: Unable to query NetworkListManager interface"); - } else - LogPrint(eLogError, "NetState: Unable to query global interface"); - } else - LogPrint(eLogError, "NetState: Unable to create INetworkListManager interface"); -} - -void UnSubscribeFromEvents() -{ - LogPrint(eLogInfo, "NetState: Unsubscribing from NetworkListManagerEvents"); - try - { - if (pConnectPoint) { - pConnectPoint->Unadvise(Cookie); - pConnectPoint->Release(); - } - - if (pNetEvent) - { - pNetEvent->Release(); - } - - if (pCPContainer) - { - pCPContainer->Release(); - } - - if (pNetworkListManager) - { - pNetworkListManager->Release(); - } - - if (pUnknown) - { - pUnknown->Release(); - } - - CoUninitialize(); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "NetState: Received exception: ", ex.what ()); - } -} - -#endif // WINVER diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h deleted file mode 100644 index c1f47a24..00000000 --- a/Win32/Win32NetState.h +++ /dev/null @@ -1,92 +0,0 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef WIN_32_NETSTATE_H__ -#define WIN_32_NETSTATE_H__ - -#if WINVER != 0x0501 // supported since Vista -#include -#include -#include "Log.h" -#include "Transports.h" - -class CNetworkListManagerEvent final : public INetworkListManagerEvents -{ -public: - CNetworkListManagerEvent() : m_ref(1) { } - ~CNetworkListManagerEvent() { } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) - { - if (IsEqualIID(riid, IID_IUnknown)) { - *ppvObject = (IUnknown *)this; - } else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) { - *ppvObject = (INetworkListManagerEvents *)this; - } else { - return E_NOINTERFACE; - } - AddRef(); - return S_OK; - } - - ULONG STDMETHODCALLTYPE AddRef() - { - return (ULONG)InterlockedIncrement(&m_ref); - } - - ULONG STDMETHODCALLTYPE Release() - { - LONG Result = InterlockedDecrement(&m_ref); - if (Result == 0) - delete this; - return (ULONG)Result; - } - - virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) - { - if (newConnectivity == NLM_CONNECTIVITY_DISCONNECTED) { - i2p::transport::transports.SetOnline (false); - LogPrint(eLogInfo, "NetState: disconnected from network"); - } - - if (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV4_INTERNET) != 0) { - i2p::transport::transports.SetOnline (true); - LogPrint(eLogInfo, "NetState: connected to internet with IPv4 capability"); - } - - if (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV6_INTERNET) != 0) { - i2p::transport::transports.SetOnline (true); - LogPrint(eLogInfo, "NetState: connected to internet with IPv6 capability"); - } - - if ( - (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV4_INTERNET) == 0) && - (((int)newConnectivity & (int)NLM_CONNECTIVITY_IPV6_INTERNET) == 0) - ) { - i2p::transport::transports.SetOnline (false); - LogPrint(eLogInfo, "NetState: connected without internet access"); - } - - return S_OK; - } - -private: - - LONG m_ref; -}; - -void SubscribeToEvents(); -void UnSubscribeFromEvents(); - -#else // WINVER == 0x0501 - -void SubscribeToEvents() { } -void UnSubscribeFromEvents() { } - -#endif // WINVER -#endif diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 057a1edd..717bc887 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -1,13 +1,10 @@ -/* -* 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 -*/ +#ifdef _WIN32 +#define _CRT_SECURE_NO_WARNINGS // to use freopen +#endif #include "Win32Service.h" #include +#include #include #include "Daemon.h" @@ -21,7 +18,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,20 +116,24 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Start error: ", dwError); + LogPrint(eLogError, "Win32Service Start", 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); } } void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv) { - LogPrint(eLogInfo, "Win32Service: in OnStart (", EVENTLOG_INFORMATION_TYPE, ")"); + LogPrint(eLogInfo, "Win32Service in OnStart", EVENTLOG_INFORMATION_TYPE); Daemon.start(); + //i2p::util::config::OptionParser(dwArgc, pszArgv); + //i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); + //i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"), + // i2p::util::config::GetArg("-port", 17070)); _worker = new std::thread(std::bind(&I2PService::WorkerThread, this)); } @@ -157,12 +158,12 @@ void I2PService::Stop() } catch (DWORD dwError) { - LogPrint(eLogInfo, "Win32Service: Stop error: ", dwError); + LogPrint(eLogInfo, "Win32Service Stop", dwError); SetServiceStatus(dwOriginalState); } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to stop.", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } @@ -170,7 +171,7 @@ void I2PService::Stop() void I2PService::OnStop() { // Log a service stop message to the Application log. - LogPrint(eLogInfo, "Win32Service: in OnStop (", EVENTLOG_INFORMATION_TYPE, ")"); + LogPrint(eLogInfo, "Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE); Daemon.stop(); m_fStopping = TRUE; if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) @@ -191,12 +192,12 @@ void I2PService::Pause() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Pause error: ", dwError); + LogPrint(eLogError, "Win32Service Pause", 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 +216,12 @@ void I2PService::Continue() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Continue error: ", dwError); + LogPrint(eLogError, "Win32Service Continue", 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 +239,11 @@ void I2PService::Shutdown() } catch (DWORD dwError) { - LogPrint(eLogCritical, "Win32Service: Shutdown error: ", dwError); + LogPrint(eLogError, "Win32Service Shutdown", dwError); } catch (...) { - LogPrint(eLogCritical, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE); } } @@ -281,3 +282,125 @@ void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService) schService = NULL; } } + +void InstallService(PCSTR pszServiceName, PCSTR pszDisplayName, DWORD dwStartType, PCSTR pszDependencies, PCSTR pszAccount, PCSTR pszPassword) +{ + printf("Try to install Win32Service (%s).\n", pszServiceName); + + char szPath[MAX_PATH]; + SC_HANDLE schSCManager = NULL; + SC_HANDLE schService = NULL; + + if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0) + { + printf("GetModuleFileName failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + char SvcOpt[] = " --daemon"; + strncat(szPath, SvcOpt, strlen(SvcOpt)); + + // Open the local default service control manager database + schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); + if (schSCManager == NULL) + { + printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + + // Install the service into SCM by calling CreateService + schService = CreateService( + schSCManager, // SCManager database + pszServiceName, // Name of service + pszDisplayName, // Name to display + SERVICE_QUERY_STATUS, // Desired access + SERVICE_WIN32_OWN_PROCESS, // Service type + dwStartType, // Service start type + SERVICE_ERROR_NORMAL, // Error control type + szPath, // Service's binary + NULL, // No load ordering group + NULL, // No tag identifier + pszDependencies, // Dependencies + pszAccount, // Service running account + pszPassword // Password of the account + ); + + if (schService == NULL) + { + printf("CreateService failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + + printf("Win32Service is installed as %s.\n", pszServiceName); + + // Centralized cleanup for all allocated resources. + FreeHandles(schSCManager, schService); +} + +void UninstallService(PCSTR pszServiceName) +{ + printf("Try to uninstall Win32Service (%s).\n", pszServiceName); + + SC_HANDLE schSCManager = NULL; + SC_HANDLE schService = NULL; + SERVICE_STATUS ssSvcStatus = {}; + + // Open the local default service control manager database + schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (schSCManager == NULL) + { + printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + + // Open the service with delete, stop, and query status permissions + schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); + if (schService == NULL) + { + printf("OpenService failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + + // Try to stop the service + if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus)) + { + printf("Stopping %s.\n", pszServiceName); + Sleep(1000); + + while (QueryServiceStatus(schService, &ssSvcStatus)) + { + if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING) + { + printf("."); + Sleep(1000); + } + else break; + } + + if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED) + { + printf("\n%s is stopped.\n", pszServiceName); + } + else + { + printf("\n%s failed to stop.\n", pszServiceName); + } + } + + // Now remove the service by calling DeleteService. + if (!DeleteService(schService)) + { + printf("DeleteService failed w/err 0x%08lx\n", GetLastError()); + FreeHandles(schSCManager, schService); + return; + } + + printf("%s is removed.\n", pszServiceName); + + // Centralized cleanup for all allocated resources. + FreeHandles(schSCManager, schService); +} diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h index 5cedbed6..2830d237 100644 --- a/Win32/Win32Service.h +++ b/Win32/Win32Service.h @@ -1,63 +1,84 @@ -/* -* 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 WIN_32_SERVICE_H__ #define WIN_32_SERVICE_H__ #include #include -#define SERVICE_NAME "i2pdService" +#ifdef _WIN32 +// Internal name of the service +#define SERVICE_NAME "i2pdService" + +// Displayed name of the service +#define SERVICE_DISPLAY_NAME "i2pd router service" + +// Service start options. +#define SERVICE_START_TYPE SERVICE_DEMAND_START + +// List of service dependencies - "dep1\0dep2\0\0" +#define SERVICE_DEPENDENCIES "" + +// The name of the account under which the service should run +#define SERVICE_ACCOUNT "NT AUTHORITY\\LocalService" + +// The password to the service account name +#define SERVICE_PASSWORD NULL +#endif class I2PService { - public: +public: - I2PService(PSTR pszServiceName, - BOOL fCanStop = TRUE, - BOOL fCanShutdown = TRUE, - BOOL fCanPauseContinue = FALSE); + I2PService(PSTR pszServiceName, + BOOL fCanStop = TRUE, + BOOL fCanShutdown = TRUE, + BOOL fCanPauseContinue = FALSE); - virtual ~I2PService(void); + virtual ~I2PService(void); - static BOOL isService(); - static BOOL Run(I2PService &service); - void Stop(); + static BOOL isService(); + static BOOL Run(I2PService &service); + void Stop(); - protected: +protected: - virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); - virtual void OnStop(); - virtual void OnPause(); - virtual void OnContinue(); - virtual void OnShutdown(); - void SetServiceStatus(DWORD dwCurrentState, - DWORD dwWin32ExitCode = NO_ERROR, - DWORD dwWaitHint = 0); + virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); + virtual void OnStop(); + virtual void OnPause(); + virtual void OnContinue(); + virtual void OnShutdown(); + void SetServiceStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode = NO_ERROR, + DWORD dwWaitHint = 0); - private: +private: - static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); - static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); - void WorkerThread(); - void Start(DWORD dwArgc, PSTR *pszArgv); - void Pause(); - void Continue(); - void Shutdown(); - static I2PService* s_service; - PSTR m_name; - SERVICE_STATUS m_status; - SERVICE_STATUS_HANDLE m_statusHandle; + static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); + static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); + void WorkerThread(); + void Start(DWORD dwArgc, PSTR *pszArgv); + void Pause(); + void Continue(); + void Shutdown(); + static I2PService* s_service; + PSTR m_name; + SERVICE_STATUS m_status; + SERVICE_STATUS_HANDLE m_statusHandle; - BOOL m_fStopping; - HANDLE m_hStoppedEvent; + BOOL m_fStopping; + HANDLE m_hStoppedEvent; - std::thread* _worker; + std::thread* _worker; }; -#endif // WIN_32_SERVICE_H__ +void InstallService( + PCSTR pszServiceName, + PCSTR pszDisplayName, + DWORD dwStartType, + PCSTR pszDependencies, + PCSTR pszAccount, + PCSTR pszPassword + ); + +void UninstallService(PCSTR pszServiceName); + +#endif // WIN_32_SERVICE_H__ \ No newline at end of file diff --git a/build/win_installer.iss b/Win32/installer.iss similarity index 66% rename from build/win_installer.iss rename to Win32/installer.iss index a4b67ad2..b215b91c 100644 --- a/build/win_installer.iss +++ b/Win32/installer.iss @@ -1,50 +1,38 @@ #define I2Pd_AppName "i2pd" +#define I2Pd_ver "2.25.0" #define I2Pd_Publisher "PurpleI2P" [Setup] AppName={#I2Pd_AppName} -AppVersion={#I2Pd_TextVer} +AppVersion={#I2Pd_ver} AppPublisher={#I2Pd_Publisher} - DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. -OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_TextVer} - -LicenseFile=..\LICENSE -SetupIconFile=..\Win32\mask.ico - +LicenseFile=../LICENSE +OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} +SetupIconFile=mask.ico InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true - ArchitecturesInstallIn64BitMode=x64 -ExtraDiskSpaceRequired=15 - -AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppVerName={#I2Pd_AppName} -AppCopyright=Copyright (c) 2013-2024, The PurpleI2P Project +ExtraDiskSpaceRequired=15 +AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases -VersionInfoProductVersion={#I2Pd_Ver} -VersionInfoVersion={#I2Pd_Ver} - -CloseApplications=yes - [Files] -Source: ..\i2pd_x32.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64; MinVersion: 6.0 -Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; MinVersion: 6.0 -Source: ..\i2pd_xp.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; OnlyBelowVersion: 6.0 +Source: ..\i2pd_x86.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64 +Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64 Source: ..\README.md; DestDir: {app}; DestName: Readme.txt; Flags: onlyifdoesntexist Source: ..\contrib\i2pd.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\subscriptions.txt; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\tunnels.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\certificates\*; DestDir: {userappdata}\i2pd\certificates; Flags: onlyifdoesntexist recursesubdirs createallsubdirs Source: ..\contrib\tunnels.d\*; DestDir: {userappdata}\i2pd\tunnels.d; Flags: onlyifdoesntexist recursesubdirs createallsubdirs -Source: ..\contrib\webconsole\*; DestDir: {userappdata}\i2pd\webconsole; Flags: onlyifdoesntexist recursesubdirs createallsubdirs [Icons] Name: {group}\I2Pd; Filename: {app}\i2pd.exe 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/Win32/resource.h b/Win32/resource.h index daa1ddcb..3b188481 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -1,11 +1,11 @@ -//{{NO_DEPENDENCIES}} -#define MAINICON 101 - -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +#define MAINICON 101 + +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..297d122f --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,16 @@ +gen +tests +bin +libs +log* +obj +.gradle +.idea +.externalNativeBuild +ant.properties +local.properties +build.sh +android.iml +build + + diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100755 index 00000000..2ae14711 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/assets/addressbook/addresses.csv b/android/assets/addressbook/addresses.csv new file mode 100644 index 00000000..97a43dfc --- /dev/null +++ b/android/assets/addressbook/addresses.csv @@ -0,0 +1,693 @@ +00.i2p,zmzpltxslembpaupg3srh4bbhv5txgh5jmms6sfj4hzsvlv3xugq +0ipfs.i2p,cdii3ou5mve5sfxyirs6kogt4tbvivk2d6o25awbcbazjrlhjeza +0xcc.i2p,gawouxh2sg32cluwlqsnpy3dwedvoqtfroi4evvdvm2pfv7tdadq +1.fcp.freenet.i2p,cuxbeputgxn75ak4nr7ltp7fjktnzl5sul3wstwnsoytbbpb4ixq +102chan.i2p,xxu3lso4h2rh6wmrxiou3ax7r7la7x6dhoepnku3jvrlwp35pefq +1st.i2p,rduua7bhest6rwsmmyttzssfdw3p4eu6bgl3mb4hin32qo3x5zfq +2.fcp.freenet.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta +333.i2p,ctvfe2fimcsdfxmzmd42brnbf7ceenwrbroyjx3wzah5eudjyyza +55cancri.i2p,b4iqenefh2fr4xtuq6civfc6nhnia6e2yo36pf7vcgdvrwmh7xua +adab.i2p,pxjr6f2cig6v7v7ekam3smdnkqgmgseyy5cdwrozdyejm7jknkha +alice.i2p,iq26r2ls2qlkhbn62cvgb6a4iib7m5lkoulohdua5z6uvzlovjtq +always.i2p,wp43sdtuxum6gxbjvyeor35r5yvgtkp3dcu7dv47lx22zeb3relq +amazone.i2p,e6kq73lsxaeyiwpmykdbdo3uy4ppj64bl7y3viegp6mqrilqybqa +amiga.i2p,edy2xappzjjh7bxqounevji4wd2binqkv7gft4usrkan45xhbk5q +amobius.i2p,rj6432agdprun5baai2hj62xfhb4l75uvzl55dhj6z5zzoxv3htq +anarchistfaq.i2p,xosberjz2geveh5dcstztq5kwew6xx2brrqaorkjf2323bjzcd3q +animal.i2p,5iedafy32swqq4t2wcmjb4fvg3onscng7ct7wb237jkvrclaftla +anodex.i2p,25cb5kixhxm6i6c6wequrhi65mez4duc4l5qk6ictbik3tnxlu6a +anoncoin.i2p,nmi3loretkk4zbili32t2e5wyznwoxcsgzmd2z4ll3msgndyqpfa +anongw.i2p,owrnciwubb3f3dctvlmnaknb6tjdxtlzvv7klocb45mmhievdjhq +anonsfw.i2p,ir6hzi66izmvqx3usjl6br3nndkpazonlckrzt3gtltqcy5ralyq +anonymnet.i2p,77ouyl2ane7ffgydosd4ye42g67aomtc4jrusmi76lds5qonlffa +anonynanny.i2p,l2lnhq2dynnmf3m46tcbpcmbbn4kifjgt26go6n2hlapy4drhyja +anonyradio.i2p,cbobsax3rhoyjbk7ii2nd2fnl5bxh3x7bbearokyxgvmudn7o5bq +antipiracyagency.i2p,by4kcmklz7xnkai6ndfio47kts3rndm6wwleegtxghllimikdapq +antipiratbyran.i2p,y2qbhrvuciifbszaqqwxd5t75bomp7kzdqx4yxsrkaq542t75k3a +aosp.i2p,ly7raldsh2na2cgw5yvueyvqqjgx3vbqinecjrqdldgya76i2p2q +arc2.i2p,rnmosuwvtftfcrk5sk7zoyhyadh2g4dhe2mif5ml7qjisgkyw2na +archaicbinarybbs.i2p,t7o2tw36cffedgfr6kahewpkrntofnliuapji2e4rucl3os55epa +archiv.tutorials.i2p,lldr2miowq6353fxy44pnxfk37d6yn2f6kaivzecbmvvnnf5exyq +archive.i2p,x54d5st3dl6mwgfxj6raiekqkypo5pdvuex3n62szwju7hgefiyq +archive.syndie.i2p,abbyu5n3mh3nj7pe3b6byldrxswvva5ttxcafsnnseidanurq3kq +ardor-wallet.i2p,tm23k5ny3umhf6vf3kghnnwacli5zywq5wrr3xcqowbcofuyr4gq +ardvark.i2p,jcmw2sol3hruwc6rfinonx4e23pjkukkg7lg7xt7xb2gpiyyraiq +arf.i2p,o46lsq4u7udxg3qqlidrmpj4lb4nr7ldxmbb2x53nftndaeyxqeq +arkan.i2p,7o5y2lyyrjx5tf6l4fyumywui7msjv5azaaheatvw5sqj7mxbuvq +asciiwhite.i2p,itbzny5ktuenhjwjfqx3jravolhlj5wullhhr2m4qr6k2emnm5dq +aspnet.i2p,tsb7zqru57p4q2a7cto2lko4w5cg4lieglwm6t27c44fkphqmf2a +asylum.i2p,p45ejjw4p2q6nq3mzi6cm6ep35grtzshboidj2lojmrmic22noha +auchan.i2p,6vxz4yp3vhjwbkmxajj7wiikxafwujig63gkhjknbq6xh4rqpm5a +aum.i2p,ohdfneqxapfd3fwfbum4tut7z6k3rnr7rrguoxdrrfe2tln2kpbq +awup.i2p,v6g32duzrkacnrezfbll3pza5u37h7lnukr2wbsk6rqen6prhbga +b.i2p,272kt3gcx6wjurunzaiiwld7s5p4mpjewfubzmlcvw2vie62ckpq +bacardi.i2p,hivhnx2v47vh234c7coi2urj5cyvbl4bu3ypjr7snklortyqeljq +backup.i2p,kepphem42whle3rkfv26wcksmnegdbg6rdp6t3oobdkc2fmzrdkq +badfish.i2p,f6v26gyr4eipy3a7pi2voulw5qvob6dg7zij6xpo2ywbi5tvbu6a +badtoyz.i2p,3qz6ubtwlt2c4iasofjirkckq43u5fgkzyg7mlutcsym5gzhijna +barry.i2p,4kyahq53ol52n23l44tefgeaxqpp3cbb632t5k3umdvqcooevdzq +bash.i2p,s3wouoilbl3mrefxjhp4qoyujgok34e7y6vmpbu6hx4342ivqo4q +bdl.i2p,kp6fnuulenbjm7r26pfbmjcq3u7c7kvxeajodvgr5flcnskdgi5a +bdsm.i2p,pa7fxql5jljegg7j5tglhnnaod2sptq3gxvdn3ji6muqyhgn3poq +betaguru.i2p,d7cduwwhrcc2voameqfkvd66u3advu4jw2p6pysgax35vq6ovriq +beyond.i2p,uaicfqlrpjtitqbqkpfujanj5dollzfzee5glsuls67ekw6hlpoa +bible.i2p,pypz7ca24n3lyp4tm3kvncg3ltp3gd5pgnacc6zltoeffiyyegda +bible4u.i2p,xs6lr2g5jiaajtb3nkno2zmy34eipitrggooxb7wtey7uko7bqmq +bigbrother.i2p,tnxiifs6uticzyg6ac4lhv2l5luwi6xra7yngocro56ive5e4jsq +bitlox.i2p,lqw5khxcdntlv3u4vhn53upcqirplvnc4etjlmoytrzs66ytettq +bittorrent.i2p,pgax2vz572i4zsp6u6paox5xubmjrkqohq6g4hvlp6ruzzy56l5q +bk1k.i2p,nlyegmtyfffo5jfgg5h4dxxnlmqko2g36gpaye5a7vd3is35xxfq +bl.i2p,e73d6uhnfbylza6wqkhxejmqeyfb7thkzw35gn5ojmna64jzyk2a +black.i2p,sjwueu62qpe6dtv5b322k3f23fl4uz3w6qe6wcrwauiwpnymypfq +blackbox.i2p,7josyf7zjieoib3ovmr5a4dh5w64kmfh45lv5h436eljtgfegtqa +blackexchange.i2p,ztgr5kghkyn43fhhkuycroxgfti6cojo3vg4wdd3usqonyvrla5q +blog.curiosity.i2p,yiz6jec5k7ccxdgnh7msqa4ze52bqqmf6rpq6bqdyojra2erd4ta +blog.polecat.i2p,orlccceubewvxo3fbdyydq6e4uuidbs4xd5u2gyqbculnowo3ehq +blog.tinlans.i2p,ylkch2nkrwehakx4z6wiyjbeqwlgasknukdkex6r6yq4xusrjnda +bluebeam.i2p,lvxp3cbcfwtol57d5pmrsck32t7ndutlxubjb4smaf32bynhlk6a +blueheron.i2p,anfb5jrhixjmvkyxctqwkezqer7dbob22wge2bh6wsewbhgnftfa +bnc.i2p,fr4zbcygmx2vdct6nrabakfys4b4derm6jqu2ovppkgqillvlqxa +bob.i2p,i76m7dwm5hnapljendbie6fc5y3mjlkdlduo3tvbwiwmvhxbpyaa +bobcat.i2p,ftuukjtcquuvppt726w37boit7gp5hf2yxwfop35prx3grzzzxlq +bobthebuilder.i2p,qlahgthqhr4uojkkwahnper2cl3ro5f5gtzy5t4lzapbzo4osy6q +boerse.i2p,7633w56hd53sesr6b532r5qlbdnvyl5bnvama6ign6xryaxol4rq +bofh.i2p,auvuinzogu6gc4pwsgbjijuszxgcjygciu2wy53pfz7mo5nfpc5a +boing.i2p,bgsq33bh74j66hn4oh7oovlvuhhdyw22lq2qi2fnv3jyh2ryap3a +books.manveru.i2p,eb2tisc2vr5jvjqrixrozcujiucwxg4m722stxwho5666ipl67zq +bote.i2p,bhjhc3lsdqzoyhxwzyrd63kvyg4br6n2337d74blyintae66mr2a +bozo.i2p,7a2d23h6htprhzrol36vgwgklsbqrnuya4tbaaaspmaeaodt57iq +brittanyworld.i2p,e76umhhic3474sdxiuax25ixyfg7y3z7oojj4fmxvhgv3ruet6aa +bt.i2p,uhkuu54pg47zey76h45tnvsdtpkf5bthbtrjgnaloi5m54h4hlaq +bugfuzz.i2p,ubszn4gsf22vga67rvzzlg4qj2bfcq6o52fmxz46xruawqm6z7rq +burntout.i2p,lkep3fd7tjvxrs25crr2c3jy7xm4s7bqiua5r327zgpw37sgyerq +bytepay.i2p,7amc4ztwkzu3cgsaaaw3223ohuihn5hlsqc6gpf2rxdyptdkyugq +ca.i2pd.i2p,u5safmawcxj5vlrdtqrsqbsndkr5cfenpicgg5euu4xqm73yicba +cases.i2p,kmpmk2fmineaiwublteqlifg4fkmewnhmxqlcgg7qwecz6daj43a +cathugger.i2p,vq43xjjcnejqpzfprws5qzrea2siieshu4tglpdepql2w3w3bpba +cbs.i2p,u3lp7wazvq6opodzwjg5sc5w5kwxehmxd4wcdpt4s4j2k4dx4apq +cerapadus.i2p,zroed2cxga5zeuu6rcvmp2yfi77nzduw7yhdplbeuqkuyxwbrzaq +cerebrum.i2p,u5gtsfn267udwfh2uq35jiabkufifvcbgv456zz34cydutsiw2eq +cgan.i2p,43z65gdr52xe3fxmkumwp3dzhedu4tu4rdtzr24hz5b4awcpfbqa +chat.i2p,ollpwnp6yidc3obbb3famgt6rw5jg5w3k3a6z7hhaegj6gcohiuq +chess.fillament.i2p,tv6wbanei647yf5bie4dhg2wmybkjurezlpdfwftc5ajqlfswwya +chess.i2p,sbnoqznp5yzxals3vs6nzyqaj2fetvonys4e3b3x4ktmfeus54sa +china.i2p,wit6f2zx6dtuqqze6nhbykrds3idppfirxvhf2f7ydqoqf4xdzeq +chitanka.i2p,u4s3jneepk3akoez46kqiwikoezi6zyj2ibjkjyi4uuvsbcojzba +ciaran.i2p,2r3645eete6xwbfu62ogonudcrcgqq25sbnij5v4geru74yrscna +ciphercraft.i2p,7s5pkqbpbfdkxtwuu2e2iwstbikyewvvscy76lij4x5pfbygbjca +closedshop.i2p,6fg67mbw2okopzyonsck4bsy3cy7l2fame56uiysr2cezhjhzdbq +cneal.i2p,g4za73ffigv3ht4jnhzy4dae52djjq7lqcguqsfg3w5cxzqm7nba +co.i2p,3mvo5eifcwplcsoubtvqkzdahwo2sdhfygfdde7lj2glybk4q22q +codevoid.i2p,2mukrqwtinsw27uoejtrz74zxtilyhnnfdyso7j3yo6vaa6nzlaa +colombo-bt.i2p,cyr75zgiu2uuzap5zeosforbgvpfbqos2g6spe4qfulvzpyhnzxa +complication.i2p,x2av6rwj5e5tp64yhdmifdyleo4wblw4ncrrcrabxwscuevpdv7a +comwiz.i2p,6p7zqfotzbd66etl5xqy3p6xvr5ijucru3am2xqa7wmnj6vf3djq +confessions.i2p,lh5vitshufxpmyr44zgyymebo5elc42eda7pxvn5lmtes47c7rxa +connelly.i2p,5yrris3nigb3fapvzrlrcaew6cdmzdknzvgrc7y2jpn3ntqurweq +costeira.i2p,abhty5xlmnyab2kqdxcd56352kcescxoux3p6dbqdrghggyygnxa +cowsay.i2p,q4ghzfpah4ffvm3bhc6fdkrznk5f6jxfjm2daytlparznai5d54q +crstrack.i2p,mm3zx3besctrx6peq5wzzueil237jdgscuvn5ugwilxrwzyuajja +crypthost.i2p,zywhrxtnkjc3rxxvxbocom7ml4hnutomgtuvqrwyf3rhuupnq5ca +crypto.i2p,vffax5jzewwv6pfim55hvhqyynafkygdalvzoqd74lkib3hla3ta +cryptostorm.i2p,mlu7mswyirjf53usqq7gyamvqc6rqihezgdbevov3dkxmkfo57aq +curiosity.i2p,eomeif4xrykxlzhawc3icdilje5iammijos6tyizwhrfh3j7qdvq +cvs.i2p,yd6k7dzpsa2tnlzx4q7xqkmd4qsjk5xk5hbiqpiarwbeyvxaxgba +danwin1210.i2p,eoqdf4no5dxn4tw5n256kkd4lzz3uk4p47np4mepsykpsdzrnvba +darknetnow.i2p,gkx3o5fy7mv7l4psqqnhp35d5iun7rt3soci6ylf3rgb7a5a655q +darknut.i2p,2mk37gtvpk2i63o6vl7vna4dr46rqexxetupgn5efuuins7x3qya +darkrealm.i2p,gbh4eerxdsph7etxsxznfhvmuiz54trlkenakqep343u4xcoekzq +darrob.i2p,hz2xhtpeo6btgiwi6od4qj2575ml5o2246rd5orarruyjhd63zja +dashninja.i2p,dzjzoefy7fx57h5xkdknikvfv3ckbxu2bx5wryn6taud343g2jma +davidkra.i2p,nq7ca2egm563nir3xegfv52ocgmxstpz56droji4jgnzfoosk45a +dcherukhin.i2p,qa4boq364ndjdgow4kadycr5vvch7hofzblcqangh3nobzvyew7a +de-ebook-archiv.i2p,6mhurvyn6b6j6xa4a3wpuz7ovpsejbuncvyl6rnhepasfgdgmn7q +de-ebooks.i2p,epqdyuuhtydkg5muwwq47n7jvr66pq4jheve7ky5euls6klzwuyq +dead.i2p,7ko27dxvicr2sezvykkrfiktlghx5y5onup3f2bas5ipocy6ibvq +deadgod.i2p,63bveyh7wefb44hlia7wtxxb3jal3r67thd6jekmwrtq4ulaaksa +debian-multimedia.i2p,cylxxz2y35x6cvyrl57wu3brckurtexatyi2i5awz3eeamqwjspq +decadence.i2p,pw5ys7k2grjb5myydpv6ohikm6nna7y6u2dro44i4rucgulu3ikq +deepwebradio.i2p,2nait2gdeozkgf6gyhzjfij6mwldwkxxwcvtxobb4b5q5cvtm5la +def2.i2p,cepsrw27kdegwo7ihzouwvgcvw2obswwjs23ollgj7hk2yrce3da +def3.i2p,xbf3ots2purqun7orn72ypkpjmrzbfrkj3u654zfe77hbrbow6la +def4.i2p,yyzdq4fwwmnlojp23drfpfqujln2vcjozjrfzfeuriuqzdq7g4mq +deploy.i2p,ujzspsqkbz5z272eozsrdv4ukl434h3fuliwrfxxnab74jmd7e6a +det.i2p,y6d4fs3rpqrctuv77ltfajf5m4tl4kzcu7rtwhxgiohylfxxow4q +detonate.i2p,nykapdsjjswdkjov7x3jzslhg4ig3cpkhmshxqzijuhbisx25jja +dev.i2p,cfscxpnm3w3qxnlv3oikewxm4qrot4u6dwp52ec2iuo6m7xb5mna +di.i2p,3irnooyt5spqiem66upksabez4f3yyrvvjwkmwyzlbealg64mgxa +diasporg.i2p,edvccoobtjukjgw2os5eetywanbb2mpag5aknkrpia5qx2koksua +diftracker.i2p,m4mer767ipj7mq6l7gdrmrq37yzvsj3kzezd7n7nsfuctntjseka +dm.i2p,heysbdivyeugdbggpscco5wje3dsvwgcpp5ot4sopooebnmiqvtq +docs.i2p,ato242wckzs4eaawlr5matzxudt6t5enw73e4p6r3wajwkxsm3za +docs.i2p2.i2p,las5l45ulwwf5i72nht6vk33sfkidcpr2okpf5b6mvgbk3a2ujna +downloads.legion.i2p,xpmxdpuuptlekyhs7mmdwkvry7h2jbvpqpzsijqe3a5ctxgodesq +dox.i2p,vk27cjdrtegfdnrjqutebgxkpyrfj42trdfbsupl5zn2kp34wb3a +dropbox.i2p,omax2s5n4mzvymidpuxp2yqknf23asvu54uon6cxl6gdrlblnuiq +duck.i2p,3u2mqm3mvcyc27yliky3xnr4khpgfd4eeadhwwjneaqhj25a65ua +dumpteam.i2p,2fwlpuouwxlk2nj4xklvm43m52tqyhqnu2fcfiuv7clvf3wd5nwa +dust.i2p,u6xgh6zhhhvdvefbqksfljfs3nyjvqcrmyamp5bryz5f4injmniq +dvdr-core.i2p,fg6l2ej6qrk5rkyfzdptxx5xkcm4kvdla4gg2tun7z7fm5cxxw5q +dyad.i2p,7n2ljphvp2dep7imoujvydxp4myuxfld3axwfgcny5xc5x6jj6ka +e-reading.i2p,z54dnry6rxtmzcg7e6y3qtsig5yf5fmehuvakcg5wnuahx3iafuq +easygpg2.i2p,bwxry5alzx5ihgrd3glah4eotddblzhalvpheppnw4zcajzqoora +eboochka.i2p,ou7g64d5in4sugv5fgmmzwnunuw5hloixio7puthmrvrkwrp6egq +ebooks.i2p,bvpy6xf6ivyws6mshhqmdmr36pruh2hvoceznzeag52mpu647nzq +echelon.i2p,afvtspvugtd32rsalxircjglh3fhcjzk7gxrm3gw4s2yrpvzk6wq +echo.baffled.i2p,bfr3lyicr72psxvt2umqfb562rtex66w6q3hi3tktzkoyane2iha +eco.i2p,2dq2o5h6c6a674qaduipp55mid5iktumjbswuwmpsrcqaeowdvwa +eddysblog.i2p,ieac3ub4g5sy3wuhsbqfembnpp7f3a37xgcx537ytzsmgfzexnbq +edge.i2p,aknsl5wmzjmwyc4wxutfdwy2w5vgd3vcx52mqx647hcgvyurmqta +eepdot.i2p,t6edyotbxmxvy56fofdvmragvsj65te2gkhvzv5qnblicutyvgoa +eepshare-project.i2p,sn26kom4qyuzouppv4lwnk6bqabdydcegtrilybviibwiq2s4nfq +eepsites.i2p,isskhl4ak3g7qevrarlmblddgr4ugnn3ckalwpjcvxafk5rjgypq +elf.i2p,duz6ey27ohpcp3llylklzdb63lylolzcixad6bh7rt5tkq42qqpa +elgoog.i2p,z6hrgkg2ajmuzlrddjlffrgctx7x7fkipm6c4hdzmohyn5wkr4ya +ems.i2p,734zw4jsegdf55zl3z6s22tqkbxcghu4qvk6q2wevjfmx7xhbn6q +epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa +es.hiddenanswers.i2p,cw7ge5ey4ekp5iep2kaw6j54boebtqytpcbnvio2bfpccd5ejzfa +eschaton.i2p,xe75f5hzmrq6rkhsef2geslmi2v2yfngdiysmlmxvh7b4pyyjk4q +esuwiki.i2p,cwxuiwcpymb72vm5vluba66ofhugyf5qeevvwo7e2fqrxl243coa +evil.i2p,ljfl7cujtmxfffcydq77pgkqfxhgbikbc6qxjgkvcpn4wzd73a4a +evilchat.i2p,s5b7l3hzs3ea535vqc5qe2ufnutyxzd63ke5hdvnhz24ltp3pjla +evilgit.i2p,mx5vyoqhg77yuhthwznsxrepjsemq4uwitx4lxdzetk36ryl5rla +exch.i2p,vsyjsbbf2pyggtilpqwqnhgcc7mymjxblamarmxe5hmbxaxvcndq +exchange.gostcoin.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva +exchanged.i2p,ylmulgfskl6uiwac4hw4ecwqdzd3oxtwaemzj25zc6k5q4rkexra +exitpoint.i2p,5zmjurq3enudcenegnxu5hqmfmayz4lxvnik6ulch4xssa2ithta +exotrack.i2p,blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva +explorer.gostcoin.i2p,ktoacmumifddtqdw6ewns3szxths2hq2fat2o7xnwq4y3auga3za +fa.i2p,6n6p3aj6xqhevfojj36dixwbl4reopkhymxmatz7ai5sroh75rka +falafel.i2p,djpn5cbcgmpumwcriuzqistbae66txca2j4apjd2xesfgb7r5zmq +false.i2p,77mpz4z6s4eenjexleclqb36uxvqjtztqikjfqa4sovojh6gwwha +false2.i2p,j5i2tfumh3ti5sdtafwzzbpupmlcbg5drysfay2kxbdpsaljrosa +fantasy-worlds.i2p,62a4xcyyhvfrcq2bkckb7ia37fmrssrgx467tlkxp32fjpq577wq +fcp.entropy.i2p,de6h6ti5z3mcbdcwucu45vplikqyoeddsu3rqy7s2zy5i47j3peq +fcp.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta +fedo.i2p,zoamh7e3k2vf2g6pfy46ho4taujk2f4mxqqsv3gbg554fxbvyfqq +feedspace.i2p,kvtnpx4jylgeyojfhix4x462sqn5uork3roml4sfzotkxx62i4wa +ferret.i2p,kkqie5qmja7bkf3iad4zxhrdarwj7kbrx2m3etn5kmba3shgwj4q +fido.r4sas.i2p,i522xmu63hfbaw2k54cthffcoqmeao6urjyq3jg4hddf6wf57p3q +fifi4all.i2p,v2stz6bsot7sbjzix5tky5dm5ej7gidmjnkvzqjju5xvz5sz6fwa +files.hypercubus.i2p,qfglq25jwieszgyt7muz6dambzqsrmjhhszygzzx2ttubc77sffa +files.i2p,w2sy74xe6oqnuz6sfh5fhkzu7boholgzd5f3anhj47srxwpj2vaa +files.nickster.i2p,yil7dp2hg5pbqyovsiwb2ig6zjsq4tize3fnwemmqdrr6j5itdtq +fillament.i2p,udj2kiino4cylstsj4edpz2jsls77e32jvffn2a4knjn4222s2oq +firerabbit.i2p,awqh7n3wskzl3epyvkdwgarmfybsncm7vye6psg4tpkmplh3mj2q +flibs.i2p,ocdm33e3h5tdml3yyholj4objdwsrhlugfqjnqgdkslmgdzb6b3a +flibusta.i2p,zmw2cyw2vj7f6obx3msmdvdepdhnw2ctc4okza2zjxlukkdfckhq +flipkick.i2p,aso5rzc4ym6g2bcbxjy2n573bmbenkjawva2jg7fhyqhwtwgu6lq +flock.i2p,hflpi33ko5bi2655lx6bpzstdnjqgzrz23inovqjx5zpntyzyb3q +floureszination.i2p,vitpvfb25sikuk3crgcvtcdi7hajxnnq2t6weay3no7ulur2wwwq +forum.fr.i2p,onvelkowkbuwrglhw2cnocggvbdudi75sll5mfirde3cbopjqivq +forum.i2p,33pebl3dijgihcdxxuxm27m3m4rgldi5didiqmjqjtg4q6fla6ya +forum.rus.i2p,zd37rfivydhkiyvau27qxwzmerlzbqtthsa5ohtcww62zrygjaga +forums.i2p,tmlxlzag7lmkgwf6g2msygby3qttxvm6ixlfkq6s6cpgwubp33ya +fproxy.i2p,keknios3gm6kh6onez6x2bm2t7stv54oanvltuagphgdfjdw5e2a +fproxy.tino.i2p,fpaituvuvyxp6xdjnv3i27alnj2ifzcvqdweqb6yj5uybotzvyha +fproxy2.i2p,r4lgw4wmza25g7j5fjocjbwzwthfg4ymcbm52ref3hh2hogskcza +fr.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq +freeciv.nightblade.i2p,rluupsgxbvw5t7jno3apyzlrdirjkljft4gdoy4mxxh4fmd4xzta +freedomarchives.i2p,4ck6oliqfjz3sccpya2q4rh5xkj5xdxkqs76ieml37537nfhwd2q +freedomforum.i2p,abzmusjcm3p3llj4z7b5kkkexpsxcnsylikokouk5txfim3evqua +freefallheavens.i2p,giqnkltyugfmsb4ot5ywpvf3ievuswfurk6bjie4hxi2hh2axajq +freenet.eco.i2p,2kf7ovb35ztqkrurkm76y34jfpwi6go25xj7peznnmxrl7aieo7a +freshcoffee.i2p,sscuukigp6alcb3ylhkcugoejjfw5jqgtqbsbafw4hyku42lgc3q +frooze.i2p,m6ofa5dmyse4b4jg7kfmluuuc4pw5jqu6zh4qnboin4vropxepja +frosk.i2p,63naq7zb3hvbcppj2ng7qwf6ztusp4kwpyrzbt4ptafcdbu4pfjq +frostmirror.i2p,ycz3imuz6yte2zhlapmsm3bsvc46senvc2jxzwsbfdct5c72qulq +fs.i2p,ah4r4vzunzfa67atljlbrdgtg3zak5esh7ablpm6xno6fhqij35q +fsoc.i2p,vaqc4jm2trq7lx2kkglve7rkzxhhaptcwwl32uicx4ehf5k3hx6q +galen.i2p,4weo7zkxscxbcouiqx4mlnb35uwl2lromikzk33er3fljktyvi2q +gaming.i2p,rfxberwod6st2zc6gblqswxjl57nucgc3xrbwss43pe3dvqqzj4q +garden.i2p,qkk2dqx6nocycgt3vinsoc76cxkb4jreybcpgz3fcps2dbe4rowq +gaytorrents.i2p,fnggbr2t2aulr6rvlo4aehotx6wecfob7u3k2nxsnvtm4xex424q +general.i2p,5fklrsztdqpl3hkkwwrrw2rdowrq7wwhwb6h7avvk4fhansp4vvq +gernika.i2p,wpzqv3lxpecdsvcaadvbmrhhwlc7kp4n2mijdv2qjw3zr3ye232a +ginnegappen.i2p,kbhfkzx5jeqhfgss4xixnf4cb3jpuo432l3hxc32feelcmnr3yja +git-ssh.crypthost.i2p,llcp7jvz3hgtt3yzkdgjolwobisgvhv4xqa5a4oddejllyozur5a +git.crypthost.i2p,7frihhdcisdcyrzdbax6jzvx5gvtgwsm7m6kcem2tlaw4jtahbqa +git.psi.i2p,em763732l4b7b7zhaolctpt6wewwr7zw3nsxfchr6qmceizzmgpa +git.repo.i2p,vsd2vtgtuua2vwqsal2mpmxm2b2cpn3qzmqjoeumrrw2p4aot7uq +git.volatile.i2p,gwqdodo2stgwgwusekxpkh3hbtph5jjc3kovmov2e2fbfdxg3woq +glog.i2p,ciaqmqmd2wnws3hcpyboqymauyz4dbwmkb3gm2eckklgvdca4rgq +gloinsblog.i2p,zqazjq6ttjtbf2psrtmmjthjeuxaubi742ujrk2eptcsaoam4k7a +go.i2p,ll6q4lsirhwkln4dqxwqkh2xu4mu3jiy546b4uhe4fypyb4vvx2q +gonzo2000.i2p,nogsv7okydhbvrewv6hb4xdojncvhkusnyib4lglluc4uw67a37a +google.i2p,4p3ajq4cotnflmuv7fhef3ptop5qpm3uzzgp5bahxif3nc4w3ffq +gostcoin.i2p,4gzcllfxktrqzv3uys5k4vgkzbth4gqednwhfpt755yivm3davuq +gott.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja +greenflog.i2p,zny5ftmhzxulxzyczmeat53qjnue2xtqv2clisc7dg76lwfceecq +gstbtc.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva +gusion.i2p,4qyfdhizjixe2psu7wcvqufix5wlijocehpb2futurcmlhlktrta +guttersnipe.i2p,kizkhzes2bzp45widihremo6geepfk7dl6juourkvzuvlc6y3spq +hack8.i2p,un63fgjgi3auvi7zscznwqfol7ka4johgthvqf635mg3fefsjgpq +hagen.i2p,e2t6rqd2ysbvs53t5nnaf7drllkgk6kfriq3lfuz6mip6xfg644q +heisenberg.i2p,jz4quyw7zt63tmw65jfp76fblwadjss4iyi4puqdg3dye7oaqlvq +heligoland.i2p,gzrjm62ektpqjfsem3r3kwvg6zpjvvhvpjvwfxkm2ay4zu7sp6oq +hidden.i2p,iqodhhqo473qv5gwhjcs2bsrbhlqtpzgpnuumpastfiyhuwb2kyq +hiddenanswers.i2p,kj2kbzt27naifij4ki6bklsa2qfewxnkzbkgvximr4ecm7y4ojdq +hiddenbooru.i2p,zma5du344hy2ip5xcu6xmt4c7dgibnlv5jm4c2fre5nxv44sln3q +hiddenchan.i2p,6y4tltjdgqwfdcz6tqwc7dxhhuradop2vejatisu64nwjzh5tuwa +hiddengate.i2p,rvblcu54jvkkfffp3fobhunsvpgfc6546crcgzielzwe2s5m5hbq +home.duck.i2p,jsh7yfvm2t5urdcnmfzdy4n6vegqskdtlwem53chgxli4ipfmuma +hopekiller.i2p,kcaelbgsvrkiwpx36b4wxofebrl3njx7rgm5amzfmqwbomt44cxa +hotline.i2p,6cczi27iuxkm3aivazaemzltdqgh42ljzurqp43uclbz2lid2uqq +hq.postman.i2p,27ivgyi2xhbwjyqmnx3ufjvc2slg6mv7767hxct74cfwzksjemaq +http.entropy.i2p,ytu7kz5bdoc26nkpw2hajwt3q7n5rcbg2eokyefhmkxmmslimbdq +human.i2p,nrtcelq3humyfvoxmzmngpka6tmyifweouku5mbi5av4lc43hzaa +i2host.i2p,awdf3nnmxxup5q2i6dobhozgcbir7fxpccejwruqcde2ptld443q +i2jump.i2p,633kqgmwzzu6vhkevwvbf2pfyejt3gkes34i6upa4og57fgdfcxa +i2p-bt.postman.i2p,jeudwnx7mekjcowpqo6xpkwn7263c57y5piurrjrdzinjziu4fla +i2p-epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa +i2p-javadocs.i2p,icgmr6hhjudl4yxhtuq4pxvss2pzypwddzowajgs5rdz6f55novq +i2p-projekt.i2p,udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna +i2pbote.i2p,tjgidoycrw6s3guetge3kvrvynppqjmvqsosmtbmgqasa6vmsf6a +i2pbuggenie.i2p,bioq5jbcnfopqwvk7qssaxcl7avzeta6mu72jmxjeowflpcrhf6q +i2pchan.i2p,tduxyvfs7fzi26znvph3mu2d2ewaess7emomfci22wvownajphuq +i2pd.i2p,4bpcp4fmvyr46vb4kqjvtxlst6puz4r3dld24umooiy5mesxzspa +i2pdocs.str4d.i2p,yfvbtrhjac3jutdsqzugog6mbz3jtyhpwovrt2mqc5mzv534y7cq +i2peek-a-boo.i2p,qgv64klyy4tgk4ranaznet5sjgi7ccsrawtjx3j5tvekvvfl67aa +i2pforum.i2p,tmipbl5d7ctnz3cib4yd2yivlrssrtpmuuzyqdpqkelzmnqllhda +i2pjump.i2p,2mwcgdjvfvd3xwumzqzqntual3l57h3zo7lwdmkjboeraudpkyka +i2plugins.i2p,bb63kmnmbpitsdu45ez54kmogvvljn3yudksurcxiyq7dn5abt7a +i2pmetrics.i2p,v65p4czypwxrn35zlrfkar2w77vr42acd7gbszegsrqq4u7sip5a +i2pnews.i2p,tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq +i2podisy.i2p,3c2jzypzjpxuq2ncr3wn3swn5d4isxlulqgccb6oq5f6zylcrvcq +i2push.i2p,mabdiml4busx53hjh4el5wlyn4go5mgji2dxsfyelagi4v5mzjxq +i2pwiki.i2p,nrbnshsndzb6homcipymkkngngw4s6twediqottzqdfyvrvjw3pq +iamevil.i2p,au7jhslyt4cxkjp365bvqvend3hhykrrhbohtjqlgoqrlijbezja +icu812.i2p,bxgqwfsnr3bgnr6adn62anjcin5nuthqglotb3wn3dgynsfofeva +id3nt.i2p,ufuqdzsxltiz224vq5gnuslt3a3t72dhy5kq6i2xway53m6pzv6q +identiguy.i2p,3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva +ilcosmista.i2p,6u2rfuq3cyeb7ytjzjxgbfa73ipzpzen5wx3tihyast2f2oeo24q +ilita.i2p,isxls447iuumsb35pq5r3di6xrxr2igugvshqwhi5hj5gvhwvqba +illuminati.i2p,syi6jakreatlm2z22u76izyqvbm4yi4yj7hr7jb63lgru5yhwwla +imhotep.i2p,qegmmhy52bdes2wqot4kfyqyg7xnxm5jzbafdb42rfoafadj2q7a +in.i2p,r5vbv2akbp6txy5amkftia757klgdy44s6cglqhmstpg65xycyjq +infosecurity.i2p,v3gkh5kqzawn2l3uzhw6xnszsh6w3nztjmlwil7p4kyrwrsm2dba +infoserver.i2p,jd3agbakybnhfvkeoxrx7t33iln6suzomv3kxkxf77j7rkonch6q +inproxy.tino.i2p,ex5yf6eqqmjkrzxnkn6cgvefgne24qxsskqnpmarmajoit43pgma +inr.i2p,joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq +instantexchange.i2p,5wiyndm44bysev22kxvczxt37p6o6qroiqykytrvn2yzi55aqfxq +investigaciones.i2p,n7hqd4asxrdwf3zwo7rzv27y2qkcfmakmz6mjar6aw6hlc4c7mha +invisible-internet.i2p,jnpykdpp46zenz4p64eb3opadl5g42dls3rurk2cvq6a3g3rvbvq +io.i2p,tx22i6crnorzuti3x6va4mijsbhoqswy2cfdxjbvprgsq4eerg7q +irc.00.i2p,bvcja52pppgfspp2ueuipoysjnvvoyblz2h6smpxcmanjquogirq +irc.arcturus.i2p,5nywlbn35p2nwsymwpfmicu6fxono6g64vwusxbsvmm2qwz6vupq +irc.baffled.i2p,5zmtoopscym6qagkvpgyn7jnkp6dwnfai745xevkxlou77c2fsjq +irc.carambar.i2p,hxzbpivxqxy6nuae4t6fnkhcgnhs4c72vt6mmsqfmfhrkn2ca6gq +irc.cerapadus.i2p,e4ckznxcxvgyikzjmjsu72i2dbj2d76ogexyukklbjvpcnhp6zzq +irc.dg.i2p,fvp3pkcw4uvijqabwtekcdilklp73gyasuek67wdcs2mucep4caq +irc.duck.i2p,chdpmm4gxffyn24xx5dhxvfd5httu42i5gtoe6cctjlsf4mbofeq +irc.echelon.i2p,ez2czsvej5p3z5bquue5q3thujcodfze7ptybctqhnqc7hms5uzq +irc.freshcoffee.i2p,ubiu2ehtfnrleemgpzsqkahwnvzuaifqa3u4wmaz5maaisd5ycfa +irc.i2p,l3ohmm4ccxvyuxuajeaddiptci5lsrnxtvtyq7iohphrt3oj2evq +irc.ilita.i2p,5xeoyfvtddmo5k3kxzv7b3d5risil6333ntqrr3yvx3yubz5tk3a +irc.ircbnc.i2p,4rqcsqd7xif6r4v55blqvmqu5er6due4eyene3mjorfkts4o3rxa +irc.killyourtv.i2p,wre4majmg2vnbi6id27et7yw6lnpf56wkbm6ftnlwpvxnktq73hq +irc.nickster.i2p,dhq3fhd5scw3jqhj5ge7kqfpprfolcgxfjbaw24obohaiqjtdu7a +irc.orz.i2p,7gifacog4aoons3syybojbbnyqqaaqijhngrehn2xlq3eucuyjcq +irc.postman.i2p,mpvr7qmek2yz2ekegp5rur573z7e77vp3xqt2lfbco5i6nkfppcq +irc.r4sas.i2p,hodhusp73gltozgrnianlbploon3rrvhrzfn5mf2g46o7aaau5la +ircssl.cerapadus.i2p,4x2i745i4w52ss3he2kse6tzwt64pr62yvrcb72lgvrb63fup6ea +irongeeks.i2p,ecduxoion5uc5hnvzjxff6iiwhdwph6gse3dknyvlo7e6gaeho7a +iscofsi.i2p,enjgdxs4um2dmhdb2ajff2egrdijkjji3g47m6unb74swbrqsddq +isotoxin.i2p,wue3ycaccf4texikza3fh6p5yrmtgnooisuypnepo5mo67lmpcqq +itemname.i2p,o35ut7hgywy35okvgkjkv3ufzv2ejv4luap4oytwbyy2jqy6u4vq +ivorytower.i2p,fpwrfvidfexsz7dspofkwtkmmizm7lyralfz5kvykffk7gubvxsq +j.i2p,kjxvohlsf5sdrzxzfcrmvquccnoevi6ytbl63mstsru5wt2dx3ea +jabber-2.i2p,pvnmzgemetkwcuvt45omgowmeznwk5xw3nc3ygeoz7yekqxy57na +jabber.duck.i2p,rhdzvvzraqzzm67zpyegb7knpfrjeffitixqzeyymdoz56uh2rtq +jake.i2p,v2axvy6pqefnla7gun5fmqs4lqe4xfyqovgzcundhxrpcdvfd7cq +jar.i2p,2fthkmujup3xiiu3yple24n6g4emzdiiimbuqwvpdddtsr3c4nrq +jazzy.i2p,ha5c3zafwkt6mwqwjcf4oqwvbwz473652ljjadiwrj4gfkfkjofa +jdot.i2p,kw4jr5qw4bhnj33avkwankjdh3zi7wtahlmgkjwvsv2isskkzgpq +jhor.i2p,c6rnm7oemydhuwzmhwwwxphkzanez5rnn7fkcs3lpgu6gkgtssoa +jikx.i2p,aazr55itvyns4lwppvx5njyx5tjdwemw4w6jbmpegdunznod2ieq +jisko.i2p,jxgfvr663uhr6m65hrgkscshysfshkq32ywdubc4ed7zda3e2pca +jmg.i2p,oglpnq7zungdukmk6gk5fzj5jp6wibuoihqgks453wztrwos4ggq +jnymo.i2p,nbfplxgykyfutyadlfko2rmizdsxox2pee2ahboj5mju4s3putda +jrandom.dev.i2p,htynimemonyzqmn76gworxyfkmqtsa7zcprbrd3i5cxqqm75tuzq +jrandom.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja +jrrecology.i2p,qxi24gpbum3w3kesuxvheyu3p5u5o6tuvoypaolub2gnvbld57xq +jwebcache.i2p,xdffxnxtjd6ji2zig3cgva7igvl2tiapyjoc7ylbzwqhxudbmvfa +k1773r.i2p,zam7u6vslhemddz347uusuzjdk5wma4h5hcmcqlng4ybbpdbjhnq +kaji.i2p,z5ic7gvm2k4doczphtrnrspl2w5sfbss2de4z3ihjijhtjw67ydq +kaji2.i2p,4lscgc6napekfx7ay5fdcjofeja4fnl7tqcd3fek63t4saavur2a +keys.echelon.i2p,mwfpkdmjur5ytq4og36ym3ychinv36b2a57f4rmgqmtrwepq3fva +keys.i2p,6qv4x7ltaxckd4vbay5s4ntqqflq4efk6oke2d5yzicqrmk443ba +keyserver.sigterm.i2p,isoxvnflrdn7cm76yjlfg5tbcugoito2hur7eidbqmo33xmwz5ga +killyourtv.i2p,aululz24ugumppq56jsaw3d7mkbmcgo7dl2lgeanvpniyk2cbrda +knijka.i2p,knjkodsakcxihwk5w5new76hibywia5zqcgoqgjttzsausnd22oa +knotwork.i2p,2yocdbcjiyfaqgxb4l6oenrrrrie6nydgmbnbfulqg7cik6bozxq +kohaar.i2p,qchpjehbhqjbxdo7w3m55jbkrtsneb7oqoxcr24qttiq6j5g3z5q +krabs.i2p,3yamyk5bgfgovg6zpvtvpdjk37ivjj2wog2w7wha5agzgxxkqaca +kuroneko.i2p,wbit2huhhwlyqp2j4undccuyrodh6qcmzdeyuaoy5o4ym7g5gdgq +kycklingar.i2p,gctswdhp4447yibxfbqg3uq2bvx63qjeqnaoaux75zw73leakyva +lazyguy.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq +legion.i2p,5oirascyhwfy2tr2horw6mixozsre7z6s7jfq7qbnj523q3bkebq +legwork.i2p,cuss2sgthm5wfipnnztrjdvtaczb22hnmr2ohnaqqqz3jf6ubf3a +lenta.i2p,nevfjzoo3eeef3lbj2nqsuwj5qh3veiztiw6gzeu2eokcowns3ra +libertor.i2p,7gajvk4dnnob6wlkoo2zcws7nor3gunvoi7ofalcps5lc76wruuq +library.i2p,brqqaq44vbeagesj5o3sxcnkc5yivkwouafyxa77ciu7l644ei2a +lifebox.i2p,pyqjnycm55cuxow22voqj62qysrjdnb6nbyladaiaiirqi7vp2yq +linuxagent.i2p,ap5riaikrjq2uv5qvy7klzhhqywvqi7wqscyipsewcun7w2eynlq +lists.i2p2.i2p,vmfwbic2brek2ez223j6fc6bl5mmouzqvbsch45msvyyzih3iqua +lm.i2p,yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q +lodikon.i2p,u3f67staiwhqxpacya3clmvurdwd2kp7qcthzhstqnhrmlwc2g4a +lolicatgirls.i2p,a4lzmjyba7aq7hl6okqpds7znnwymolqnr7xhvno2wraqb7uhfla +lolifox.i2p,7fd2clkiotjnaoeigdtxlkkb24eik675ovezjf67x26ysham4zca +longhorn.i2p,pohcihzxzttjclrazhs3p76wt3ih737egb5bovqb6ym3du6z3o7a +lp.i2p,jiklbujn3cbfikf4pca526jgmorx6mxhil3twqmfoteaplx6ddwq +lucky.i2p,wx36m3wnpt2y6bngdpg3ifrarvtkpwnluarx377bllpgvkuhybaa +luckypunk.i2p,y4t6cujjxnnrtln3rgmfbgbh46hic7wkef57krd7opitbgngohka +lunokhod.i2p,3yc6sp7xic4grmpfecbwuij6z3dp5kdgoo362pszaco7io42mnwa +m16.i2p,ucsr3eveuc4mx5y6gxnoaywd4ojvbel5q3ynns6s5yfw3vusmfva +mac7.i2p,3yjowssqzydciwa5zx55kazgf7q7w6g7rkocr7bngy35ii44t34q +madman2003.i2p,a2sam2xbhxbzmeyobphbxrkdwlppoerewq5qvibbyk3ftsr643qq +magix.i2p,cgfnyxv62msfynsfbv3kju22j2mt6tfnopshhmrcmpcrxyts6xwq +magnets.i2p,snz46nez6hrrpg6336neinflw56l3vwatk6bzzytwu77xmsfsoca +manas.i2p,6qolj62ikkoq6wdn3hbvcbdmlvf2rcyv432kgi5uy7mvrczmjtba +manveru.i2p,pbmbofs76wpjnxi55eqtwg4y6ltyij72o4fm4sxfjol3y57ze5sq +marcos.i2p,vpo36bsil2voqaou53zshuegssqaroa5mbrzxfmhjywlbojckalq +marshmallow.i2p,svdqd6j3y3gwryufcl4fkzpmcujgvrvphvk2oy4r7m75xs327e2q +marxists.i2p,lepah55qyp2fhuwxlz7bwrhzckn4gkuofivnofoeuyfpmke5x2hq +mattermost.i2p,x5oovnhnuli5fnwtgkbd5z5jvrvdvprqyuofywx6uoxkk4bie6ya +me.i2p,dbpegthe42sx2yendpesxgispuohjixm4bds7ts5gjxzni5nu6na +meeh.i2p,4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa +mesh.firerabbit.i2p,3x5wokr4bjy5z3ynji4fyhvwzv4fvgry3xafi5df5h75doezjytq +messageinabottle.i2p,avfhe3kvrrv7utxn2vre65lg7damxzzsewq3vukwie4llitd254a +metrics.i2p,z45ieamhex2ihqv7oowk5fz4qq47rbvxhhhbaaiinpajbhuevtpq +mhatta.i2p,o4rsxdeepfrnncsnjq675xogp5v5qkbfgbt6ooqeyfvlifobrjxq +microbleu.i2p,mtapervgibruizniems2yyr47pin2wpysyh7m632rigl26vjc6qa +microsoft.i2p,hvaqr5idszdyrjph34amb4mjosqd3ynggoxlnj7ciqhnx7q6plza +mindisl0st.i2p,u7rnqhvsuyxd3fabm4kyzn7brgz3i3cporj2emk2jmbpcmltyf7a +mindspore.i2p,uuh5dd3y2rqa7x2jpggm4p2pg6znarm5uanwsvybe4tk36ymwr4q +modulus.i2p,ctz3o6hdefrzwt3hlg6rjhdcbjk6irppbndq32u6jnn4lz72f62a +monerotools.i2p,5bal7dngxde2ddmhuzbtfken6w5nmxmixtjlrlmxt3wbhnemv73q +monerujo.i2p,puri6y5dtwh6zr4u77ep6ozatun6iz7v4wai2dzxppz7654corlq +morph.i2p,iovyp2dao5rta6g5v6hke2s4ugx2btkpcljddak2yhxfrx3l4dqa +mosbot.i2p,5bhmrp43mjwlzf4x64xgdrkwmw4luvng6eq5waa663a7vnkp732a +mosfet.i2p,s5ynkgagndmpxpf2kmnenv4x72io664gzd2x3qef54ilammnte3q +moxonom.i2p,gcjdrvnlobgexh7ebv276pwmnoj3yoyaqm3w4vmmdha4lgxfinqq +mp3.aum.i2p,n7bmu5dwux7f6gedmdik6zrm77bnls4lkzo2vo3bf4bwegk7vkjq +mp3.tc.i2p,w3ied5s7ldjcvnhxu2gyofe3oogzbplkyxshzfkhspiy2526snsa +mpaa.i2p,m6cqnglo7xlytwxkdsmwf3d23d6lq5r446c3tktb2tdmuah36zya +mrbamboo.i2p,tmpmkx6wlbbrgsnexrqlrib7laoegpbfeop7bnyezegii7hecpxa +mrflibble.i2p,u7k2qcmkrril6yvudvwxjqz7k3dzgp3jdejjjeapej7liselj3eq +mrplod.i2p,fjn5hxtybxyfyvdf6u5v5seg2sjd47hb5by6sa6ais4w3xnrxwyq +mtn.i2p,xisk3h6sku3iqj52uriogaajmnku7pwjux7wa4omx2zloamuw6eq +mtn.i2p-projekt.i2p,f52x5fp6uhq53f5zle5d6rq5un34xgmxgazvilvmzcby37xcmsfa +mtn.i2p2.i2p,l6kuhtmgvbp57d7jwalj5nksi6nr4gfzbz4oit62lxgipb3llt5a +mtn.meeh.i2p,h7ylrsuzzynrxp3jql7anoozyqblavj7eqces6o3wngvuuxhs2la +mudgaard.i2p,yz32lk42gtoesknesfolq3tt4erxxcejcote5pontaeqev3bj2kq +mush.zeit.i2p,dk3sg23kljawxqp3cb6xz5mnzjlyckzvq5jhqs5gnvdsv7wqn6ha +music.i2p,akamh76yi6p7xxbvl3qv3yhaockne57yfuh77acogbgpjmwypvia +mysterious.i2p,p66g2a4nzfkvidd3l7nwphcnfa3ttyu5kiolcb4czec2rn2kvwsq +mywastedlife.i2p,ceumy3puvvsrru5bmfmtgsajsx5qyehqac7l7a23xpwtfs2bvcgq +nacl.i2p,bm2fib3tumer72lopjh4nmqomwvqu2sdfyb2hmr6lnk7jbw3vvia +nano.i2p,ex5ssv7s3hj6jp7hvadxfw3wvbjbvnczxr4pbk7qw26ihiorjmba +nassai.i2p,v653cocvn3i6bgjdm3ciwbdnu32supglv6gn4fh23bohemsp545q +neodome.i2p,5hkhjehj3ct2pvcah7dcylwef2oti3xij5myxbv3pd7rocio5vkq +news-i2pn.i2p,wwcqkwfo5yhe6uribv5tzylk25j5hkdk6gdnyftzd3k7dawlzwca +news.neodome.i2p,trhwcnygfkeqjj6g4xhmrdp4gsjqsye47lsxshbmwbten4ywt5oq +news.underscore.i2p,rl7t3kspoktuatjcu7gf7xleu7y6biibs4fspzo24kll6n7hbq4q +newsbyte.i2p,gsk3rgsejxxrfabjxu5w5plplxsu47aoeoke22vvhlwwllzosnxq +nibble.i2p,jmdxcpdzqafedn3clc4y7u6o56qocfiffrzbzncmtggqtio5qjpa +nic.i2p,vzu5ymab6klevpcdudv4ypisjqaznmt44e6lcg7dwiuza4saibxq +nickster.i2p,zkwsa6kvq2wdhovw5g5wqakpb7rlaylyhfriwmurots5pvwbqauq +nickster2.i2p,eofzi7npzpk4p5gb4qper4hmwgxo6kepo3dheeblakewedxj2bwq +nickyb.i2p,gmpxk4tje7mnud32kg2kjmf36f6cpwqakzc2dxuzjnnz4qr5w4sa +nightblade.i2p,p4gkon7ytswxrbwkl7vruw6mg7kfw5aofovqjgt4c7tnqmbq6lha +ninja.i2p,q6dg6hlb3egzdqz352ri5rc4fx4gcrdeu3tpiyfxlv73yfjgrhya +nm.i2p,3itdpqzyn3ii7sivppo4sxxwhvgtpskzkbokrdibim6gqpvlw5ya +nntp.baffled.i2p,kc6muo2tih5mttbpzecteegvtonuysjidk3emcy4cm4yifzild2a +nntp.duck.i2p,gvzzor4utsqxswvf6jaglfks7yxudlz2s326ftrk56i4lpd2s47q +nntp.fr.i2p,npoztnqadfnu4vrokoh6rusoi3yne47s6jurc3lzhcrzzia5eqva +nntp.i2p,wwdzmeyler4djegvyt2bxmkwrckfgg3epkkwowyb75s47he6df6q +no.i2p,lpsg4x4gdrf7antxcdy47cl6abcqei5ommgzt55retq7go5ku3ba +noname56.i2p,oiyoslismzyxuw7ehxoigmtkdj35idim6flmlplddxuiiif6msfa +nop.i2p,ssag45lathm4gqp46si7c4w4tioyvjpcza5uvz5x2zuljnplylca +normal.i2p,j5fex634r2altzb3kjvu35qekt2r3hgsqzg5qxoy7dp53heu5pma +normanabcd.i2p,si2vh43gvxjnw2shwr24j76xyanow4oa6gbu4idookbraoxl3s3a +nothingburger.i2p,tesfpn757ysc7nih7mxher2b3jstkc3l5fhfcyb5kxhzhvv52trq +nothingspecial.i2p,wzrwqrp52bilqijrlboclynuev4kzpjzfzlvzl5aqxqt5fdnpbga +novospice.i2p,ukqap24nwac4gns77s4zy7j5cagt7l7syb5zo7eukfg3zn5gg5qq +nsa.i2p,nsetvbclpomqxfcit4mghn6z7vdhnza6jdzczby4crnto32uykga +nvspc.i2p,anlncoi2fzbsadbujidqmtji7hshfw3nrkqvbgdleepbxx3d5xra +nxt-wallet.i2p,33pp74k4ivy67z332qpyl3qlcqmi6gxqumrow4bldkblxxlxqq5a +obmen.i2p,vodkv54jaetjw7q2t2iethc4cbi4gjdrmw2ovfmr43mcybt7ekxa +obscuratus.i2p,i4j37hcmfssokfb6w3npup77v6v4awdxzxa65ranu34urjs4cota +ogg.aum.i2p,wchgsx6d6p3czloeqvna2db5jr7odw4v4kqrn4gr4qiipfyrbh5q +ogg.baffled.i2p,tfbvj2xal6lcuxv3hzuw7cw4g3whguombcv2zuotzvul4qtrimgq +ol.i2p,bnb46culzbssz6aipcjkuytanflz6dtndyhmlaxn3pfiv6zqrohq +onboard.i2p,qwlgxrmv62mhdu6bgkh4ufnxowxsatfb6tbs2zr666qyunwqnecq +onelon.i2p,irkvgdnlc6tidoqomre4qr7q4w4qcjfyvbovatgyolk6d4uvcyha +onhere.i2p,vwjowg5exhxxsmt4uhjeumuecf5tvticndq2qilfnhzrdumcnuva +oniichan.i2p,nnkikjorplul4dlytwfovkne66lwo7ln26xzuq33isvixw3wu3yq +onionforum.i2p,yadam2bp6hccgy7uvcigf5cabknovj5hrplcqxnufcu4ey33pu5q +ooo.i2p,iqp5wt326fyai5jajsa3vkkk5uk56ofn4anocgpe5iwlpisq6l7a +opal.i2p,li5kue3hfaqhhvaoxiw2ollhhkw765myhwcijgock5rs4erdqdaa +open4you.i2p,ice6ax5qrzwfwzsy64bctffj6zlzpuzdr5np65zsxlbt7hztyc6a +opendiftracker.i2p,bikpeyxci4zuyy36eau5ycw665dplun4yxamn7vmsastejdqtfoq +openforums.i2p,lho7cvuuzddql24utu7x6mzfsdmxqq7virxp5bcqsxzry2vmwj5q +opentracker.dg2.i2p,w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa +orion.i2p,5vntdqqckjex274sma3uqckwqep2czxs5zew25zlntwoofxk3sga +orz.i2p,oxomqkekybmyk6befjlouesit5mhstonzvzd2xnvsk7i6uyrqsfq +os3.i2p,s7x4ww5osrrfein3xgwyq67wnk6lgliw4mzt7shtu66wrb2zdojq +osiristomb.i2p,t3slf77axkv3qm7c3gzpv3jgmkraoqqe2bojr6h66eipibofsyzq +ot.knotwork.i2p,cxhvvfkbp2qbv5qojph7zb46molpe2ffanghnerjag3xdmy6ltxq +outproxy-tor.meeh.i2p,77igjr2pbg73ox5ngqy5ohzvrnur3ezqcogtl4vpuqtrcl3irsqq +outproxy.h2ik.i2p,nwgvfpfarpnyjjl4pwsxr2zdsppcx5we3kos2vlwicbiukopgaza +outproxyng.h2ik.i2p,v32zse2zczzgegelwxbx7n5i2lm2xhh2avltg76h6fz5tb53sfxq +overchan.oniichan.i2p,g7c54d4b7yva4ktpbaabqeu2yx6axalh4gevb44afpbwm23xuuya +p4bl0.i2p,lkgdfm4w6e2kkjhcdzr4ahhz26s3aunhrn6t2or436o73qh4z7ga +pants.i2p,xez3clscjfafkqwk6f473ccp3yvac4kh6rdp6dptwxa2lhixizgq +papel.i2p,mxskjqntn2d34q4ovsnd5mud7cgde734tdjldd3lt4hczh2645zq +pasta-nojs.i2p,dkkl3ab6iovxfqnp44wsjgqaabznvu7u3hugpzyagbeqlxgvx3la +paste.crypthost.i2p,2zaj4u4s4l3lgas2h5p6c6pvzr2dckylkrh5ngabursj4oh25ozq +paste.i2p2.i2p,b2gizskfea4sjxlw6ru2tb6kdrj47dsjc77cijsf5mzh4ogbmfvq +paste.r4sas.i2p,csen43keji3qiw6uobsgzysxyjd225g6446ylq5uuz6ur2glkzaa +pastebin.i2p,mnicncxrg2qqi55qftigiitaheugnj4rpysbk7zabdrirgktelqa +pastethis.i2p,erkqiwnjl7vtysqd3wvddv6tfvnhswarqkbn4blhdlhfxn7cf2ha +pdforge.i2p,wzeg3ehf6d2mqjqji3sd3rns776thvhe2vam2r6gjlmsqis2dctq +perv.i2p,f3k3wm4ae7t7ottfjd4hu6is7zsls73izl2gm2qynzficxcdsiwq +pgp.duck.i2p,wujajyxj3cgsfsbtr3g7g7npv5ft3de6pcstxlav26zq6cxdjmha +pharos.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa +pharoz.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa +phonebooth.i2p,noxia7rv6uvamoy2fkcgyj4ssjpdt4io6lzgx6jl6wujpufxedrq +photo.i2p,fqhuy77ugd5htnubzkyy5guvwboqn6goahtmn2g7feewvdj7k3iq +piespy.i2p,vzusfjzcu5ntnvobcvyzc4dcu4j6ommtnpmba2puk3kexgdzrl7a +pisekot.i2p,7yzdwhy723fodqz4onp6k3nyvixra2sa6dl45tcblhmyoa7i36nq +pizdabol.i2p,5vik2232yfwyltuwzq7ht2yocla46q76ioacin2bfofgy63hz6wa +planet.i2p,y45f23mb2apgywmftrjmfg35oynzfwjed7rxs2mh76pbdeh4fatq +plugins.i2p,wwgtflbaa7od2fxbw4u7q7uugmdclxf56alddvizugwcz5edjgia +polecat.i2p,het5jrdn35nhkanxmom5mjyggyvmn2wdj2agyqlrv4mhzhtmavwq +politguy.i2p,6dkkh3wnlwlr6k7wnlp4dbtf7pebjrph5afra2vqgfjnbihdglkq +pomoyka.i2p,omt56v4jxa4hurbwk44vqbbcwn3eavuynyc24c25cy7grucjh24q +pool.gostcoin.i2p,m4f4k3eeaj7otbc254ccj7d5hivguqgnohwelkibr4ddk43qhywa +pop.mail.i2p,bup6pmac7adgzkb5r6eknk2juczkxigolkwqkbmenawkes5s5qfq +pop.postman.i2p,ipkiowj7x4yjj7jc35yay3c6gauynkkl64gzzyxra3wmyhtfxlya +pravtor.i2p,2sr27o5x2v2pyqro7wl5nl6krrsbizwrzsky5y7pkohwh24gn6xq +pris.i2p,ahiwycgzuutdxvfqu3wseqffdnhy675nes57s4it2uysy5pxmz6a +project-future.i2p,ivqynpfwxzl746gxf376lxqvgktql2lqshzwnwjk2twut6xq7xta +projectmayhem2012-086.i2p,ehkjj4ptsagxlo27wpv4a5dk4zxqf4kg4p6fh35xrlz4y6mhe4eq +protokol.i2p,f4xre35ehc5l6ianjvt3zcktxkjlyp2iwdje65qnu2j6vurhy6nq +proxynet.i2p,7gar5a3n4hzvsgi73iizo65mjza4kujf7feopfxuwu5p6wtwog5a +psi.i2p,avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a +psy.i2p,s3elzoj3wo6v6wqu5ehd56vevpz2vrhhjc5m6mxoazicrl43y62q +psyco.i2p,eoilbrgyaiikxzdtmk2zeoalteupjrvcu3ui23p4wvfqo25bb73q +pt.hiddenanswers.i2p,o5jlxbbnx3byzgmihqye3kysop5jgl3unsrkmurbtr2nrnl2y74a +ptm.i2p,7dna5745ynxgogpjermnq26hwrqyjdlsibpjfmjxlwig247bjisa +ptt.i2p,q7r32j7lc3xgrcw2ym33wv4lfgqbez7vtm4lts7n34qfe3iygeha +pull.git.repo.i2p,3so7htzxzz6h46qvjm3fbd735zl3lrblerlj2xxybhobublcv67q +push.git.repo.i2p,jef4g5vxnqybm4zpouum3lzbl6ti6456q57nbyj5kfyldkempm3a +pycache.awup.i2p,w45lkxdnqhil4sgzanmxce62sv3q4szeowcjb2e72a5y5vbhm4ra +r4sas.i2p,2gafixvoztrndawkmhfxamci5lgd3urwnilxqmlo6ittu552cndq +radio.r4sas.i2p,cv72xsje5ihg6e24atitmhyk2cbml6eggi6b6fjfh2vgw62gdpla +ragnarok.i2p,jpzw6kbuzz3ll2mfi3emcaan4gidyt7ysdhu62r5k5xawrva7kca +ransack.i2p,mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq +rasputin-sucks.i2p,fdozdbyak4rul4jwpqfisbkcx4xbrkuvf2o5r6fd3xryyrjgvjiq +rebel.i2p,nch2arl45crkyk6bklyk2hrdwjf5nztyxdtoshy6llhwqgxho5jq +red.i2p,fzbdltgsg7jrpz7gmjfvhpcdnw5yrglwspnxqp4zoym3bglntzfa +redpanda.i2p,3wcnp6afz4cikqzdu2ktb5wfz7hb3ejdbpn7ocpy7fmeqyzbaiea +redzara.i2p,ty7bt62rw5ryvk44dd3v5sua6c7wnbpxxqb6v4dohajmwmezi7va +reefer.i2p,4cde25mrrnt5n4nvp5tl62gej33nekfvq2viubmx4xdakhm5pfaa +relatelist.i2p,utrer5zgnou72hs4eztmk37pmzdtfw3d6s23wwl7nk3lkqpzbdiq +repo.i2p,uxe3lqueuuyklel23sf5h25zwgqgjwsofrqchhnptd5y6pedzbxa +repo.r4sas.i2p,ymzx5zgt6qzdg6nhxnecdgbqjd34ery6mpqolnbyo5kcwxadnodq +reseed.i2p,j7xszhsjy7orrnbdys7yykrssv5imkn4eid7n5ikcnxuhpaaw6cq +retrobbs-nntp.i2p,fkyzl24oxcxvjzkx74t3533x7qjketzmvzk6bwn3d6hj5t7hlw6q +retrobbs.i2p,mnn77stihntxdoade3ca2vcf456w6vhhvdsfepdvq5qggikvprxq +retrobbs2.i2p,ejff7jtyaus37slkwgeqrrcmyhpj26carp7n27f5h6s5vlbeiy6q +revo-ua.i2p,hpojpumki22xjwhmhe6zkiy44oanyn7u4ctcfe3in2ibwm5l32hq +riaa.i2p,lfbezn7amkzhswnx7lb4lxihyggl2kuqo5c7vwkcv6bwqmr4cuoa +rideronthestorm.i2p,xrdc2qc7quhumhglpbcuiqxr42nuffv4xj4a73jbr4ygepitibqq +romster.i2p,eaf2stdqdbepylt53egvixdi34g2usvgi7a4oixsja6atkran43a +rootd.i2p,mzbe5wofwn7eaqq4yefrmxizqaxoslwqxrv5qcv2opx5lnhg64dq +rospravosudie.i2p,z55khrnlj6bzhs5zielutm6ae6t2bbhfuiujwlrp3teubqyc4w7q +rotten.i2p,j4bm3rvezlejnb44elniagi5v2gazh7jaqrzhbod2pbxmgeb2frq +rpi.i2p,56p5qxsrvo5ereibevetw2qbj5bronmos7wxunku27g2s4kpbnlq +rslight.i2p,bitag46q3465nylvzuikfwjcj7ewi4gjkjtvuxhn73f6vsxffyiq +rsync.thetower.i2p,w4brpcdod7wnfqhwqrxyt4sbf2acouqfk5wyosfpq4mxq4s35kqa +ru.hiddenanswers.i2p,o6rmndvggfwnuvxwyq54y667fmmurgveerlzufyrhub6w3vkagva +ru.i2p,m7fqktjgtmsb3x7bvfrdx4tf7htnhytnz5qi2ujjcnph33u3hnja +rufurus.i2p,7msryymfdta3ssyz34qur6gi4jyfkvca5iyfmnceviipwu7g2wca +rus.i2p,gh6655arkncnbrzq5tmq4xpn36734d4tdza6flbw5xppye2dt6ga +ruslibgen.i2p,kk566cv37hivbjafiij5ryoui2ebxnm7b25gb3troniixopaj6nq +rutor.i2p,tro5tvvtd2qg34naxhvqp4236it36jjaipbda5vnjmggp55navdq +salt.i2p,6aflphlze6btsbez5cm4x53ydrmwhqrkxsud535d3qjh4wq62rxq +sasquotch.i2p,p6535uyfk2y6etc3t47vd3oqxydznqior5jxcvq5bdxe5kw5th6q +schwarzwald.i2p,4gokilzy73mmudufy3pohgatm42fcstx7uzg5hjvnfyphxpnphuq +sciencebooks.i2p,ypftjpgck75swz3bnsu4nw7rmrlr2vqsn4mwivwt3zcc3rxln5cq +scp.duck.i2p,ghbpsolpnveizxu4wbs7jbs2vj3kntnsexfcdleyhpqdhfpxleda +search.i2p,nz4qj6xaw5fda3rsmsax6yjthqy4c7uak2j3dzcehtkgyso4q46q +secretchat.i2p,cl3j2zxhpw6u6jevny45i557ojhwfxn4g375nnuqhy6lp27mry2q +secure.thetinhat.i2p,4q3qyzgz3ub5npbmt3vqqege5lg4zy62rhbgage4lpvnujwfpala +seeker.i2p,ipll7sit24oyhnwawpvokz5u7dabq6klveuqpx3sbi6o5qemy2bq +seomon.i2p,5mvpsy4h45w4fx7upen7ay3vkrs5klphz5nptmtcqvc3fsajsm4q +septu.i2p,5lqvih7yzbqacfi63hwnmih57dxopu5g2o5o4e2aorq7bt4ooyra +serien.i2p,3z5k3anbbk32thinvwcy4g5al7dmb75fagcm3zgh4rzrt3maphda +ses.i2p,5qfoz6qfgbo7z5sdi26naxstpi2xiltamkcdbhmj6y6q2bo4inja +shiftfox.i2p,wpvnuzslu7hjy4gujvnphtyckchdoxccrlhbyomsmjizykczyseq +shoieq3.i2p,3fjk4nfk3mccch4hdreghnyijcvovsi3yucjz3qzj5sxngqk5j6q +shoronil.i2p,7shqzgmb6tabiwrnwlasruq7pswy2d3emvfhaitehkqgod7i62sa +short.i2p,z5mt5rvnanlex6r3x3jnjhzzfqpv36r4ylesynigytegjmebauba +sion.i2p,lcbmmw2tvplvqh2dq5lmpxl3vnd5o4j3bdul5moa23deakjrso5q +sirup.i2p,aohdp4yajnkitrtw7v2mo3sp7swuqhjfwlsi5xwd7dudzftumsma +site.games.i2p,zeuczucfxeev3k7tvqlfcdpfbnqggheiknyyb5r2q4utn3d2auja +skank.i2p,qiii4iqrj3fwv4ucaji2oykcvsob75jviycv3ghw7dhzxg2kq53q +slack.i2p,gfcsh2yrb2tx7hyvmobriv52skz7qoobn7n7y7n6xaehhh4rpbja +slacker.i2p,wq7m2wdguzweleb666ygv3bmfhha63zj74rub76vfesbyhsyk6iq +smeghead.i2p,ojf4czveeuekxqkjvkszvv7eiop5dg7x2p6rgfzl4ng4xrjk6lja +smtp.mail.i2p,kdn7zx7fgoe4bn5abaaj5cb3e4ql22fklb5veui5yajpj4cxapya +smtp.postman.i2p,jj7pt6chsziz6oxxnzpqj7mzhxm2xfhcrbh7dl3tegifb577vx5q +socks1.tor.i2p,sifawcdexgdmoc3krv46pvvz74nzd6fkju2vzykjxsx3egqsb6wq +sonax.i2p,jmuxdhlok5ggojehesfjlit2e2q3fhzwwfxjndts7vzdshucbjjq +sponge.i2p,o5hu7phy7udffuhts6w5wn5mw3sepwe3hyvw6kthti33wa2xn5tq +squid.i2p,r4ll5zkbokgxlttqc2lrojvvey5yar4xr5prnndvnmggnqzjaeoq +squid2.i2p,hum4wlwizbsckbudcklflei66qxhpxsdkyo4l2rn256smmjleila +sqz.i2p,3jvbwc7sy4lnhj25nj7yepx7omli4ulqirnawv3mz6qlhgokjgzq +ssh.i2p,xpvdadaouc4qr75pteymyozc7mcsynjfkuqqkkla542lpcsqionq +stasher.i2p,6ilgpudnba4kroleunc2weh5txgoxys5yucij5gla6pjyki4oewa +stats.i2p,7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq +status.str4d.i2p,ycyyjo3psqbo45nuz243xvgvwnmzlanzqbzxv3kh6gyjztv7425q +sto-man.i2p,rg4eilfpe24ws6nctix63qw2dlvd2tqgwdcgdxzji6l5bc4dc7aa +str4d.i2p,wrrwzdgsppwl2g2bdohhajz3dh45ui6u3y7yuop5ivvfzxtwnipa +stream.i2p,prmbv3xm63ksoetnhbzqg4nzu2lhqdnqytgsydb7u3quxfrg7rna +streams.darkrealm.i2p,ud3gcmvysjch4lbjr2khmhqpf7r2x5if4q43xkqdptl4k7lc4muq +striker.i2p,4gswsrfpbd44hwjoj33jbqfbwzxfkwpuplb3ydq5zm7nfu2pxvdq +subrosa.i2p,g3lnglrnoual7wyabnwwv37uwhadgbxiqz36pf3f5cwfuxsx4mxq +subterra.i2p,vdmhe4u26unzgd7ysq6w36ubjncms5wzbhzr2gq576sq4xut5zwq +sugadude.i2p,yzjn76iyqard64wgggfrnywkxi7tbfkw7mjhpviqz3p2dguey4yq +suicidal.i2p,yfamynllow5xiqbbca7eh5xn733wtnuti5bi4ovc7dwycntqmiuq +sungo.i2p,h67s3jw56rwfyoxqxj3fngrluybsgxc2meendngkehzqowxnpj3q +surrender.adab.i2p,jgz7xglgfgnjfklrytyn427np2ubipztlm5bxrtbiucayglukrta +susi.i2p,qc6g2qfi2ccw7vjwpst6rwuofgzbeoewsb2usv7rubutf4gzqveq +syncline.i2p,5kcqmhislu3lmr7llgmdl72yu3efhyriljdc6wp774ftpwlcs5ra +syndie-project.i2p,xa63tpfoaqt3zru2ehxjjfbpadwj4ha6qsdvtcqtyr3b7hmt4iaq +syndie.echelon.i2p,vwrl2qmcif722fdkn3ldxcgz76df5cq4qypbndzthxwgmykyewta +syndie.i2p,7lm3yzpuejhpl4tt4l7o4ndqlu7hgijohofh7oaydx7q7kelenbq +syndiemedia.i2p,4lrbbblclodhobn3jadt5bf2yab2pxzoz4ey4a2cvrl44tdv3jma +tabak.i2p,y5o2vwb6kart7ivpnbpk4yte3i7kf2dsx7fy3i6w7htqtxhmbzia +tahoeserve.i2p,yhs7tsjeznxdenmdho5gjmk755wtredfzipb5t272oi5otipfkoa +tc.i2p,qkv2yk6rof3rh7n3eelg5niujae6cmdzcpqbv3wsttedxtqqqj7a +telegram.i2p,i6jow7hymogz2s42xq62gqgej2zdm4xtnmpc6vjcwktdxpdoupja +templar.i2p,zxeralsujowfpyi2ynyjooxy222pzz4apc2qcwrfx5ikhf64et7q +terror.i2p,wsijm6aqz4qtuyn2jedpx6imar5uq4yuhjdgtfqumxbqww47vbnq +thebland.i2p,oiviukgwapzxsrwxsoucpqa47s3wt6nfuhfjxvgbqsyrze2mwrda +thebreton.i2p,woutbsflcrlgppx4y7ag2kawlqijyenvlwrhbbvbkoaksuhf2hkq +thedarkside.i2p,fxt3z33nzkrg5kjrk7bp5vvmu7w2vsn4i6jo6cily3hsm6u664ca +theland.i2p,26ppxbseda6xmim37ksarccdb4q5ctdagfmt2u5aba6xjh452zsa +thetower.i2p,3xqa5nype64y6fxgqjq6r5w2qpiqftoraj2niebumseat4cj654a +thornworld.i2p,vinz4ygmodxarocntyjlfwk2wjpvzndlf4hxss2w2t3fk52oplva +tino.i2p,e4bfnhvaofu4s67ztcgiskos2mqyhskid64dvlqexxs2c2bno3iq +tinyurl.i2p,mc4oxv3v7dnyzpvok7v5qxkwtgjprgyz6w7x3tag4fipsen6rdwa +tome.i2p,qktkxwawgixrm5lzofnj5n24zspbnzxy4pvjm7uvaxvmgwrsuvgq +tor-gw.meeh.i2p,ounrqi7cfemnt66yhnhigt2u27fkctbvct527cp2522ozy3btjza +tor-www-proxy.i2p,xov45rvjks5fe4ofmpblkj23bnwxgslbypbgvchbr7yul2ujej2q +torapa.i2p,eejqjtpko6mdd4opvntbpsuandstrebxpbymfhix7avp5obrw5ta +torrentfinder.i2p,mpc73okj7wq2xl6clofl64cn6v7vrvhpmi6d524nrsvbeuvjxalq +torrfreedom.i2p,nfrjvknwcw47itotkzmk6mdlxmxfxsxhbhlr5ozhlsuavcogv4hq +trac.i2p,kyioa2lgdi2za2fwfwajnb3ljz6zwlx7yzjdpnxnch5uw3iqn6ca +trac.i2p2.i2p,i43xzkihpdq34f2jlmtgiyyay5quafg5rebog7tk7xil2c6kbyoa +tracker-fr.i2p,qfrvqrfoqkistgzo2oxpfduz4ktkhtqopleozs3emblmm36fepea +tracker.awup.i2p,dl47cno335ltvqm6noi5zcij5hpvbj7vjkzuofu262efvu6yp6cq +tracker.crypthost.i2p,ri5a27ioqd4vkik72fawbcryglkmwyy4726uu5j3eg6zqh2jswfq +tracker.fr.i2p,rzwqr7pfibq5wlcq4a7akm6ohfyhz7hchmy4wz5t55lhd7dwao5q +tracker.i2p,lsjcplya2b4hhmezz2jy5gqh6zlk3nskisjkhhwapy3jjly4ds5q +tracker.lodikon.i2p,q2a7tqlyddbyhxhtuia4bmtqpohpp266wsnrkm6cgoahdqrjo3ra +tracker.mastertracker.i2p,tiwurhqvaaguwpz2shdahqmcfze5ejre52ed2rmoadnjkkilskda +tracker.postman.i2p,jfcylf4j3gfmqogkltwy7v5m47wp4h7ffrnfsva6grfdavdn7ueq +tracker.psi.i2p,vmow3h54yljn7zvzbqepdddt5fmygijujycod2q6yznpy2rrzuwa +tracker.thebland.i2p,s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq +tracker.welterde.i2p,cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa +tracker2.postman.i2p,ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna +traditio.i2p,wkpjjloylf6jopu2itgpktr45t2xvpjijxilxd5tq4i7wkqgwhhq +trevorreznik.i2p,wc2z6o5fxm2saqzpfcawr63lejwccvzkysmgtfudkrigqopzfdma +true.i2p,pdilhl5vmefyzrrnmak5bnmxqxk2pmw7rpy4f7wbaeppqu2vvugq +trwcln.i2p,evml6jiiujhulsgxkdu3wcmkwbokxlv4is6w5qj46tp3ajz3hqzq +trypanom.i2p,tgv5acj4khwvr6t44cmryohybd2e5o2kndysnzae6qwcr4hzda3q +ts.i2p,nebcjgfx3f7q4wzihqmguwcdeopaf7f6wyk2dojw4bcuku472zxq +ttc.i2p,wb4tsfyvfv4idgrultsq6o7inza4fxkc7dijsfpncbx7zko4cdlq +ttp.i2p,uuczclxejmetohwf2vqewovx3qcumdfh5zecjb3xkcdmk6e5j72a +tumbach.i2p,u6pciacxnpbsq7nwc3tgutywochfd6aysgayijr7jxzoysgxklvq +tutorials.i2p,zy37tq6ynucp3ufoyeegswqjaeofmj57cpm5ecd7nbanh2h6f2ja +ugha.i2p,z3f3owc72awbywk4p6qb5l2mxgitvs6ejztggbpn2a3ddmymfjda +uk.i2p,vydbychnep3mzkzhg43ptewp242issy47whamfbxodc4ma6wc63a +underground.i2p,dlnuthb6tpw3kchlb7xoztyspy4ehlggjhl44l64vbcrulrfeica +underscore.i2p,3gmezyig6gvsjbpkq2kihoskpuqpkfrajmhhm7hpyrjuvtasgepa +unqueued.i2p,3gvn4kwd7z74jxc2sn4ucx52dpvpscxbzjluux3ul4t3eu5g64xq +up.i2p,25it5olgdo7pht25z6buzd32sw7jvc65oziqeuocfozfhgua655q +update.dg.i2p,iqj6ysfh3wl26m4buvyna73yhduifv523l7bwuexxak4mgldexja +update.killyourtv.i2p,gqdfg25jlqtm35qnmt4b7r53d6u2vep4ob23fwd42iyy4j6cvdqq +update.postman.i2p,u5rbu6yohfafplp6lgbbmmcuip34s7g3zqdd63cp27dl3nbd7gtq +utansans.i2p,u2oyre7ygqv4qs5xjjijfg3x7ddwtod6nqwgbomuuzljzvnq4rda +v2mail.i2p,4gg7fykcqe7oaqt4w5fmlarnia7vtmwkv3h45zzgoj6o6crryg5a +vadino.i2p,aalttzlt3z25ktokesceweabm5yyhhvml2z3rfotndgpfyh6myra +visibility.i2p,pwgma3snbsgkddxgb54mrxxkt3l4jzchrtp52vxmw7rbkjygylxq +volatile.i2p,q6rve733tvhgyys57jfw4fymqf3xsnza6dqailcdjcq7w4fa5m3a +vpnbest.i2p,ov5f74ndsy5rfkuyps56waf42vxncufqu5rzm3vsnxkdtogccaea +vudu.i2p,3zlwci7pvgep2igygzyjej24ue7mjsktlhaff6crpsr75yquak2q +w.i2p,j2xorlcb3qxubnthzqu7lt4fvxqn63it4ikwmze55yjkzeeampuq +wa11ed.city.i2p,7mxwtmala3ycg2sybjwwfil7s6dqck2fbemeutghhwu73rznmqoa +wahoo.i2p,vqe5vkpe5wbda7lwekcd2jaj44ar3rawgv54u5rcolezbg5f5vwa +wallet.gostcoin.i2p,reuvum7lgetglafn72chypesvto773oy53zumagrpigkckybrwda +wallsgetbombed.i2p,tzhea5d65fllm4263wztghgw4ijdgibsca5xsecp6lk4xlsbdeuq +web.telegram.i2p,re6cgwg2yrkgaixlqvt5ufajbb3w42fsldlq7k5brpvnd5gp6x5a +wiht.i2p,yojmpj3sh76g3i6ogzgsf7eouipdgdij5o2blcpdgmu5oyjk5xca +wiki.fr.i2p,lrqa7hw52uxjb5q3pedmjs6hzos5zrod4y6a4e25hu7vcjhohvxq +wiki.ilita.i2p,r233yskmowqe4od4he4b37wydr5fqzvj3z77v5fdei2etp2kg34a +wintermute.i2p,4gvlfrdy2rkmem33c342tjntpvqik65wekcvm4275qbkuwotoila +wspucktracker.i2p,ubd2txda3kllumx7ftg4unzgqy536cn6dd2ax6mlhodczfas7rgq +www.aum.i2p,3xolizygkzkqrldncjqsb734szznw2u36lliceuacqnbs2n65aeq +www.baffled.i2p,lqrsfslwu4xnubkk2hofhmuvvr4dia2zevxefinbzdsjurvehtqq +www.fr.i2p,rmkgvlfwo3vkb3xrr6epoypxasdzzuilv3sckcqbo6c4os5jo2ea +www.i2p,ojxyenivrrqvycgbxbm3phgisu5abspzq4g2us4fjlwz4tx222va +www.i2p2.i2p,rjxwbsw4zjhv4zsplma6jmf5nr24e4ymvvbycd3swgiinbvg7oga +www.imule.i2p,657xcllunctawyjtar5kgh3wpt6z4l7ba6mmam5rf7hev5w2lsvq +www.infoserver.i2p,fq7xhxkdcauhwn4loufcadiiy24zbei25elnup33a3gfrdzrtlyq +www.janonymous.i2p,vosqx5qw22hwrzcgsm4ib7hymf5ryovsbtaexqrzmnzshy5bhakq +www.mail.i2p,nctas6ioo7aaekfstv3o45yh6ywzwa3vznrdae52ouupzke5pyba +www.nntp.i2p,kly3o7zmetuwyz7xonnhttw4lj2244pkbibjz26uflyfte3b3dka +www.postman.i2p,rb3srw2gaooyw63q62cp4udrxxa6molr2irbkgrloveylpkkblhq +www.syndie.i2p,vojgy5ep4wffmtpjmpnbpa4gq64bgn4yicuw6qmhbm6nqa2ysrva +www1.squid.i2p,vbh3bltd2duwbukafgj6f6vfi6aigwso7snucp5zohnf66a2hkpa +xc.i2p,mt45a2z3sb2iyy2mwauj4rwa2lwu4peanfy6gx6ybidwnbasusyq +xeha.i2p,oartgetziabrdemxctowp7bbeggc7ktmj7tr4qgk5y5jcz4prbtq +xilog.i2p,eoc5i5q52hutnmsmq56edvooulutaxfikddgdz27otmgtsxmiloq +xmpp.crypthost.i2p,ittkqpjuliwsdewdugkhvgzstejr2jp5tzou7p332lxx4xw7srba +xmpp.rpi.i2p,3yv65pfwiwfuv4ciwtx34clqps6o2mc3vtyltcbqdkcki6untbca +xn--l2bl5aw.i2p,d2epikjh5crt2l5xjmtceqw2ho44hzp6x3u7hgjrd4mi4wywikwa +xolotl.i2p,rwr6rrlmrotxfkxt22mah42cycliy2g5k7hgxyxkpcyyxkd2bgwq +xotc.i2p,gqgvzum3xdgtaahkjfw3layb33vjrucmw5btyhrppm463cz3c5oq +z-lab.i2p,s6g2pz3mrwzsl4ts65ox3scqawfj7mzvd7hn2ekiiycawopkriba +zab.i2p,n4xen5sohufgjhv327ex4qra77f4tpqohlcyoa3atoboknzqazeq +zcash.i2p,zcashmliuw3yd2ptfyd5sadatcpyxj4ldiqahtjzg73cgoevxp4q +zener.i2p,mcbyglflte3dhwhqyafsfpnqtcapqkv2sepqd62wzd7fo2dzz4ca +zerobin.i2p,3564erslxzaoucqasxsjerk4jz2xril7j2cbzd4p7flpb4ut67hq +zeroman.i2p,gq77fmto535koofcd53f6yzcc5y57ccrxg3pb6twhcodc7v5dutq +zeronet.i2p,fe6pk5sibhkr64veqxkfochdfptehyxrrbs3edwjs5ckjbjn4bna +znc.i2p,uw2yt6njjl676fupd72hiezwmd4ouuywowrph6fvhkzhlnvp7jwa +znc.str4d.i2p,ufkajv3stxpxlwgwwb2ae6oixdjircnbwog77qxpxv7nt67rpcxq +zzz.i2p,ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq diff --git a/android/assets/certificates b/android/assets/certificates new file mode 120000 index 00000000..01b21e5d --- /dev/null +++ b/android/assets/certificates @@ -0,0 +1 @@ +../../contrib/certificates \ No newline at end of file diff --git a/android/assets/i2pd.conf b/android/assets/i2pd.conf new file mode 100644 index 00000000..312a24ea --- /dev/null +++ b/android/assets/i2pd.conf @@ -0,0 +1,90 @@ +## Configuration file for a typical i2pd user +## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ +## for more options you can use in this file. + +#logfile = /sdcard/i2pd/i2pd.log +loglevel = none +#tunnelsdir = /sdcard/i2pd/tunnels.d + +# host = 1.2.3.4 +# port = 4567 + +ipv4 = true +ipv6 = false + +# ntcp = true +# ntcpproxy = http://127.0.0.1:8118 +# ssu = true + +bandwidth = L +# share = 100 + +# notransit = true +# floodfill = true + +[ntcp2] +enabled = true + +[http] +enabled = true +address = 127.0.0.1 +port = 7070 +# auth = true +# user = i2pd +# pass = changeme + +[httpproxy] +enabled = true +address = 127.0.0.1 +port = 4444 +inbound.length = 1 +inbound.quantity = 5 +outbound.length = 1 +outbound.quantity = 5 +signaturetype=7 +keys = proxy-keys.dat +# addresshelper = true +# outproxy = http://false.i2p +## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. + +[socksproxy] +enabled = true +address = 127.0.0.1 +port = 4447 +keys = proxy-keys.dat +# outproxy.enabled = false +# outproxy = 127.0.0.1 +# outproxyport = 9050 +## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. + +[sam] +enabled = false +# address = 127.0.0.1 +# port = 7656 + +[precomputation] +elgamal = true + +[upnp] +enabled = true +# name = I2Pd + +[reseed] +verify = true +## Path to local reseed data file (.su3) for manual reseeding +# file = /path/to/i2pseeds.su3 +## or HTTPS URL to reseed from +# file = https://legit-website.com/i2pseeds.su3 +## Path to local ZIP file or HTTPS URL to reseed from +# zipfile = /path/to/netDb.zip +## If you run i2pd behind a proxy server, set proxy server for reseeding here +## Should be http://address:port or socks://address:port +# proxy = http://127.0.0.1:8118 +## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default +# threshold = 25 + +[limits] +transittunnels = 50 + +[persist] +profiles = false diff --git a/android/assets/subscriptions.txt b/android/assets/subscriptions.txt new file mode 100644 index 00000000..8f4afb03 --- /dev/null +++ b/android/assets/subscriptions.txt @@ -0,0 +1,3 @@ +http://inr.i2p/export/alive-hosts.txt +http://stats.i2p/cgi-bin/newhosts.txt +http://i2p-projekt.i2p/hosts.txt diff --git a/android/assets/tunnels.conf b/android/assets/tunnels.conf new file mode 100644 index 00000000..e95fdf2e --- /dev/null +++ b/android/assets/tunnels.conf @@ -0,0 +1,33 @@ +[IRC-IRC2P] +#type = client +#address = 127.0.0.1 +#port = 6668 +#destination = irc.postman.i2p +#destinationport = 6667 +#keys = irc-keys.dat + +#[IRC-ILITA] +#type = client +#address = 127.0.0.1 +#port = 6669 +#destination = irc.ilita.i2p +#destinationport = 6667 +#keys = irc-keys.dat + +#[SMTP] +#type = client +#address = 127.0.0.1 +#port = 7659 +#destination = smtp.postman.i2p +#destinationport = 25 +#keys = smtp-keys.dat + +#[POP3] +#type = client +#address = 127.0.0.1 +#port = 7660 +#destination = pop.postman.i2p +#destinationport = 110 +#keys = pop3-keys.dat + +# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ diff --git a/android/assets/tunnels.d b/android/assets/tunnels.d new file mode 120000 index 00000000..ff262031 --- /dev/null +++ b/android/assets/tunnels.d @@ -0,0 +1 @@ +../../contrib/tunnels.d \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..e68c160f --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,84 @@ +buildscript { + repositories { + mavenCentral() + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + } +} + +apply plugin: 'com.android.application' + +repositories { + jcenter() + maven { + url 'https://maven.google.com' + } +} + +dependencies { + implementation 'com.android.support:support-compat:28.0.0' +} + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + defaultConfig { + applicationId "org.purplei2p.i2pd" + targetSdkVersion 28 + minSdkVersion 14 + versionCode 2250 + versionName "2.25.0" + ndk { + abiFilters 'armeabi-v7a' + abiFilters 'x86' + //abiFilters 'arm64-v8a' + //abiFilters 'x86_64' + } + externalNativeBuild { + ndkBuild { + arguments "-j3" + } + } + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + jniLibs.srcDirs = ['libs'] + assets.srcDirs = ['assets'] + } + } + splits { + abi { + // change that to true if you need splitted apk + enable false + reset() + include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" + universalApk true + } + } + signingConfigs { + orignal { + storeFile file("i2pdapk.jks") + storePassword "android" + keyAlias "i2pdapk" + keyPassword "android" + } + } + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.orignal + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' + } + } + externalNativeBuild { + ndkBuild { + path './jni/Android.mk' + } + } +} diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 00000000..ed8196c3 --- /dev/null +++ b/android/build.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 00000000..af82e006 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.parallel=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0e4283d9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Mar 14 18:21:08 MSK 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100755 index 00000000..d44fcc5a --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,73 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := i2pd +LOCAL_CPP_FEATURES := rtti exceptions +LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) +LOCAL_STATIC_LIBRARIES := \ + boost_system \ + boost_date_time \ + boost_filesystem \ + boost_program_options \ + crypto ssl \ + miniupnpc +LOCAL_LDLIBS := -lz + +LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp $(IFADDRS_PATH)/ifaddrs.c \ + $(wildcard $(LIB_SRC_PATH)/*.cpp)\ + $(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp)\ + $(DAEMON_SRC_PATH)/Daemon.cpp \ + $(DAEMON_SRC_PATH)/UPnP.cpp \ + $(DAEMON_SRC_PATH)/HTTPServer.cpp \ + $(DAEMON_SRC_PATH)/I2PControl.cpp + +include $(BUILD_SHARED_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_system +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_68_0-clang/$(TARGET_ARCH_ABI)/lib/libboost_system.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_68_0-clang/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_date_time +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_68_0-clang/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_68_0-clang/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_filesystem +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_68_0-clang/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_68_0-clang/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_program_options +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_68_0-clang/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_68_0-clang/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include +LOCAL_STATIC_LIBRARIES := crypto +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := miniupnpc +LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnpc-2.1/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a +LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnpc-2.1/include +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100755 index 00000000..6d51ac1a --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,36 @@ +#APP_ABI := armeabi-v7a x86 +#APP_PLATFORM := android-14 + +# ABI arm64-v8a and x86_64 supported only from platform-21 +#APP_ABI := arm64-v8a x86_64 +#APP_PLATFORM := android-21 + +NDK_TOOLCHAIN_VERSION := clang +#APP_STL := c++_shared +APP_STL := c++_static + +# Enable c++11 extensions in source code +APP_CPPFLAGS += -std=c++11 -fexceptions -frtti + +APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +APP_CPPFLAGS += -DANDROID_ARM7A +endif + +# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/android-ifaddrs.git +# change to your own +I2PD_LIBS_PATH = /path/to/libraries +BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt +OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs + +# don't change me +I2PD_SRC_PATH = $(PWD)/.. + +LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd +LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client +DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp new file mode 100644 index 00000000..c3a1b805 --- /dev/null +++ b/android/jni/DaemonAndroid.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +//#include "mainwindow.h" +#include "FS.h" +#include "DaemonAndroid.h" +#include "Daemon.h" + +namespace i2p +{ +namespace android +{ +/* Worker::Worker (DaemonAndroidImpl& daemon): + m_Daemon (daemon) + { + } + + void Worker::startDaemon() + { + Log.d(TAG"Performing daemon start..."); + m_Daemon.start(); + Log.d(TAG"Daemon started."); + emit resultReady(); + } + void Worker::restartDaemon() + { + Log.d(TAG"Performing daemon restart..."); + m_Daemon.restart(); + Log.d(TAG"Daemon restarted."); + emit resultReady(); + } + void Worker::stopDaemon() { + Log.d(TAG"Performing daemon stop..."); + m_Daemon.stop(); + Log.d(TAG"Daemon stopped."); + emit resultReady(); + } + + Controller::Controller(DaemonAndroidImpl& daemon): + m_Daemon (daemon) + { + Worker *worker = new Worker (m_Daemon); + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); + connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); + connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); + connect(worker, &Worker::resultReady, this, &Controller::handleResults); + workerThread.start(); + } + Controller::~Controller() + { + Log.d(TAG"Closing and waiting for daemon worker thread..."); + workerThread.quit(); + workerThread.wait(); + Log.d(TAG"Waiting for daemon worker thread finished."); + if(m_Daemon.isRunning()) + { + Log.d(TAG"Stopping the daemon..."); + m_Daemon.stop(); + Log.d(TAG"Stopped the daemon."); + } + } +*/ + std::string dataDir = ""; + + DaemonAndroidImpl::DaemonAndroidImpl () + //: + /*mutex(nullptr), */ + //m_IsRunning(false), + //m_RunningChangedCallback(nullptr) + { + } + + DaemonAndroidImpl::~DaemonAndroidImpl () + { + //delete mutex; + } + + bool DaemonAndroidImpl::init(int argc, char* argv[]) + { + //mutex=new QMutex(QMutex::Recursive); + //setRunningCallback(0); + //m_IsRunning=false; + + // make sure assets are ready before proceed + i2p::fs::DetectDataDir(dataDir, false); + int numAttempts = 0; + do + { + if (i2p::fs::Exists (i2p::fs::DataDirPath("assets.ready"))) break; // assets ready + numAttempts++; + std::this_thread::sleep_for (std::chrono::seconds(1)); // otherwise wait for 1 more second + } + while (numAttempts <= 10); // 10 seconds max + return Daemon.init(argc,argv); + } + + void DaemonAndroidImpl::start() + { + //QMutexLocker locker(mutex); + //setRunning(true); + Daemon.start(); + } + + void DaemonAndroidImpl::stop() + { + //QMutexLocker locker(mutex); + Daemon.stop(); + //setRunning(false); + } + + void DaemonAndroidImpl::restart() + { + //QMutexLocker locker(mutex); + stop(); + start(); + } + /* + void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) + { + m_RunningChangedCallback = cb; + } + + bool DaemonAndroidImpl::isRunning() + { + return m_IsRunning; + } + + void DaemonAndroidImpl::setRunning(bool newValue) + { + bool oldValue = m_IsRunning; + if(oldValue!=newValue) + { + m_IsRunning = newValue; + if(m_RunningChangedCallback) + m_RunningChangedCallback(); + } + } +*/ + static DaemonAndroidImpl daemon; + static char* argv[1]={strdup("tmp")}; + /** + * returns error details if failed + * returns "ok" if daemon initialized and started okay + */ + std::string start(/*int argc, char* argv[]*/) + { + try + { + //int result; + + { + //Log.d(TAG"Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(1,argv); + if(!daemonInitSuccess) + { + //QMessageBox::critical(0, "Error", "Daemon init failed"); + return "Daemon init failed"; + } + //Log.d(TAG"Initialised, creating the main window..."); + //MainWindow w; + //Log.d(TAG"Before main window.show()..."); + //w.show (); + + { + //i2p::qt::Controller daemonQtController(daemon); + //Log.d(TAG"Starting the daemon..."); + //emit daemonQtController.startDaemon(); + //daemon.start (); + //Log.d(TAG"Starting GUI event loop..."); + //result = app.exec(); + //daemon.stop (); + daemon.start(); + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + //Log.d(TAG"Exiting the application"); + //return result; + } + catch (boost::exception& ex) + { + std::stringstream ss; + ss << boost::diagnostic_information(ex); + return ss.str(); + } + catch (std::exception& ex) + { + std::stringstream ss; + ss << ex.what(); + return ss.str(); + } + catch(...) + { + return "unknown exception"; + } + return "ok"; + } + + void stop() + { + daemon.stop(); + } + + void SetDataDir(std::string jdataDir) + { + dataDir = jdataDir; + } +} +} diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h new file mode 100644 index 00000000..64bf64fd --- /dev/null +++ b/android/jni/DaemonAndroid.h @@ -0,0 +1,89 @@ +#ifndef DAEMON_ANDROID_H +#define DAEMON_ANDROID_H + +#include + +namespace i2p +{ +namespace android +{ + class DaemonAndroidImpl + { + public: + + DaemonAndroidImpl (); + ~DaemonAndroidImpl (); + + //typedef void (*runningChangedCallback)(); + + /** + * @return success + */ + bool init(int argc, char* argv[]); + void start(); + void stop(); + void restart(); + //void setRunningCallback(runningChangedCallback cb); + //bool isRunning(); + private: + //void setRunning(bool running); + private: + //QMutex* mutex; + //bool m_IsRunning; + //runningChangedCallback m_RunningChangedCallback; + }; + + /** + * returns "ok" if daemon init failed + * returns errinfo if daemon initialized and started okay + */ + std::string start(); + + // stops the daemon + void stop(); + + // set datadir received from jni + void SetDataDir(std::string jdataDir); + /* + class Worker : public QObject + { + Q_OBJECT + public: + + Worker (DaemonAndroidImpl& daemon); + + private: + + DaemonAndroidImpl& m_Daemon; + + public slots: + void startDaemon(); + void restartDaemon(); + void stopDaemon(); + + signals: + void resultReady(); + }; + + class Controller : public QObject + { + Q_OBJECT + QThread workerThread; + public: + Controller(DaemonAndroidImpl& daemon); + ~Controller(); + private: + DaemonAndroidImpl& m_Daemon; + + public slots: + void handleResults(){} + signals: + void startDaemon(); + void stopDaemon(); + void restartDaemon(); + }; + */ +} +} + +#endif // DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp new file mode 100755 index 00000000..da908648 --- /dev/null +++ b/android/jni/i2pd_android.cpp @@ -0,0 +1,94 @@ +#include +#include "org_purplei2p_i2pd_I2PD_JNI.h" +#include "DaemonAndroid.h" +#include "RouterContext.h" +#include "Transports.h" + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith + (JNIEnv *env, jclass clazz) { +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif + #elif defined(__i386__) + #define ABI "x86" + #elif defined(__x86_64__) + #define ABI "x86_64" + #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ + #define ABI "mips64" + #elif defined(__mips__) + #define ABI "mips" + #elif defined(__aarch64__) + #define ABI "arm64-v8a" + #else + #define ABI "unknown" +#endif + + return env->NewStringUTF(ABI); +} + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon + (JNIEnv *env, jclass clazz) { + return env->NewStringUTF(i2p::android::start().c_str()); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon + (JNIEnv *env, jclass clazz) { + i2p::android::stop(); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels + (JNIEnv *env, jclass clazz) { + i2p::context.SetAcceptsTunnels (false); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels + (JNIEnv *env, jclass clazz) { + i2p::context.SetAcceptsTunnels (true); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged + (JNIEnv *env, jclass clazz, jboolean isConnected) { + bool isConnectedBool = (bool) isConnected; + i2p::transport::transports.SetOnline (isConnectedBool); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir + (JNIEnv *env, jclass clazz, jstring jdataDir) { + + /* + // Method 1: convert UTF-16 jstring to std::string (https://stackoverflow.com/a/41820336) + const jclass stringClass = env->GetObjectClass(jdataDir); + const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); + const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jdataDir, getBytes, env->NewStringUTF("UTF-8")); + + size_t length = (size_t) env->GetArrayLength(stringJbytes); + jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); + + std::string dataDir = std::string((char *)pBytes, length); + env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); + + env->DeleteLocalRef(stringJbytes); + env->DeleteLocalRef(stringClass); */ + + // Method 2: get string chars and make char array. + auto dataDir = env->GetStringUTFChars(jdataDir, NULL); + env->ReleaseStringUTFChars(jdataDir, dataDir); + + // Set DataDir + i2p::android::SetDataDir(dataDir); +} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h new file mode 100644 index 00000000..6939a153 --- /dev/null +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -0,0 +1,39 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_purplei2p_i2pd_I2PD_JNI */ + +#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI +#define _Included_org_purplei2p_i2pd_I2PD_JNI +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_purplei2p_i2pd_I2PD_JNI + * Method: stringFromJNI + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith + (JNIEnv *, jclass); + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged + (JNIEnv * env, jclass clazz, jboolean isConnected); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir + (JNIEnv *env, jclass clazz, jstring jdataDir); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/android/proguard-project.txt b/android/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/android/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/project.properties b/android/project.properties new file mode 100644 index 00000000..919ca9c3 --- /dev/null +++ b/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-28 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 00000000..9a2f7404 Binary files /dev/null and b/android/res/drawable/icon.png differ diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 00000000..fa99e7fc Binary files /dev/null and b/android/res/drawable/itoopie_notification_icon.png differ diff --git a/android/res/layout/activity_perms_asker.xml b/android/res/layout/activity_perms_asker.xml new file mode 100644 index 00000000..d2d12cb6 --- /dev/null +++ b/android/res/layout/activity_perms_asker.xml @@ -0,0 +1,27 @@ + + + + + \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"; - } - - if (dest->GetNumRemoteLeaseSets()) - { - s << "
\r\n\r\n
\r\n" - << "" - << "" - << "" // LeaseSet expiration button column - << "" - << "" - << ""; + s << "
\r\n\r\n

\r\n"; for(auto& it: dest->GetLeaseSets ()) - { - s << "

" - << "" - << "" - << "" - << "" - << "\r\n"; - } - s << "
" << tr("Address") << " " << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n
\r\n
\r\n
\r\n"; + s << it.first.ToBase32 () << " " << (int)it.second->GetStoreType () << "
\r\n"; + s << "

\r\n\r\n"; } else - s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; - + s << "LeaseSets: 0
\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { - s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; + s << "Inbound tunnels:
\r\n"; for (auto & it : pool->GetInboundTunnels ()) { - s << "
"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; - } - ); - } - s << "⇒ " << it->GetTunnelID () << ":me"; - if (it->LatencyIsKnown()) - s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; + it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); - s << "
\r\n"; } s << "
\r\n"; - s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; + s << "Outbound tunnels:
\r\n"; for (auto & it : pool->GetOutboundTunnels ()) { - s << "
"; - s << it->GetTunnelID () << ":me ⇒"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; - } - ); - } - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "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 << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
"; 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 (); + tmp_s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " " << it.second->GetNumOutgoingTags () << "
\r\n"; + out_tags = 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" << tmp_s.str () << "

\r\n
\r\n"; } else - s << tr("Outgoing") << ": 0
\r\n"; + s << "Outgoing: 0
\r\n"; s << "
\r\n"; - - auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); - if (numECIESx25519Tags > 0) { - s << "ECIESx25519
\r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
\r\n"; - if (!dest->GetECIESx25519Sessions ().empty ()) - { - std::stringstream tmp_s; uint32_t ecies_sessions = 0; - for (const auto& it: dest->GetECIESx25519Sessions ()) { - 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"; - } else - s << tr("Tags sessions") << ": 0
\r\n"; - s << "
\r\n"; - } } - void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) + void ShowLocalDestination (std::stringstream& s, const std::string& b32) { - s << "" << tr("Local Destination") << ":
\r\n
\r\n"; + s << "Local Destination:
\r\n
\r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); - if (dest) { - 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"; + ShowLeaseSetDestination (s, dest); + // show streams + s << "
" - << tr("Streams") - << "
StreamID DestinationSentReceivedOutInBufRTTWindowStatus
\r\n"; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << "\r\n"; for (const auto& it: dest->GetAllStreams ()) { - auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); - std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; s << ""; - s << ""; - if (it->GetRecvStreamID ()) { - s << ""; - } else { - s << ""; + s << ""; + s << ""; s << ""; s << ""; s << ""; @@ -618,313 +433,236 @@ namespace http { s << ""; s << "\r\n"; } - s << "\r\n
Streams
StreamIDDestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetRecvStreamID () << ""; - } - s << "" << streamDestShort << "" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << (int)it->GetStatus () << "
"; + s << ""; } - else - ShowError(s, tr("Such destination is not found")); } - void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) + static void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) { - s << "I2CP " << tr("Local Destination") << ":
\r\n
\r\n"; + s << "I2CP Local Destination:
\r\n
\r\n"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) - ShowLeaseSetDestination (s, it->second->GetDestination (), 0); + ShowLeaseSetDestination (s, it->second->GetDestination ()); else - ShowError(s, tr("I2CP session not found")); + ShowError(s, "I2CP session not found"); } else - ShowError(s, tr("I2CP is not enabled")); + ShowError(s, "I2CP is not enabled"); } void ShowLeasesSets(std::stringstream& s) { - if (i2p::data::netdb.GetNumLeaseSets ()) - { - s << "" << tr("LeaseSets") << ":
\r\n
\r\n"; - int counter = 1; - // for each lease set - i2p::data::netdb.VisitLeaseSets( - [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) - { - // create copy of lease set so we extract leases - auto storeType = leaseSet->GetStoreType (); - std::unique_ptr ls; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) - ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); - else + s << "LeaseSets:
\r\n
\r\n"; + int counter = 1; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + auto storeType = leaseSet->GetStoreType (); + std::unique_ptr ls; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) + ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + else + ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + if (!ls) return; + s << "
\r\n"; + if (!ls->IsValid()) + s << "
!! Invalid !!
\r\n"; + s << "
\r\n"; + s << "\r\n

\r\n"; + s << "Store type: " << (int)storeType << "
\r\n"; + s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
\r\n"; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) + { + // leases information is available + auto leases = ls->GetNonExpiredLeases(); + s << "Non Expired Leases: " << leases.size() << "
\r\n"; + for ( auto & l : leases ) { - ls.reset (new i2p::data::LeaseSet2 (storeType)); - ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), false); + s << "Gateway: " << l->tunnelGateway.ToBase64() << "
\r\n"; + s << "TunnelID: " << l->tunnelID << "
\r\n"; + s << "EndDate: " << ConvertTime(l->endDate) << "
\r\n"; } - if (!ls) return; - s << "

IsExpired()) - s << " expired"; // additional css class for expired - s << "\">\r\n"; - if (!ls->IsValid()) - s << "
!! " << tr("Invalid") << " !!
\r\n"; - s << "
\r\n"; - s << "\r\n
\r\n"; - s << "" << tr("Store type") << ": " << (int)storeType << "
\r\n"; - s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
\r\n"; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) - { - // leases information is available - auto leases = ls->GetNonExpiredLeases(); - s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
\r\n"; - for ( auto & l : leases ) - { - s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
\r\n"; - s << "" << tr("TunnelID") << ": " << l->tunnelID << "
\r\n"; - s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
\r\n"; - } - } - s << "
\r\n
\r\n
\r\n"; - } - ); - // end for each lease set - } - else if (!i2p::context.IsFloodfill ()) - { - s << "" << tr("LeaseSets") << ": " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".
\r\n"; - } - else - { - s << "" << tr("LeaseSets") << ": 0
\r\n"; - } + } + s << "

\r\n
\r\n
\r\n"; + } + ); + // end for each lease set } void ShowTunnels (std::stringstream& s) { - 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"; + s << "Tunnels:
\r\n
\r\n"; + s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); - s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; + s << "Inbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { - s << "
"; - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; - } - ); - } - s << "⇒ " << it->GetTunnelID () << ":me"; - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); - s << "
\r\n"; } - s << "
\r\n
\r\n"; - s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; + s << "
\r\n"; + s << "Outbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { - s << "
"; - s << it->GetTunnelID () << ":me ⇒"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; - } - ); - } - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); - s << "
\r\n"; } - s << "
\r\n"; + s << "
\r\n"; } static void ShowCommands (std::stringstream& s, uint32_t token) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - - s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; - s << " " << tr("Run peer test") << "
\r\n"; - s << " " << tr("Reload tunnels configuration") << "
\r\n"; - + /* commands */ + s << "Router Commands
\r\n
\r\n"; + s << " Run peer test
\r\n"; + //s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) - s << " " << tr("Decline transit tunnels") << "
\r\n"; + s << " Decline transit tunnels
\r\n"; else - s << " " << tr("Accept transit tunnels") << "
\r\n"; - + s << " Accept transit tunnels
\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " " << tr("Cancel graceful shutdown") << "
\r\n"; + s << " Cancel graceful shutdown
"; else - s << " " << tr("Start graceful shutdown") << "
\r\n"; + s << " Start graceful shutdown
\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " " << tr("Cancel graceful shutdown") << "
\r\n"; + s << " Cancel graceful shutdown
"; else - s << " " << tr("Start graceful shutdown") << "
\r\n"; + s << " Graceful shutdown
\r\n"; #endif + s << " Force shutdown
\r\n"; - s << " " << tr("Force shutdown") << "

\r\n"; - s << " " << tr("Reload external CSS styles") << "\r\n"; - s << "
"; - - 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"; - - uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels (); - s << "" << tr("Transit tunnels limit") << "
\r\n"; - s << "
\r\n"; - s << " \r\n"; - s << " \r\n"; - s << " \r\n"; - s << " \r\n"; - s << "
\r\n
\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" - << " \r\n" - << "
\r\n
\r\n"; - + s << "
\r\nLogging level
\r\n"; + s << " [none] "; + s << " [error] "; + s << " [warn] "; + s << " [info] "; + s << " [debug]
\r\n"; } void ShowTransitTunnels (std::stringstream& s) { - if (i2p::tunnel::tunnels.CountTransitTunnels()) + s << "Transit tunnels:
\r\n
\r\n"; + for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { - s << "" << tr("Transit Tunnels") << ":
\r\n"; - s << ""; - for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) - { - if (std::dynamic_pointer_cast(it)) - s << "\r\n"; - } - s << "
ID" << tr("Amount") << "" << tr("Next") << "
" << it->GetTunnelID () << ""; - else if (std::dynamic_pointer_cast(it)) - s << "
" << it->GetTunnelID () << ""; - else - s << "
" << it->GetTunnelID () << ""; - ShowTraffic(s, it->GetNumTransmittedBytes ()); - s << "" << it->GetNextPeerName () << "
\r\n"; - } - else - { - s << "" << tr("Transit Tunnels") << ": " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".
\r\n"; + if (std::dynamic_pointer_cast(it)) + s << it->GetTunnelID () << " ⇒ "; + else if (std::dynamic_pointer_cast(it)) + s << " ⇒ " << it->GetTunnelID (); + else + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " " << it->GetNumTransmittedBytes () << "
\r\n"; } } template - static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) + static void ShowNTCPTransports (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->GetSocket ().remote_endpoint ().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]"; - tmp_s << "
\r\n" << std::endl; + // incoming connection doesn't have remote RI + if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; + tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << it.second->GetSocket ().remote_endpoint().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->GetSocket ().remote_endpoint ().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 () << "]"; - tmp_s6 << "
\r\n" << std::endl; + if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << "[" << it.second->GetSocket ().remote_endpoint().address ().to_string () << "]"; + if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; + tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + tmp_s6 << "
\r\n" << std::endl; cnt6++; } } if (!tmp_s.str ().empty ()) { - s << "
\r\n\r\n
" - << tmp_s.str () << "
\r\n
\r\n"; + s << "
\r\n\r\n

"; + s << tmp_s.str () << "

\r\n
\r\n"; } if (!tmp_s6.str ().empty ()) { - s << "
\r\n\r\n
" - << tmp_s6.str () << "
\r\n
\r\n"; + s << "
\r\n\r\n

"; + s << tmp_s6.str () << "

\r\n
\r\n"; } } void ShowTransports (std::stringstream& s) { - s << "" << tr("Transports") << ":
\r\n"; + s << "Transports:
\r\n
\r\n"; + auto ntcpServer = i2p::transport::transports.GetNTCPServer (); + if (ntcpServer) + { + auto sessions = ntcpServer->GetNTCPSessions (); + if (!sessions.empty ()) + ShowNTCPTransports (s, sessions, "NTCP"); + } auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { auto sessions = ntcp2Server->GetNTCP2Sessions (); if (!sessions.empty ()) - ShowTransportSessions (s, sessions, "NTCP2"); + ShowNTCPTransports (s, sessions, "NTCP2"); } - auto ssu2Server = i2p::transport::transports.GetSSU2Server (); - if (ssu2Server) + auto ssuServer = i2p::transport::transports.GetSSUServer (); + if (ssuServer) { - auto sessions = ssu2Server->GetSSU2Sessions (); + auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) - ShowTransportSessions (s, sessions, "SSU2"); + { + s << "
\r\n\r\n

"; + for (const auto& it: sessions) + { + 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) + { + 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"; + } } } @@ -932,150 +670,133 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) - { - ShowError(s, tr("SAM disabled")); + if (!sam) { + ShowError(s, "SAM disabled"); return; } - - if (sam->GetSessions ().size ()) + s << "SAM Sessions:
\r\n
\r\n"; + for (auto& it: sam->GetSessions ()) { - s << "" << tr("SAM sessions") << ":
\r\n
\r\n"; - for (auto& it: sam->GetSessions ()) - { - auto& name = it.second->GetLocalDestination ()->GetNickname (); - s << "\r\n" << std::endl; - } - s << "
\r\n"; + auto& name = it.second->localDestination->GetNickname (); + s << ""; + s << name << " (" << it.first << ")
\r\n" << std::endl; } - else - s << "" << tr("SAM sessions") << ": " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".
\r\n"; } - void ShowSAMSession (std::stringstream& s, const std::string& id) + static void ShowSAMSession (std::stringstream& s, const std::string& id) { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "SAM Session:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, tr("SAM disabled")); + ShowError(s, "SAM disabled"); return; } - auto session = sam->FindSession (id); if (!session) { - ShowError(s, tr("SAM session not found")); + ShowError(s, "SAM session not found"); return; } - - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("SAM Session") << ":
\r\n
\r\n"; - auto& ident = session->GetLocalDestination ()->GetIdentHash(); - s << "\r\n"; + auto& ident = session->localDestination->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n"; s << "
\r\n"; - s << "" << tr("Streams") << ":
\r\n
\r\n"; + s << "Streams:
\r\n"; for (const auto& it: sam->ListSockets(id)) { - s << "
"; switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; - case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
\r\n"; + s << "
\r\n"; } - s << "
\r\n"; } 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 << "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 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 Proxy" << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; } - auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { - s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; + s << "
\r\nServer Tunnels:
\r\n
\r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; + s << ""; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); - s << "
\r\n"<< std::endl; + s << "
\r\n"<< std::endl; } - s << "
\r\n"; } - auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { - s << "
\r\n" << tr("Client Forwards") << ":
\r\n
\r\n"; + s << "
\r\nClient Forwards:
\r\n
\r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; + s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; + s << "
\r\n"<< std::endl; } - s << "
\r\n"; } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { - s << "
\r\n" << tr("Server Forwards") << ":
\r\n
\r\n"; + s << "
\r\nServer Forwards:
\r\n
\r\n"; 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; + s << "
\r\n"<< std::endl; } - s << "
\r\n"; } } + std::string ConvertTime (uint64_t time) + { + ldiv_t divTime = ldiv(time,1000); + time_t t = divTime.quot; + struct tm *tm = localtime(&t); + char date[128]; + snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03ld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); + return date; + } + HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): m_Socket (socket), m_BufferLen (0), expected_host(hostname) { @@ -1088,8 +809,8 @@ namespace http { void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), - std::bind(&HTTPConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&HTTPConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -1129,8 +850,7 @@ namespace http { m_Socket->close (); } - bool HTTPConnection::CheckAuth (const HTTPReq & req) - { + bool HTTPConnection::CheckAuth (const HTTPReq & req) { /* method #1: http://user:pass@127.0.0.1:7070/ */ if (req.uri.find('@') != std::string::npos) { URL url; @@ -1145,7 +865,7 @@ namespace http { if (expected == provided) return true; } - LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); + LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); return false; } @@ -1155,7 +875,7 @@ namespace http { std::string content; HTTPRes res; - LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); + LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); if (needAuth && !CheckAuth(req)) { res.code = 401; @@ -1163,7 +883,6 @@ namespace http { SendReply(res, content); return; } - bool strictheaders; i2p::config::GetOption("http.strictheaders", strictheaders); if (strictheaders) @@ -1186,15 +905,14 @@ namespace http { return; } } - - // HTML head start + // Html5 head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); } else { - ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); + ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); res.add_header("Refresh", "10"); } ShowPageTail (s); @@ -1205,24 +923,6 @@ namespace http { } std::map HTTPConnection::m_Tokens; - - uint32_t HTTPConnection::CreateToken () - { - uint32_t token; - RAND_bytes ((uint8_t *)&token, 4); - token &= 0x7FFFFFFF; // clear first bit - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) - { - if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) - it = m_Tokens.erase (it); - else - ++it; - } - m_Tokens[token] = ts; - return token; - } - void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; @@ -1239,7 +939,18 @@ namespace http { ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { - uint32_t token = CreateToken (); + uint32_t token; + RAND_bytes ((uint8_t *)&token, 4); + token &= 0x7FFFFFFF; // clear first bit + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) + { + if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) + it = m_Tokens.erase (it); + else + ++it; + } + m_Tokens[token] = ts; ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) @@ -1247,10 +958,7 @@ namespace http { else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (page == HTTP_PAGE_LOCAL_DESTINATION) - { - uint32_t token = CreateToken (); - ShowLocalDestination (s, params["b32"], token); - } + ShowLocalDestination (s, params["b32"]); else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) ShowI2CPLocalDestination (s, params["i2cp_id"]); else if (page == HTTP_PAGE_SAM_SESSIONS) @@ -1263,7 +971,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO + ShowError(s, "Unknown page: " + page); return; } } @@ -1276,216 +984,72 @@ namespace http { url.parse(req.uri); 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 token = params["token"]; - if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { - ShowError(s, tr("Invalid token")); + ShowError(s, "Invalid token"); return; } 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); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); - else if (cmd == HTTP_COMMAND_SHUTDOWN_START) - { + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 10*60; #elif defined(WIN32_APP) i2p::win32::GracefulShutdown (); #endif - } - else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) - { + } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) +#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); #endif - } - else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) - { + } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif - } - else if (cmd == HTTP_COMMAND_LOGLEVEL) - { + } else if (cmd == HTTP_COMMAND_LOGLEVEL){ std::string level = params["level"]; SetLogLevel (level); - } - else if (cmd == HTTP_COMMAND_KILLSTREAM) - { - std::string b32 = params["b32"]; - uint32_t streamID = std::stoul(params["streamID"], nullptr); - - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (streamID) - { - if (dest) - { - if (dest->DeleteStream (streamID)) - s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
\r\n
\r\n"; - else - s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; - } - else - 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; - 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); - else { - s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n
\r\n
\r\n"; - s << "" << tr("Back to commands list") << "\r\n
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - res.add_header("Refresh", redirect.c_str()); - return; - } - } - else if (cmd == HTTP_COMMAND_GET_REG_STRING) - { - std::string b32 = params["b32"]; - std::string name = i2p::http::UrlDecode(params["name"]); - - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (dest) - { - std::size_t pos; - pos = name.find (".i2p"); - if (pos == (name.length () - 4)) - { - pos = name.find (".b32.i2p"); - if (pos == std::string::npos) - { - auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); - uint8_t * signature = new uint8_t[signatureLen]; - 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); - out << "#!sig=" << sig; - s << "" << tr("SUCCESS") << ":
\r\n

\r\n" - "\r\n
\r\n
\r\n" - "" << tr("Register at reg.i2p") << ":\r\n
\r\n" - "" << tr("Description") << ":\r\n\r\n" - "\r\n" - "
\r\n
\r\n"; - delete[] signature; - } - else - s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
\r\n
\r\n"; - - s << "" << tr("Return to destination page") << "\r\n"; - return; - } - else if (cmd == HTTP_COMMAND_SETLANGUAGE) - { - std::string lang = params["lang"]; - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); - - if (currLang.compare(lang) != 0) - i2p::i18n::SetLanguage(lang); - } - else if (cmd == HTTP_COMMAND_RELOAD_CSS) - { - LoadExtCSS(); - } - else - { + } else { res.code = 400; - ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO + ShowError(s, "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) << ""; + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string redirect = "5; url=" + webroot + "?page=commands"; + s << "SUCCESS: Command accepted

\r\n"; + s << "Back to commands list
\r\n"; + s << "

You will be redirected in 5 seconds"; res.add_header("Refresh", redirect.c_str()); } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { reply.add_header("X-Frame-Options", "SAMEORIGIN"); - reply.add_header("X-Content-Type-Options", "nosniff"); - reply.add_header("X-XSS-Protection", "1; mode=block"); reply.add_header("Content-Type", "text/html"); 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) { } @@ -1512,27 +1076,18 @@ namespace http { pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); - LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); + LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } - m_IsRunning = true; - m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); + m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); - - LoadExtCSS(); } void HTTPServer::Stop () { m_IsRunning = false; - - boost::system::error_code ec; - m_Acceptor.cancel(ec); - if (ec) - LogPrint (eLogDebug, "HTTPServer: Error while cancelling operations on acceptor: ", ec.message ()); m_Acceptor.close(); - m_Service.stop (); if (m_Thread) { @@ -1543,8 +1098,6 @@ namespace http { void HTTPServer::Run () { - i2p::util::SetThreadName("Webconsole"); - while (m_IsRunning) { try @@ -1553,7 +1106,7 @@ namespace http { } catch (std::exception& ex) { - LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); } } } @@ -1561,20 +1114,22 @@ namespace http { void HTTPServer::Accept () { auto newSocket = std::make_shared (m_Service); - m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, - std::placeholders::_1, newSocket)); + m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this, + boost::asio::placeholders::error, newSocket)); } void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { - if (!ecode) - CreateConnection(newSocket); - else + if (ecode) { - if (newSocket) newSocket->close(); - LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); + if(newSocket) newSocket->close(); + LogPrint(eLogError, "HTTP Server: error handling accept ", ecode.message()); + if(ecode != boost::asio::error::operation_aborted) + Accept(); + return; } + CreateConnection(newSocket); Accept (); } diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 38b790d4..3d32ed2b 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ @@ -24,8 +16,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 { @@ -45,7 +35,6 @@ namespace http void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void SendReply (HTTPRes & res, std::string & content); - uint32_t CreateToken (); private: @@ -83,25 +72,23 @@ 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; }; - //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml - enum OutputFormatEnum { forWebConsole, forQtUi }; - void ShowStatus (std::stringstream& s, bool includeHiddenContent, OutputFormatEnum outputFormat); - void ShowLocalDestinations (std::stringstream& s); - void ShowLeasesSets(std::stringstream& s); - void ShowTunnels (std::stringstream& s); - void ShowTransitTunnels (std::stringstream& s); - void ShowTransports (std::stringstream& s); - void ShowSAMSessions (std::stringstream& s); - void ShowI2PTunnels (std::stringstream& s); - void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); - void ShowSAMSession (std::stringstream& s, const std::string& id); - void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id); + //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml + enum OutputFormatEnum { forWebConsole, forQtUi }; + void ShowStatus (std::stringstream& s, bool includeHiddenContent, OutputFormatEnum outputFormat); + void ShowLocalDestinations (std::stringstream& s); + void ShowLeasesSets(std::stringstream& s); + void ShowTunnels (std::stringstream& s); + void ShowTransitTunnels (std::stringstream& s); + void ShowTransports (std::stringstream& s); + void ShowSAMSessions (std::stringstream& s); + void ShowI2PTunnels (std::stringstream& s); + void ShowLocalDestination (std::stringstream& s, const std::string& b32); } // http } // i2p diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h deleted file mode 100644 index 1e5b6f75..00000000 --- a/daemon/HTTPServerResources.h +++ /dev/null @@ -1,97 +0,0 @@ -/* -* Copyright (c) 2013-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 HTTP_SERVER_RESOURCES_H__ -#define HTTP_SERVER_RESOURCES_H__ - -namespace i2p -{ -namespace http -{ - const std::string itoopieFavicon = - "data:image/png;base64," - "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" - "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" - "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" - "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" - "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" - "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" - "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" - "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" - "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" - "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" - "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" - "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" - "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" - "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" - "RU5ErkJggg=="; - - // bundled style sheet - const std::string internalCSS = - "\r\n"; - - // for external style sheet - std::string externalCSS; - -} // http -} // i2p - -#endif /* HTTP_SERVER_RESOURCES_H__ */ diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 6261a14c..572292ee 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,27 +1,31 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include -#include #include #include +#include +#include +#include +#include -// Use global placeholders from boost introduced when local_time.hpp is loaded -#define BOOST_BIND_GLOBAL_PLACEHOLDERS +// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) +#if !GCC47_BOOST149 #include +#endif +#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 +33,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 +48,59 @@ 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)) - { - LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection"); + 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 - LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt); + } 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["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlHandlers::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlHandlers::ClientServicesInfoHandler; + m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; + m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; + 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 +114,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 +123,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; } } @@ -139,74 +136,52 @@ namespace client void I2PControlService::Run () { - i2p::util::SetThreadName("I2PC"); - while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { - LogPrint (eLogError, "I2PControl: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ()); } } } 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) - { - LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); + 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,20 +191,17 @@ 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) { if (ecode) { - LogPrint (eLogError, "I2PControl: Read error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); return; } else @@ -252,7 +224,7 @@ namespace client } if (ss.eof ()) { - LogPrint (eLogError, "I2PControl: Malformed request, HTTP header expected"); + LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read @@ -263,6 +235,12 @@ namespace client } } std::ostringstream response; +#if GCC47_BOOST149 + LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); + response << "{\"id\":null,\"error\":"; + response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; + response << "\"jsonrpc\":\"2.0\"}"; +#else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); @@ -277,16 +255,17 @@ namespace client } else { - LogPrint (eLogWarning, "I2PControl: Unknown method ", method); + LogPrint (eLogWarning, "I2PControl: unknown method ", method); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; } +#endif SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) { - LogPrint (eLogError, "I2PControl: Exception when handle request: ", ex.what ()); + LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); std::ostringstream response; response << "{\"id\":null,\"error\":"; response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; @@ -295,12 +274,37 @@ namespace client } catch (...) { - LogPrint (eLogError, "I2PControl: Handle request unknown exception"); + LogPrint (eLogError, "I2PControl: handle request unknown exception"); } } } - 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) const + { + ss << "\"" << name << "\":"; + if (value.length () > 0) + 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 +314,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 +327,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 @@ -376,11 +385,94 @@ namespace client void I2PControlService::PasswordHandler (const std::string& value) { - LogPrint (eLogWarning, "I2PControl: New password=", value, ", to make it persistent you should update your config!"); + LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } +// RouterInfo + + void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + 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 (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); + } + } + + void I2PControlService::UptimeHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); + } + + 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 +480,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); @@ -407,7 +498,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -421,7 +512,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -433,6 +524,37 @@ 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) { @@ -455,15 +577,15 @@ namespace client X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves - X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA + X509_sign (x509, pkey, EVP_sha1 ()); // sign // save cert if ((f = fopen (crt_path, "wb")) != NULL) { - LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); + 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)); + LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno)); } // save key @@ -472,14 +594,187 @@ namespace client PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { - LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); + LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno)); } X509_free (x509); } else { - LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); + 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 (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); + auto it1 = m_ClientServicesInfoHandlers.find (it->first); + if (it1 != m_ClientServicesInfoHandlers.end ()) + { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); + } + } + + void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + boost::property_tree::ptree client_tunnels, server_tunnels; + + for (auto& it: i2p::client::context.GetClientTunnels ()) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + + auto& serverTunnels = i2p::client::context.GetServerTunnels (); + if (!serverTunnels.empty ()) { + for (auto& it: serverTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + st.put("port", it.second->GetLocalPort ()); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + } + + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + pt.add_child("client", client_tunnels); + pt.add_child("server", server_tunnels); + + InsertParam (results, "I2PTunnel", pt); + } + + void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto httpProxy = i2p::client::context.GetHttpProxy (); + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "HTTPProxy", pt); + } + + void I2PControlService::SOCKSInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto socksProxy = i2p::client::context.GetSocksProxy (); + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "SOCKS", pt); + } + + void I2PControlService::SAMInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + pt.put("enabled", true); + boost::property_tree::ptree sam_sessions; + for (auto& it: sam->GetSessions ()) + { + boost::property_tree::ptree sam_session, sam_session_sockets; + auto& name = it.second->localDestination->GetNickname (); + auto& ident = it.second->localDestination->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..3233ad12 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,11 +1,3 @@ -/* -* 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 I2P_CONTROL_H__ #define I2P_CONTROL_H__ @@ -20,7 +12,6 @@ #include #include #include -#include "I2PControlHandlers.h" namespace i2p { @@ -33,8 +24,9 @@ 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 +39,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) 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 f3ea7f61..00000000 --- a/daemon/I2PControlHandlers.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* -* Copyright (c) 2013-2022, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#define BOOST_BIND_GLOBAL_PLACEHOLDERS -#include -#include - -#include "Log.h" -#include "RouterContext.h" -#include "NetDb.hpp" -#include "Tunnel.h" -#include "Transports.h" -#include "version.h" -#include "ClientContext.h" -#include "I2PControlHandlers.h" - -namespace i2p -{ -namespace client -{ - I2PControlHandlers::I2PControlHandlers () - { - // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlHandlers::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlHandlers::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlHandlers::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlHandlers::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlHandlers::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlHandlers::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"] = &I2PControlHandlers::InboundBandwidth15S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"] = &I2PControlHandlers::OutboundBandwidth15S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlHandlers::NetStatusHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlHandlers::TunnelsSuccessRateHandler; - m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlHandlers::NetTotalReceivedBytes; - m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlHandlers::NetTotalSentBytes; - - // NetworkSetting - m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlHandlers::InboundBandwidthLimit; - m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlHandlers::OutboundBandwidthLimit; - - // ClientServicesInfo - m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlHandlers::I2PTunnelInfoHandler; - m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlHandlers::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlHandlers::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlHandlers::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlHandlers::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlHandlers::I2CPInfoHandler; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, int value) const - { - ss << "\"" << name << "\":" << value; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const - { - ss << "\"" << name << "\":"; - if (value.length () > 0) - { - if (quotes) - ss << "\"" << value << "\""; - else - ss << value; - } - else - ss << "null"; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, double value) const - { - ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } - - void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const - { - std::ostringstream buf; - boost::property_tree::write_json (buf, value, false); - ss << "\"" << name << "\":" << buf.str(); - } - -// RouterInfo - - void I2PControlHandlers::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - bool first = true; - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); - auto it1 = m_RouterInfoHandlers.find (it->first); - if (it1 != m_RouterInfoHandlers.end ()) - { - if (!first) results << ","; - else first = false; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); - } - } - - void I2PControlHandlers::UptimeHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); - } - - void I2PControlHandlers::VersionHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.version", VERSION); - } - - void I2PControlHandlers::StatusHandler (std::ostringstream& results) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); - } - - void I2PControlHandlers::NetDbKnownPeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); - } - - void I2PControlHandlers::NetDbActivePeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); - } - - void I2PControlHandlers::NetStatusHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); - } - - void I2PControlHandlers::TunnelsParticipatingHandler (std::ostringstream& results) - { - int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); - InsertParam (results, "i2p.router.net.tunnels.participating", transit); - } - - void I2PControlHandlers::TunnelsSuccessRateHandler (std::ostringstream& results) - { - int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); - InsertParam (results, "i2p.router.net.tunnels.successrate", rate); - } - - void I2PControlHandlers::InboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth (); - InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); - } - - void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth15s (); - InsertParam (results, "i2p.router.net.bw.inbound.15s", bw); - } - - void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth (); - InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); - } - - void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth15s (); - InsertParam (results, "i2p.router.net.bw.outbound.15s", bw); - } - - void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); - } - - void I2PControlHandlers::NetTotalSentBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); - } - -// network setting - void I2PControlHandlers::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); - auto it1 = m_NetworkSettingHandlers.find (it->first); - if (it1 != m_NetworkSettingHandlers.end ()) { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(it->second.data (), results); - } else - LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); - } - } - - void I2PControlHandlers::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.in", bw); - } - - void I2PControlHandlers::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.out", bw); - } - -// ClientServicesInfo - - void I2PControlHandlers::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); - auto it1 = m_ClientServicesInfoHandlers.find (it->first); - if (it1 != m_ClientServicesInfoHandlers.end ()) - { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); - } - } - - void I2PControlHandlers::I2PTunnelInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - boost::property_tree::ptree client_tunnels, server_tunnels; - - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - - auto& serverTunnels = i2p::client::context.GetServerTunnels (); - if (!serverTunnels.empty ()) { - for (auto& it: serverTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - st.put("port", it.second->GetLocalPort ()); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - auto& clientForwards = i2p::client::context.GetClientForwards (); - if (!clientForwards.empty ()) - { - for (auto& it: clientForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - } - - auto& serverForwards = i2p::client::context.GetServerForwards (); - if (!serverForwards.empty ()) - { - for (auto& it: serverForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - pt.add_child("client", client_tunnels); - pt.add_child("server", server_tunnels); - - InsertParam (results, "I2PTunnel", pt); - } - - void I2PControlHandlers::HTTPProxyInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "HTTPProxy", pt); - } - - void I2PControlHandlers::SOCKSInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) - { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "SOCKS", pt); - } - - void I2PControlHandlers::SAMInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - pt.put("enabled", true); - boost::property_tree::ptree sam_sessions; - for (auto& it: sam->GetSessions ()) - { - boost::property_tree::ptree sam_session, sam_session_sockets; - auto& name = it.second->GetLocalDestination ()->GetNickname (); - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - sam_session.put("name", name); - sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - - for (const auto& socket: sam->ListSockets(it.first)) - { - boost::property_tree::ptree stream; - stream.put("type", socket->GetSocketType ()); - stream.put("peer", socket->GetSocket ().remote_endpoint()); - - sam_session_sockets.push_back(std::make_pair("", stream)); - } - sam_session.add_child("sockets", sam_session_sockets); - sam_sessions.add_child(it.first, sam_session); - } - - pt.add_child("sessions", sam_sessions); - } - else - pt.put("enabled", false); - - InsertParam (results, "SAM", pt); - } - - void I2PControlHandlers::BOBInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto bob = i2p::client::context.GetBOBCommandChannel (); - if (bob) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "BOB", pt); - } - - void I2PControlHandlers::I2CPInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto i2cp = i2p::client::context.GetI2CPServer (); - if (i2cp) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "I2CP", pt); - } -} -} diff --git a/daemon/I2PControlHandlers.h b/daemon/I2PControlHandlers.h 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..3aad19c6 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,15 +1,11 @@ -/* -* 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 + #include "Log.h" #include "RouterContext.h" @@ -34,7 +30,7 @@ namespace transport { if (m_IsRunning) { - LogPrint(eLogInfo, "UPnP: Stopping"); + LogPrint(eLogInfo, "UPnP: stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); @@ -51,8 +47,8 @@ namespace transport void UPnP::Start() { m_IsRunning = true; - LogPrint(eLogInfo, "UPnP: Starting"); - boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); + LogPrint(eLogInfo, "UPnP: starting"); + 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 @@ -65,8 +61,6 @@ namespace transport void UPnP::Run () { - i2p::util::SetThreadName("UPnP"); - while (m_IsRunning) { try @@ -77,7 +71,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "UPnP: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); PortMapping (); } } @@ -85,189 +79,127 @@ namespace transport void UPnP::Discover () { - bool isError; - int err; - -#if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNPDISCOVER_SUCCESS; - -#if (MINIUPNPC_API_VERSION >= 14) - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); +#if MINIUPNPC_API_VERSION >= 14 + int nerror = 0; + m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 2, &nerror); +#elif ( MINIUPNPC_API_VERSION >= 8 || defined(UPNPDISCOVER_SUCCESS) ) + int nerror = 0; + m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); #else - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, &err); + m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0); #endif - - isError = err != UPNPDISCOVER_SUCCESS; -#else // MINIUPNPC_API_VERSION >= 8 - err = 0; - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); - isError = m_Devlist == NULL; -#endif // MINIUPNPC_API_VERSION >= 8 { - // notify starting thread + // notify satrting thread std::unique_lock l(m_StartedMutex); m_Started.notify_all (); } - if (isError) + int r; + r = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); + if (r == 1) { - LogPrint (eLogError, "UPnP: Unable to discover Internet Gateway Devices: error ", err); - 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) + r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) { - LogPrint (eLogError, "UPnP: Unable to get external address: error ", err); + LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress() returned ", r); return; } else -#endif { - LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { - LogPrint (eLogError, "UPnP: Found Internet Gateway Device doesn't know our external address"); + LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed."); return; } } } else { - LogPrint (eLogError, "UPnP: Unable to find valid Internet Gateway Device: error ", err); + LogPrint (eLogError, "UPnP: GetValidIGD() failed."); return; } // 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 (); } - int UPnP::CheckMapping (const char* port, const char* type) - { - int err = UPNPCOMMAND_SUCCESS; - -#if (MINIUPNPC_API_VERSION >= 10) - err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL, NULL); -#elif ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL); -#else - err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL); -#endif - return err; - } - 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 (); }); + + } + + void UPnP::CloseMapping () + { + const auto& a = context.GetRouterInfo().GetAddresses(); + for (const auto& address : a) + { + if (!address->host.is_v6 () && address->port) + CloseMapping (address); + } } void UPnP::TryPortMapping (std::shared_ptr address) { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); + int r; std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); - int err = UPNPCOMMAND_SUCCESS; - - // check for existing mapping - err = CheckMapping (strPort.c_str (), strType.c_str ()); - if (err != UPNPCOMMAND_SUCCESS) // if mapping not found - { - LogPrint (eLogDebug, "UPnP: Port ", strPort, " is possibly not forwarded: return code ", err); - -#if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); +#ifdef UPNPDISCOVER_SUCCESS + r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); #else - err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL); + r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); #endif - if (err != UPNPCOMMAND_SUCCESS) - { - LogPrint (eLogError, "UPnP: Port forwarding to ", m_NetworkAddr, ":", strPort, " failed: return code ", err); - return; - } - else - { - LogPrint (eLogInfo, "UPnP: Port successfully forwarded (", m_externalIPAddress ,":", strPort, " type ", strType, " -> ", m_NetworkAddr ,":", strPort ,")"); - return; - } + if (r!=UPNPCOMMAND_SUCCESS) + { + LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") failed with code ", r); + return; } else { - LogPrint (eLogDebug, "UPnP: External forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); + LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")"); return; } } - void UPnP::CloseMapping () - { - auto a = context.GetRouterInfo().GetAddresses(); - if (!a) return; - for (const auto& address : *a) - { - if (address && !address->host.is_v6 () && address->port) - CloseMapping (address); - } - } - void UPnP::CloseMapping (std::shared_ptr address) { - if(!m_upnpUrlsInitialized) { - return; - } std::string strType (GetProto (address)), strPort (std::to_string (address->port)); - int err = UPNPCOMMAND_SUCCESS; - - err = CheckMapping (strPort.c_str (), strType.c_str ()); - if (err == UPNPCOMMAND_SUCCESS) - { - err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); - LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); - } + int r = 0; + r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); + LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r); } void UPnP::Close () { freeUPNPDevlist (m_Devlist); m_Devlist = 0; - if(m_upnpUrlsInitialized){ - FreeUPNPUrls (&m_upnpUrls); - m_upnpUrlsInitialized=false; - } + FreeUPNPUrls (&m_upnpUrls); } std::string UPnP::GetProto (std::shared_ptr address) { switch (address->transportStyle) { - case i2p::data::RouterInfo::eTransportNTCP2: - return "TCP"; - break; - case i2p::data::RouterInfo::eTransportSSU2: + case i2p::data::RouterInfo::eTransportNTCP: + return "TCP"; + break; + case i2p::data::RouterInfo::eTransportSSU: default: - return "UDP"; + return "UDP"; } } } diff --git a/daemon/UPnP.h b/daemon/UPnP.h index 2a5fe9f3..5313a1c4 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef __UPNP_H__ #define __UPNP_H__ @@ -27,72 +19,60 @@ 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, - UPNP_IGD_VALID_CONNECTED = 1, - UPNP_IGD_VALID_NOT_CONNECTED = 2, - UPNP_IGD_INVALID = 3 - }; - class UPnP { - public: + public: - UPnP (); - ~UPnP (); - void Close (); + UPnP (); + ~UPnP (); + void Close (); - void Start (); - void Stop (); + void Start (); + void Stop (); - private: + private: - void Discover (); - int CheckMapping (const char* port, const char* type); - void PortMapping (); - void TryPortMapping (std::shared_ptr address); - void CloseMapping (); - void CloseMapping (std::shared_ptr address); + void Discover (); + void PortMapping (); + void TryPortMapping (std::shared_ptr address); + void CloseMapping (); + void CloseMapping (std::shared_ptr address); - void Run (); - std::string GetProto (std::shared_ptr address); + void Run (); + std::string GetProto (std::shared_ptr address); - private: + private: - bool m_IsRunning; - std::unique_ptr m_Thread; - std::condition_variable m_Started; - std::mutex m_StartedMutex; - boost::asio::io_context m_Service; - boost::asio::deadline_timer m_Timer; - bool m_upnpUrlsInitialized = false; - struct UPNPUrls m_upnpUrls; - struct IGDdatas m_upnpData; + bool m_IsRunning; + std::unique_ptr m_Thread; + std::condition_variable m_Started; + std::mutex m_StartedMutex; + boost::asio::io_service m_Service; + boost::asio::deadline_timer m_Timer; + struct UPNPUrls m_upnpUrls; + struct IGDdatas m_upnpData; - // For miniupnpc - struct UPNPDev * m_Devlist = 0; - char m_NetworkAddr[64]; - char m_externalIPAddress[40]; + // For miniupnpc + char * m_MulticastIf = 0; + char * m_Minissdpdpath = 0; + struct UPNPDev * m_Devlist = 0; + char m_NetworkAddr[64]; + char m_externalIPAddress[40]; }; } } -#else // USE_UPNP +#else // USE_UPNP namespace i2p { namespace transport { - /* class stub */ - class UPnP { - public: - - UPnP () {}; - ~UPnP () {}; - void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } - void Stop () {}; - }; + /* class stub */ + class UPnP { + public: + UPnP () {}; + ~UPnP () {}; + void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } + void Stop () {}; + }; } } #endif // USE_UPNP diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index 66661e0f..3dd38fba 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include "Daemon.h" #ifndef _WIN32 @@ -24,8 +16,6 @@ #include "Tunnel.h" #include "RouterContext.h" #include "ClientContext.h" -#include "Transports.h" -#include "util.h" void handle_signal(int sig) { @@ -56,14 +46,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; } } @@ -82,8 +64,7 @@ namespace i2p if (pid < 0) // error { - LogPrint(eLogError, "Daemon: Could not fork: ", strerror(errno)); - std::cerr << "i2pd: Could not fork: " << strerror(errno) << std::endl; + LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); return false; } @@ -92,15 +73,13 @@ namespace i2p int sid = setsid(); if (sid < 0) { - LogPrint(eLogError, "Daemon: Could not create process group."); - std::cerr << "i2pd: Could not create process group." << std::endl; + LogPrint(eLogError, "Daemon: could not create process group."); return false; } std::string d = i2p::fs::GetDataDir(); if (chdir(d.c_str()) != 0) { - LogPrint(eLogError, "Daemon: Could not chdir: ", strerror(errno)); - std::cerr << "i2pd: Could not chdir: " << strerror(errno) << std::endl; + LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); return false; } @@ -115,14 +94,14 @@ namespace i2p uint16_t nfiles; i2p::config::GetOption("limits.openfiles", nfiles); getrlimit(RLIMIT_NOFILE, &limit); if (nfiles == 0) { - LogPrint(eLogInfo, "Daemon: Using system limit in ", limit.rlim_cur, " max open files"); + LogPrint(eLogInfo, "Daemon: using system limit in ", limit.rlim_cur, " max open files"); } else if (nfiles <= limit.rlim_max) { limit.rlim_cur = nfiles; if (setrlimit(RLIMIT_NOFILE, &limit) == 0) { - LogPrint(eLogInfo, "Daemon: Set max number of open files to ", + LogPrint(eLogInfo, "Daemon: set max number of open files to ", nfiles, " (system limit is ", limit.rlim_max, ")"); } else { - LogPrint(eLogError, "Daemon: Can't set max number of open files: ", strerror(errno)); + LogPrint(eLogError, "Daemon: can't set max number of open files: ", strerror(errno)); } } else { LogPrint(eLogError, "Daemon: limits.openfiles exceeds system limit: ", limit.rlim_max); @@ -135,11 +114,11 @@ namespace i2p if (cfsize <= limit.rlim_max) { limit.rlim_cur = cfsize; if (setrlimit(RLIMIT_CORE, &limit) != 0) { - LogPrint(eLogError, "Daemon: Can't set max size of coredump: ", strerror(errno)); + LogPrint(eLogError, "Daemon: can't set max size of coredump: ", strerror(errno)); } else if (cfsize == 0) { LogPrint(eLogInfo, "Daemon: coredumps disabled"); } else { - LogPrint(eLogInfo, "Daemon: Set max size of core files to ", cfsize / 1024, "Kb"); + LogPrint(eLogInfo, "Daemon: set max size of core files to ", cfsize / 1024, "Kb"); } } else { LogPrint(eLogError, "Daemon: limits.coresize exceeds system limit: ", limit.rlim_max); @@ -156,43 +135,28 @@ namespace i2p pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFH < 0) { - LogPrint(eLogError, "Daemon: Could not create pid file ", pidfile, ": ", strerror(errno)); - std::cerr << "i2pd: Could not create pid file " << pidfile << ": " << strerror(errno) << std::endl; + LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } #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; + LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); 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)); - std::cerr << "i2pd: Could not write pidfile " << pidfile << ": " << strerror(errno) << std::endl; + LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); 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; @@ -203,12 +167,7 @@ namespace i2p sigaction(SIGABRT, &sa, 0); sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); - sigaction(SIGPIPE, &sa, 0); - if (handleTSTP) - { - sigaction(SIGTSTP, &sa, 0); - sigaction(SIGCONT, &sa, 0); - } + sigaction(SIGPIPE, &sa, 0); return Daemon_Singleton::start(); } @@ -216,12 +175,12 @@ namespace i2p bool DaemonLinux::stop() { i2p::fs::Remove(pidfile); + return Daemon_Singleton::stop(); } void DaemonLinux::run () { - i2p::util::SetThreadName ("i2pd-daemon"); while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); @@ -238,4 +197,5 @@ namespace i2p } } } + #endif diff --git a/daemon/i2pd.cpp b/daemon/i2pd.cpp index 028aa916..8718ad0c 100644 --- a/daemon/i2pd.cpp +++ b/daemon/i2pd.cpp @@ -1,31 +1,24 @@ -/* -* 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 "Daemon.h" #if defined(QT_GUI_LIB) + namespace i2p { namespace qt { - int RunQT (int argc, char* argv[]); + int RunQT (int argc, char* argv[]); } } - int main( int argc, char* argv[] ) { - return i2p::qt::RunQT (argc, argv); + return i2p::qt::RunQT (argc, argv); } + #else int main( int argc, char* argv[] ) { - if (Daemon.init(argc, argv)) + if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); 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 d170f534..a0ad2cd5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,247 +1,3 @@ -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 - - -- orignal Sun, 22 May 2022 16:00:00 +0000 - -i2pd (2.41.0-1) unstable; urgency=medium - - * updated to version 2.41.0/0.9.53 - - -- r4sas Sun, 20 Feb 2022 13:00:00 +0000 - -i2pd (2.40.0-1) unstable; urgency=medium - - * updated to version 2.40.0/0.9.52 - - -- orignal Mon, 29 Nov 2021 16:00:00 +0000 - -i2pd (2.39.0-1) unstable; urgency=medium - - * updated to version 2.39.0/0.9.51 - - -- orignal Mon, 23 Aug 2021 16:00:00 +0000 - -i2pd (2.38.0-1) unstable; urgency=medium - - * updated to version 2.38.0/0.9.50 - - -- orignal Mon, 17 May 2021 16:00:00 +0000 - -i2pd (2.37.0-1) unstable; urgency=medium - - * updated to version 2.37.0 - - -- orignal Mon, 15 Mar 2021 16:00:00 +0000 - -i2pd (2.36.0-1) unstable; urgency=high - - * updated to version 2.36.0/0.9.49 - - -- orignal Mon, 15 Feb 2021 16:00:00 +0000 - -i2pd (2.35.0-1) unstable; urgency=high - - * updated to version 2.35.0/0.9.48 - - -- orignal Mon, 30 Nov 2020 16:00:00 +0000 - -i2pd (2.34.0-1) unstable; urgency=medium - - * updated to version 2.34.0 - - -- orignal Tue, 27 Oct 2020 16:00:00 +0000 - -i2pd (2.33.0-1) unstable; urgency=medium - - * updated to version 2.33.0/0.9.47 - - -- orignal Mon, 24 Aug 2020 16:00:00 +0000 - -i2pd (2.32.1-1) unstable; urgency=high - - * updated to version 2.32.1 - - -- r4sas Tue, 02 Jun 2020 16:30:00 +0000 - -i2pd (2.32.0-1) unstable; urgency=high - - * updated to version 2.32.0/0.9.46 - * updated systemd service file (see #1394) - * updated apparmor profile (see 9318388007cff0495b4b360d0480f4fc1219a9dc) - * updated logrotate config and moved it to contrib - - -- r4sas Mon, 25 May 2020 12:45:00 +0000 - -i2pd (2.31.0-1) unstable; urgency=medium - - * updated to version 2.31.0 - - -- orignal Fri, 10 Apr 2020 16:00:00 +0000 - -i2pd (2.30.0-1) unstable; urgency=medium - - * updated to version 2.30.0/0.9.45 - - -- orignal Tue, 25 Feb 2020 16:00:00 +0000 - -i2pd (2.29.0-1) unstable; urgency=medium - - * updated to version 2.29.0/0.9.43 - - -- orignal Mon, 21 Oct 2019 16:00:00 +0000 - -i2pd (2.28.0-1) unstable; urgency=medium - - * updated to version 2.28.0/0.9.42 - - -- orignal Tue, 27 Aug 2019 16:00:00 +0000 - -i2pd (2.27.0-1) unstable; urgency=medium - - * updated to version 2.27.0/0.9.41 - - -- orignal Wed, 3 Jul 2019 16:00:00 +0000 - -i2pd (2.26.0-1) unstable; urgency=medium - - * updated to version 2.26.0 - - -- orignal Fri, 7 Jun 2019 16:00:00 +0000 - i2pd (2.25.0-1) unstable; urgency=medium * updated to version 2.25.0/0.9.40 @@ -274,7 +30,7 @@ i2pd (2.21.1-1) unstable; urgency=medium * updated to version 2.21.1 - -- orignal Mon, 22 Oct 2018 16:00:00 +0000 + -- orignal Thu, 22 Oct 2018 16:00:00 +0000 i2pd (2.21.0-1) unstable; urgency=medium diff --git a/debian/compat b/debian/compat index 48082f72..f11c82a4 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -12 +9 \ No newline at end of file diff --git a/debian/control b/debian/control index 48f5e680..843a153c 100644 --- a/debian/control +++ b/debian/control @@ -2,17 +2,31 @@ 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.6 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 +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. + . + This package contains the full-featured C++ implementation of I2P router. + +Package: i2pd-dbg +Architecture: any +Priority: extra +Section: debug +Depends: i2pd (= ${binary:Version}), ${misc:Depends} +Description: i2pd debugging symbols + 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. + . + This package contains symbols required for debugging. diff --git a/debian/copyright b/debian/copyright index 352e260b..3c513020 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,18 +3,25 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2023 PurpleI2P +Copyright: 2013-2017 PurpleI2P License: BSD-3-clause +Files: qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl + qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl + qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java + qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java +Copyright: 2011-2013 BogDan Vatra +License: BSD-2-Clause + Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen - 2016-2023 R4SAS - 2017-2020 Yangfl + 2016-2017 R4SAS + 2017-2018 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. . @@ -42,6 +49,28 @@ License: BSD-3-clause TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +License: BSD-2-Clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/docs b/debian/docs index b43bf86b..dfa6f572 100644 --- a/debian/docs +++ b/debian/docs @@ -1 +1,5 @@ README.md +contrib/i2pd.conf +contrib/subscriptions.txt +contrib/tunnels.conf +contrib/tunnels.d 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.dirs b/debian/i2pd.dirs new file mode 100644 index 00000000..3b643352 --- /dev/null +++ b/debian/i2pd.dirs @@ -0,0 +1,2 @@ +etc/i2pd +var/lib/i2pd 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..d20b2c17 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,6 +1,7 @@ -i2pd usr/bin/ -contrib/i2pd.conf etc/i2pd/ +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/i2pd.logrotate b/debian/i2pd.logrotate deleted file mode 120000 index 40dea037..00000000 --- a/debian/i2pd.logrotate +++ /dev/null @@ -1 +0,0 @@ -../contrib/i2pd.logrotate \ No newline at end of file diff --git a/debian/i2pd.logrotate b/debian/i2pd.logrotate new file mode 100644 index 00000000..63c44270 --- /dev/null +++ b/debian/i2pd.logrotate @@ -0,0 +1,9 @@ +/var/log/i2pd/i2pd.log { + rotate 6 + daily + missingok + notifempty + compress + delaycompress + copytruncate +} diff --git a/contrib/openrc/i2pd.openrc b/debian/i2pd.openrc similarity index 97% rename from contrib/openrc/i2pd.openrc rename to debian/i2pd.openrc index 0233eed8..deca4625 100644 --- a/contrib/openrc/i2pd.openrc +++ b/debian/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/upstart/i2pd.upstart b/debian/i2pd.upstart similarity index 75% rename from contrib/upstart/i2pd.upstart rename to debian/i2pd.upstart index d2cd4d5e..19b58958 100644 --- a/contrib/upstart/i2pd.upstart +++ b/debian/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/debian/patches/01-tune-build-opts.patch b/debian/patches/01-tune-build-opts.patch new file mode 100644 index 00000000..dd2b4638 --- /dev/null +++ b/debian/patches/01-tune-build-opts.patch @@ -0,0 +1,17 @@ +diff --git a/Makefile b/Makefile +index bdadfe0..2f71eec 100644 +--- a/Makefile ++++ b/Makefile +@@ -9,10 +9,10 @@ DEPS := obj/make.dep + + include filelist.mk + +-USE_AESNI := yes ++USE_AESNI := no +-USE_AVX := yes ++USE_AVX := no + USE_STATIC := no + USE_MESHNET := no + USE_UPNP := no + DEBUG := yes + 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/debian/patches/02-fix-1210.patch b/debian/patches/02-fix-1210.patch new file mode 100644 index 00000000..6a4caf94 --- /dev/null +++ b/debian/patches/02-fix-1210.patch @@ -0,0 +1,25 @@ +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: 2018-08-25 + +--- a/contrib/i2pd.service ++++ b/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=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service + ExecReload=/bin/kill -HUP $MAINPID diff --git a/debian/patches/series b/debian/patches/series index f97fdf65..002802b5 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1,2 @@ -01-upnp.patch +01-tune-build-opts.patch +02-fix-1210.patch diff --git a/debian/postinst b/debian/postinst index 720753fd..9c9e74ae 100755 --- a/debian/postinst +++ b/debian/postinst @@ -12,6 +12,7 @@ case "$1" in # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true + usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi @@ -22,7 +23,7 @@ case "$1" in chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME - chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" diff --git a/debian/postrm b/debian/postrm index ba69785e..53ab15e7 100755 --- a/debian/postrm +++ b/debian/postrm @@ -6,7 +6,7 @@ if [ "$1" = "purge" ]; then rm -rf /etc/i2pd rm -rf /var/lib/i2pd rm -rf /var/log/i2pd - rm -rf /run/i2pd + rm -rf /var/run/i2pd fi #DEBHELPER# diff --git a/debian/rules b/debian/rules index fc769066..77ecd7b7 100755 --- a/debian/rules +++ b/debian/rules @@ -1,13 +1,22 @@ #!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. #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 = +DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow +#DPKG_EXPORT_BUILDFLAGS = 1 +#include /usr/share/dpkg/buildflags.mk +#CXXFLAGS+=$(CPPFLAGS) +#PREFIX=/usr %: - dh $@ + dh $@ --parallel +# dh_apparmor --profile-name=usr.sbin.i2pd -pi2pd -override_dh_auto_install: +override_dh_strip: + dh_strip --dbg-package=i2pd-dbg + +## uncomment this if you have "missing info" problem when building package +#override_dh_shlibdeps: +# dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info diff --git a/debian/watch b/debian/watch index 2ec6c29b..55cda021 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%" \ - https://github.com/PurpleI2P/i2pd/tags \ - (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate +version=3 +opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ + https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz diff --git a/filelist.mk b/filelist.mk index d8f503e6..8d451dc9 100644 --- a/filelist.mk +++ b/filelist.mk @@ -5,13 +5,13 @@ # SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ # Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ # Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp Family.cpp \ -# Config.cpp HTTP.cpp Timestamp.cpp util.cpp api.cpp Gost.cpp +# Config.cpp HTTP.cpp Timestamp.cpp util.cpp api.cpp Event.cpp Gost.cpp LIB_SRC = $(wildcard $(LIB_SRC_DIR)/*.cpp) #LIB_CLIENT_SRC = \ # AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp MatchedDestination.cpp \ -# SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp +# SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp WebSocks.cpp LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) @@ -19,8 +19,4 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) #DAEMON_SRC = \ # HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp -LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) - -WRAP_LIB_SRC = $(wildcard $(WRAP_SRC_DIR)/*.cpp) - DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp deleted file mode 100644 index b69c42ef..00000000 --- a/i18n/Afrikaans.cpp +++ /dev/null @@ -1,81 +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 -#include -#include -#include "I18N.h" - -// Afrikaans localization file - -namespace i2p -{ -namespace i18n -{ -namespace afrikaans // language namespace -{ - // language name in lowercase - static std::string language = "afrikaans"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"failed", "Het misluk"}, - {"unknown", "onbekend"}, - {"Tunnels", "Tonnels"}, - {"I2P tunnels", "I2P tonnels"}, - {"SAM sessions", "SAM sessies"}, - {"OK", "LEKKER"}, - {"Testing", "Besig om te toets"}, - {"Firewalled", "Vuurmuur'd"}, - {"Unknown", "Onbekend"}, - {"Error", "Fout"}, - {"Offline", "Aflyn"}, - {"Uptime", "Optyd"}, - {"Network status", "Netwerk status"}, - {"Network status v6", "Netwerk status v6"}, - {"Family", "Familie"}, - {"Received", "Ontvang"}, - {"Sent", "Gestuur"}, - {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, - {"Router Ident", "Router Ident"}, - {"Router Family", "Router Familie"}, - {"Enabled", "Geaktiveer"}, - {"Disabled", "Gedeaktiveer"}, - {"Change", "Verander"}, - {"Change language", "Verander taal"}, - {"Description", "Beskrywing"}, - {"Submit", "Stuur"}, - {"Proxy error", "Proxy-fout"}, - {"Host", "Gasheer"}, - {"", ""}, - }; - - 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"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp deleted file mode 100644 index 67955d8a..00000000 --- a/i18n/Armenian.cpp +++ /dev/null @@ -1,204 +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 -#include -#include -#include "I18N.h" - -// Armenian localization file - -namespace i2p -{ -namespace i18n -{ -namespace armenian // language namespace -{ - // language name in lowercase - static std::string language = "armenian"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f ԿիԲ"}, - {"%.2f MiB", "%.2f ՄիԲ"}, - {"%.2f GiB", "%.2f ԳիԲ"}, - {"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", "MESH-ցանց"}, - {"Clock skew", "Ոչ ճշգրիտ ժամանակ"}, - {"Offline", "Օֆլայն"}, - {"Symmetric NAT", "Սիմետրիկ NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"Uptime", "Առկայություն"}, - {"Network status", "Ցանցի կարգավիճակ"}, - {"Network status v6", "Ցանցի կարգավիճակ v6"}, - {"Stopping in", "Դադարում"}, - {"Family", "Խմբատեսակ"}, - {"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", "Floodfills-ներ"}, - {"Client Tunnels", "Oգտատիրական թունելներ"}, - {"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", "Գաղտնագրի տեսակը"}, - {"Inbound tunnels", "Մուտքային թունելներ"}, - {"%dms", "%dմլվ"}, - {"Outbound tunnels", "Ելքային թունելներ"}, - {"Tags", "Թեգեր"}, - {"Incoming", "Մուտքային"}, - {"Outgoing", "ելքային"}, - {"Destination", "Նշանակման վայր"}, - {"Amount", "Քանակ"}, - {"Incoming Tags", "Մուտքային պիտակներ"}, - {"Tags sessions", "Նստաշրջանի պիտակներ"}, - {"Status", "Կարգավիճակ"}, - {"Local Destination", "Տեղական նշանակման կետ"}, - {"Streams", "Հոսքեր"}, - {"Close stream", "Փակել հոսքը"}, - {"I2CP session not found", "I2CP նստաշրջանը գոյություն չունի"}, - {"I2CP is not enabled", "I2CP միացված է"}, - {"Invalid", "Անվավեր"}, - {"Store type", "Պահեստավորման տեսակը"}, - {"Expires", "Սպառվում է"}, - {"Non Expired Leases", "Չսպառված Lease-եր"}, - {"Gateway", "Դարպաս"}, - {"TunnelID", "Թունելի ID"}, - {"EndDate", "Ավարտ"}, - {"Queue size", "Հերթի չափսը"}, - {"Run peer test", "Գործարկել փորձարկումը"}, - {"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", "Վերադառնալ նախորդ էջի հասցե"}, - {"Back to commands list", "Վերադառնալ հրահանգների ցուցակ"}, - {"Description", "Նկարագրություն"}, - {"A bit information about service on domain", "Մի փոքր տեղեկատվություն տիրոիյթում գտնվող ծառայության մասին"}, - {"Submit", "Ուղարկվել"}, - {"Domain can't end with .b32.i2p", "Տիրույթը չպետք է վերջանա .b32.i2p-ով"}, - {"Domain must end with .i2p", "Տիրույթը պետք է վերջանա .i2p-ով"}, - {"Such destination is not found", "Այդիպսի հասցե գոյություն չունի"}, - {"Unknown command", "Անհայտ հրահանգ"}, - {"Command accepted", "Հրարահանգն ընդունված է"}, - {"Proxy error", "Պրոկսի սխալ"}, - {"Proxy info", "Պրոկսի տեղեկություն"}, - {"Proxy error: Host not found", "Պրոկսի սխալ՝ նման հոսթ գոյություն չունի"}, - {"Remote host not found in router's addressbook", "Դեպի հոսթ կատարված հարցումը գոյություն չունի երթուղիչի հասցեագրքում"}, - {"You may try to find this host on jump services below", "Ստորև Դուք կարող եք գտնել այս հոսթը jump ծառայությունների միջոցով"}, - {"Invalid request", "Սխալ հարցում"}, - {"Proxy unable to parse your request", "Պրոկսին չի կարող հասկանալ Ձեր հարցումը"}, - {"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", "Սխալ 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 պրոկսի սերվերին"}, - {"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, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp deleted file mode 100644 index e3b63ebd..00000000 --- a/i18n/Chinese.cpp +++ /dev/null @@ -1,223 +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; - } - - 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, 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 94803354..00000000 --- a/i18n/Czech.cpp +++ /dev/null @@ -1,223 +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; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "vytváří se"}, - {"failed", "selhalo"}, - {"expiring", "vyprší platnost"}, - {"established", "vytvořeno"}, - {"unknown", "neznámý"}, - {"exploratory", "průzkumné"}, - {"Purple I2P Webconsole", "Purple I2P webová konzole"}, - {"i2pd webconsole", "i2pd webová konzole"}, - {"Main page", "Hlavní stránka"}, - {"Router commands", "Router příkazy"}, - {"Local Destinations", "Místní cíle"}, - {"LeaseSets", "Sety pronájmu"}, - {"Tunnels", "Tunely"}, - {"Transit Tunnels", "Tranzitní tunely"}, - {"Transports", "Transporty"}, - {"I2P tunnels", "I2P tunely"}, - {"SAM sessions", "SAM relace"}, - {"ERROR", "CHYBA"}, - {"OK", "OK"}, - {"Testing", "Testuji"}, - {"Firewalled", "Za Firewallem"}, - {"Unknown", "Neznámý"}, - {"Proxy", "Proxy"}, - {"Mesh", "Síť"}, - {"Clock skew", "Časová nesrovnalost"}, - {"Offline", "Offline"}, - {"Symmetric NAT", "Symetrický NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Žádné popisovače"}, - {"Uptime", "Doba provozu"}, - {"Network status", "Stav sítě"}, - {"Network status v6", "Stav sítě v6"}, - {"Stopping in", "Zastavuji za"}, - {"Family", "Rodina"}, - {"Tunnel creation success rate", "Úspěšnost vytváření tunelů"}, - {"Total tunnel creation success rate", "Celková míra úspěšnosti vytváření tunelů"}, - {"Received", "Přijato"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Odesláno"}, - {"Transit", "Tranzit"}, - {"Data path", "Cesta k datovým souborům"}, - {"Hidden content. Press on text to see.", "Skrytý obsah. Pro zobrazení klikněte sem."}, - {"Router Ident", "Routerová Identita"}, - {"Router Family", "Rodina routerů"}, - {"Router Caps", "Omezení Routerů"}, - {"Version", "Verze"}, - {"Our external address", "Naše externí adresa"}, - {"supported", "podporováno"}, - {"Routers", "Routery"}, - {"Floodfills", "Floodfilly"}, - {"Client Tunnels", "Klientské tunely"}, - {"Services", "Služby"}, - {"Enabled", "Zapnuto"}, - {"Disabled", "Vypnuto"}, - {"Encrypted B33 address", "Šifrovaná adresa B33"}, - {"Address registration line", "Registrační řádek adresy"}, - {"Domain", "Doména"}, - {"Generate", "Vygenerovat"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Poznámka: výsledný řetězec může být použit pouze pro registraci 2LD domén (example.i2p). Pro registraci subdomén použijte prosím i2pd-tools."}, - {"Address", "Adresa"}, - {"Type", "Typ"}, - {"EncType", "EncType"}, - {"Expire LeaseSet", "Zrušit platnost setu pronájmu"}, - {"Inbound tunnels", "Příchozí tunely"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Odchozí tunely"}, - {"Tags", "Štítky"}, - {"Incoming", "Příchozí"}, - {"Outgoing", "Odchozí"}, - {"Destination", "Destinace"}, - {"Amount", "Množství"}, - {"Incoming Tags", "Příchozí štítky"}, - {"Tags sessions", "Relace štítků"}, - {"Status", "Stav"}, - {"Local Destination", "Místní cíl"}, - {"Streams", "Toky"}, - {"Close stream", "Uzavřít tok"}, - {"Such destination is not found", "Takováto destinace nebyla nalezena"}, - {"I2CP session not found", "I2CP relace nenalezena"}, - {"I2CP is not enabled", "I2CP není zapnuto"}, - {"Invalid", "Neplatný"}, - {"Store type", "Druh uložení"}, - {"Expires", "Vyprší"}, - {"Non Expired Leases", "Pronájmy, kterým nevypršela platnost"}, - {"Gateway", "Brána"}, - {"TunnelID", "ID tunelu"}, - {"EndDate", "Datum ukončení"}, - {"floodfill mode is disabled", "režim floodfill je vypnut"}, - {"Queue size", "Velikost fronty"}, - {"Run peer test", "Spustit peer test"}, - {"Reload tunnels configuration", "Znovu načíst nastavení tunelů"}, - {"Decline transit tunnels", "Odmítnout tranzitní tunely"}, - {"Accept transit tunnels", "Přijmout tranzitní tunely"}, - {"Cancel graceful shutdown", "Zrušit hladké vypnutí"}, - {"Start graceful shutdown", "Zahájit hladké vypnutí"}, - {"Force shutdown", "Vynutit vypnutí"}, - {"Reload external CSS styles", "Znovu načíst externí CSS"}, - {"Note: any action done here are not persistent and not changes your config files.", "Poznámka: žádná vykonaná akce zde není trvalá a nemění konfigurační soubory."}, - {"Logging level", "Úroveň logování"}, - {"Transit tunnels limit", "Limit tranzitních tunelů"}, - {"Change", "Změnit"}, - {"Change language", "Změnit jazyk"}, - {"no transit tunnels currently built", "Žádný tranzitní tunel není momentálně vytvořen"}, - {"SAM disabled", "SAM vypnutý"}, - {"no sessions currently running", "Momentálně nejsou spuštěné žádné relace"}, - {"SAM session not found", "SAM relace nenalezena"}, - {"SAM Session", "SAM Relace"}, - {"Server Tunnels", "Server Tunely"}, - {"Client Forwards", "Přesměrování Klienta"}, - {"Server Forwards", "Přesměrování Serveru"}, - {"Unknown page", "Neznámá stránka"}, - {"Invalid token", "Neplatný token"}, - {"SUCCESS", "ÚSPĚCH"}, - {"Stream closed", "Tok uzavřen"}, - {"Stream not found or already was closed", "Tok nenalezen nebo byl již uzavřen"}, - {"Destination not found", "Destinace nenalezena"}, - {"StreamID can't be null", "StreamID nemůže být null"}, - {"Return to destination page", "Zpět na stránku destinací"}, - {"You will be redirected in %d seconds", "Budete přesměrováni za %d sekund"}, - {"LeaseSet expiration time updated", "Aktualizován čas vypršení platnosti setu pronájmu"}, - {"LeaseSet is not found or already expired", "Set pronájmu není k nalezení nebo již vypršela jeho platnost"}, - {"Transit tunnels count must not exceed %d", "Počet tranzitních tunelů nesmí překročit %d"}, - {"Back to commands list", "Zpět na seznam příkazů"}, - {"Register at reg.i2p", "Zaregistrovat na reg.i2p"}, - {"Description", "Popis"}, - {"A bit information about service on domain", "Trochu informací o službě na doméně"}, - {"Submit", "Odeslat"}, - {"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"}, - {"Domain must end with .i2p", "Doména musí končit s .i2p"}, - {"Unknown command", "Neznámý příkaz"}, - {"Command accepted", "Příkaz přijat"}, - {"Proxy error", "Chyba proxy serveru"}, - {"Proxy info", "Proxy informace"}, - {"Proxy error: Host not found", "Chyba proxy serveru: Hostitel nenalezen"}, - {"Remote host not found in router's addressbook", "Vzdálený hostitel nebyl nalezen v adresáři routeru"}, - {"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"}, - {"Invalid request", "Neplatný požadavek"}, - {"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"}, - {"Addresshelper is not supported", "Addresshelper není podporován"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Buďte opatrní: zdroj této URL může být škodlivý! Klikněte zde pro aktualizaci záznamu: Pokračovat."}, - {"Addresshelper forced update rejected", "Addresshelperem vynucená aktualizace zamítnuta"}, - {"To add host %s in router's addressbook, click here: Continue.", "Pro přidání hostitele %s do adresáře routeru, klikněte zde: Pokračovat."}, - {"Addresshelper request", "Požadavek Addresshelperu"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Hostitel %s přidán do adresáře routeru od pomocníka. Klikněte zde pro pokračování: Pokračovat."}, - {"Addresshelper adding", "Addresshelper přidávání"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Hostitel %s je již v adresáři routeru. Klikněte zde pro aktualizaci záznamu: Pokračovat."}, - {"Addresshelper update", "Addresshelper aktualizace"}, - {"Invalid request URI", "Neplatný URI požadavek"}, - {"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"}, - {"Outproxy failure", "Outproxy selhání"}, - {"Bad outproxy settings", "Špatné outproxy nastavení"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Hostitel %s není uvnitř I2P sítě a outproxy není nastavena"}, - {"Unknown outproxy URL", "Neznámá outproxy URL"}, - {"Cannot resolve upstream proxy", "Nelze rozluštit upstream proxy server"}, - {"Hostname is too long", "Název hostitele je příliš dlouhý"}, - {"Cannot connect to upstream SOCKS proxy", "Nelze se připojit k upstream SOCKS proxy serveru"}, - {"Cannot negotiate with SOCKS proxy", "Nelze vyjednávat se SOCKS proxy serverem"}, - {"CONNECT error", "Chyba PŘIPOJENÍ"}, - {"Failed to connect", "Připojení se nezdařilo"}, - {"SOCKS proxy error", "Chyba SOCKS proxy serveru"}, - {"Failed to send request to upstream", "Odeslání žádosti upstream serveru se nezdařilo"}, - {"No reply from SOCKS proxy", "Žádná odpověď od SOCKS proxy serveru"}, - {"Cannot connect", "Nelze se připojit"}, - {"HTTP out proxy not implemented", "HTTP out proxy není implementován"}, - {"Cannot connect to upstream HTTP proxy", "Nelze se připojit k upstream HTTP proxy serveru"}, - {"Host is down", "Hostitel je nedostupný"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Připojení k požadovanému hostiteli nelze vytvořit, může být nedostupný. Zkuste to, prosím, znovu později."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}}, - {"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}}, - {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, - {"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}}, - {"", {"", "", "", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/English.cpp b/i18n/English.cpp deleted file mode 100644 index fb774527..00000000 --- a/i18n/English.cpp +++ /dev/null @@ -1,50 +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 -#include -#include -#include "I18N.h" - -// English localization file -// This is an example translation file without strings in it. - -namespace i2p -{ -namespace i18n -{ -namespace english // language namespace -{ - // language name in lowercase - static std::string language = "english"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"", ""}, - }; - - static std::map> plurals - { - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/French.cpp b/i18n/French.cpp deleted file mode 100644 index 985296a3..00000000 --- a/i18n/French.cpp +++ /dev/null @@ -1,223 +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" - -// French localization file - -namespace i2p -{ -namespace i18n -{ -namespace french // language namespace -{ - // language name in lowercase - static std::string language = "french"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f Kio"}, - {"%.2f MiB", "%.2f Mio"}, - {"%.2f GiB", "%.2f Gio"}, - {"building", "En construction"}, - {"failed", "échoué"}, - {"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", "Destinations locales"}, - {"LeaseSets", "Jeu de baux"}, - {"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"}, - {"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"}, - {"Received", "Reçu"}, - {"%.2f KiB/s", "%.2f 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."}, - {"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."}, - {"Address", "Adresse"}, - {"Type", "Type"}, - {"EncType", "EncType"}, - {"Expire LeaseSet", "Expirer le jeu de baux"}, - {"Inbound tunnels", "Tunnels entrants"}, - {"%dms", "%dms"}, - {"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"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/German.cpp b/i18n/German.cpp deleted file mode 100644 index 90ce82f4..00000000 --- a/i18n/German.cpp +++ /dev/null @@ -1,218 +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" - -// German localization file - -namespace i2p -{ -namespace i18n -{ -namespace german // language namespace -{ - // language name in lowercase - static std::string language = "german"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "In Bau"}, - {"failed", "fehlgeschlagen"}, - {"expiring", "läuft ab"}, - {"established", "hergestellt"}, - {"unknown", "Unbekannt"}, - {"exploratory", "erforschend"}, - {"Purple I2P Webconsole", "Purple I2P-Webkonsole"}, - {"i2pd webconsole", "i2pd-Webkonsole"}, - {"Main page", "Startseite"}, - {"Router commands", "Routerbefehle"}, - {"Local Destinations", "Lokale Ziele"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Tunnel"}, - {"Transit Tunnels", "Transittunnel"}, - {"Transports", "Transporte"}, - {"I2P tunnels", "I2P Tunnel"}, - {"SAM sessions", "SAM Sitzungen"}, - {"ERROR", "FEHLER"}, - {"OK", "OK"}, - {"Testing", "Testen"}, - {"Firewalled", "Hinter einer Firewall"}, - {"Unknown", "Unbekannt"}, - {"Proxy", "Proxy"}, - {"Mesh", "Mesh"}, - {"Clock skew", "Zeitabweichung"}, - {"Offline", "Offline"}, - {"Symmetric NAT", "Symmetrisches NAT"}, - {"No Descriptors", "Keine Beschreibungen"}, - {"Uptime", "Laufzeit"}, - {"Network status", "Netzwerkstatus"}, - {"Network status v6", "Netzwerkstatus v6"}, - {"Stopping in", "Stoppt in"}, - {"Family", "Familie"}, - {"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"}, - {"Received", "Eingegangen"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Gesendet"}, - {"Transit", "Transit"}, - {"Data path", "Datenpfad"}, - {"Hidden content. Press on text to see.", "Versteckter Inhalt. Klicke hier, um ihn zu sehen."}, - {"Router Ident", "Routeridentität"}, - {"Router Family", "Routerfamilie"}, - {"Router Caps", "Routerattribute"}, - {"Version", "Version"}, - {"Our external address", "Unsere externe Adresse"}, - {"supported", "unterstützt"}, - {"Routers", "Router"}, - {"Floodfills", "Floodfills"}, - {"Client Tunnels", "Clienttunnel"}, - {"Services", "Services"}, - {"Enabled", "Aktiviert"}, - {"Disabled", "Deaktiviert"}, - {"Encrypted B33 address", "Verschlüsselte B33-Adresse"}, - {"Address registration line", "Adressregistrierungszeile"}, - {"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."}, - {"Address", "Adresse"}, - {"Type", "Typ"}, - {"EncType", "Verschlüsselungstyp"}, - {"Inbound tunnels", "Eingehende Tunnel"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Ausgehende Tunnel"}, - {"Tags", "Tags"}, - {"Incoming", "Eingehend"}, - {"Outgoing", "Ausgehend"}, - {"Destination", "Ziel"}, - {"Amount", "Anzahl"}, - {"Incoming Tags", "Eingehende Tags"}, - {"Tags sessions", "Tags-Sitzungen"}, - {"Status", "Status"}, - {"Local Destination", "Lokales Ziel"}, - {"Streams", "Streams"}, - {"Close stream", "Stream schließen"}, - {"I2CP session not found", "I2CP-Sitzung nicht gefunden"}, - {"I2CP is not enabled", "I2CP ist nicht aktiviert"}, - {"Invalid", "Ungültig"}, - {"Store type", "Speichertyp"}, - {"Expires", "Ablaufdatum"}, - {"Non Expired Leases", "Nicht abgelaufene Leases"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Enddatum"}, - {"floodfill mode is disabled", "Floodfill Modus ist deaktiviert"}, - {"Queue size", "Größe der Warteschlange"}, - {"Run peer test", "Peer-Test durchführen"}, - {"Reload tunnels configuration", "Tunnel Konfiguration neu laden"}, - {"Decline transit tunnels", "Transittunnel ablehnen"}, - {"Accept transit tunnels", "Transittunnel akzeptieren"}, - {"Cancel graceful shutdown", "Beende das kontrollierte Herunterfahren"}, - {"Start graceful shutdown", "Starte das kontrollierte Herunterfahren"}, - {"Force shutdown", "Herunterfahren erzwingen"}, - {"Reload external CSS styles", "Lade externe CSS-Stile 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 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"}, - {"Server Tunnels", "Servertunnel"}, - {"Client Forwards", "Client-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"}, - {"StreamID can't be null", "StreamID kann nicht null sein"}, - {"Return to destination page", "Zurück zur Ziel-Seite"}, - {"You will be redirected in %d seconds", "Du wirst umgeleitet in %d Sekunden"}, - {"Transit tunnels count must not exceed %d", "Die Anzahl der Transittunnel darf nicht über %d gehen"}, - {"Back to commands list", "Zurück zur Befehlsliste"}, - {"Register at reg.i2p", "Auf reg.i2p registrieren"}, - {"Description", "Beschreibung"}, - {"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"}, - {"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"}, - {"Invalid request", "Ungültige Anfrage"}, - {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht verarbeiten"}, - {"Addresshelper is not supported", "Adresshelfer wird nicht unterstützt"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Vorsicht: Die Quelle dieser URL kann schädlich sein! Klicken Sie hier, um den Datensatz zu aktualisieren: Weiter."}, - {"Addresshelper forced update rejected", "Adresshelfer gezwungene Aktualisierung abgelehnt"}, - {"To add host %s in router's addressbook, click here: Continue.", "Um den Host %s im Adressbuch des Routers hinzuzufügen, klicken Sie hier: Weiter."}, - {"Addresshelper request", "Adresshelfer gefunden"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Host %s wurde vom Helfer zum Adressbuch des Routers hinzugefügt. Klicken Sie hier, um fortzufahren: Weiter."}, - {"Addresshelper adding", "Adresshelfer hinzufügen"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Klicken Sie hier, um den Eintrag zu aktualisieren: Weiter."}, - {"Addresshelper update", "Adresshelfer aktualisieren"}, - {"Invalid request URI", "Ungültige Anfrage-URI"}, - {"Can't detect destination host from request", "Kann den Ziel-Host von der Anfrage nicht erkennen"}, - {"Outproxy failure", "Outproxy-Fehler"}, - {"Bad outproxy settings", "Ungültige Outproxy-Einstellungen"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s außerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, - {"Unknown outproxy URL", "Unbekannte Outproxy-URL"}, - {"Cannot resolve upstream proxy", "Kann den Upstream-Proxy nicht auflösen"}, - {"Hostname is too long", "Hostname zu lang"}, - {"Cannot connect to upstream SOCKS proxy", "Kann keine Verbindung zum Upstream-SOCKS-Proxy herstellen"}, - {"Cannot negotiate with SOCKS proxy", "Kann nicht mit SOCKS-Proxy verhandeln"}, - {"CONNECT error", "CONNECT-Fehler"}, - {"Failed to connect", "Verbindung konnte nicht hergestellt werden"}, - {"SOCKS proxy error", "SOCKS-Proxy-Fehler"}, - {"Failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, - {"No reply from SOCKS proxy", "Keine Antwort vom SOCKS-Proxy"}, - {"Cannot connect", "Kann nicht verbinden"}, - {"HTTP out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, - {"Cannot connect to upstream HTTP proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, - {"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."}, - {"", ""}, - }; - - 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"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, 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 deleted file mode 100644 index 8ed77a6b..00000000 --- a/i18n/I18N.h +++ /dev/null @@ -1,137 +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 -*/ - -#ifndef __I18N_H__ -#define __I18N_H__ - -#include -#include -#include -#include -#include - -namespace i2p -{ -namespace i18n -{ - typedef std::map LocaleStrings; - class Locale - { - public: - Locale ( - const std::string& language, - const LocaleStrings& strings, - const std::map>& plurals, - std::function formula - ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; - - // Get activated language name for webconsole - std::string GetLanguage() const - { - return m_Language; - } - - std::string_view GetString (std::string_view arg) const - { - const auto it = m_Strings.find(arg); - if (it == m_Strings.end()) - { - return arg; - } - else - { - return it->second; - } - } - - std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const - { - const auto it = m_Plurals.find(arg2); - if (it == m_Plurals.end()) // not found, fallback to english - { - return n == 1 ? arg : arg2; - } - else - { - int form = m_Formula(n); - return it->second[form]; - } - } - - private: - const std::string m_Language; - const LocaleStrings m_Strings; - const std::map> m_Plurals; - std::function m_Formula; - }; - - void SetLanguage(const std::string &lang); - std::string_view translate (std::string_view arg); - std::string translate (const std::string& arg, const std::string& arg2, int n); -} // i18n -} // i2p - -/** - * @brief Get translation of string - * @param arg String with message - */ -template -std::string_view tr (TValue&& arg) -{ - return i2p::i18n::translate(std::forward(arg)); -} - -/** - * @brief Get translation of string and format it - * @param arg String with message - * @param args Array of arguments for string formatting -*/ -template -std::string tr (TValue&& arg, TArgs&&... args) -{ - std::string tr_str = std::string (i2p::i18n::translate(std::forward(arg))); // TODO: - - size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - std::string str(size, 0); - std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); - - return str; -} - -/** - * @brief Get translation of string with plural forms - * @param arg String with message in singular form - * @param arg2 String with message in plural form - * @param n Integer, used for selection of form - */ -template -std::string ntr (TValue&& arg, TValue2&& arg2, int n) -{ - return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); -} - -/** - * @brief Get translation of string with plural forms and format it - * @param arg String with message in singular form - * @param arg2 String with message in plural form - * @param n Integer, used for selection of form - * @param args Array of arguments for string formatting - */ -template -std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) -{ - std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); - - size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); - std::string str(size, 0); - std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); - - return str; -} - -#endif // __I18N_H__ diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h deleted file mode 100644 index 6426e2ce..00000000 --- a/i18n/I18N_langs.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright (c) 2021-2023, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef __I18N_LANGS_H__ -#define __I18N_LANGS_H__ - -#include "I18N.h" - -namespace i2p -{ -namespace i18n -{ - struct langData - { - std::string LocaleName; // localized name - std::string ShortCode; // short language code, like "en" - std::function (void)> LocaleFunc; - }; - - // 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 italian { std::shared_ptr GetLocale (); } - namespace polish { std::shared_ptr GetLocale (); } - namespace portuguese { std::shared_ptr GetLocale (); } - namespace russian { std::shared_ptr GetLocale (); } - namespace spanish { std::shared_ptr GetLocale (); } - namespace swedish { std::shared_ptr GetLocale (); } - namespace turkish { std::shared_ptr GetLocale (); } - namespace turkmen { std::shared_ptr GetLocale (); } - namespace ukrainian { std::shared_ptr GetLocale (); } - namespace uzbek { std::shared_ptr GetLocale (); } - - /** - * That map contains international language name lower-case, name in it's language and it's code - */ - 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} }, - { "english", {"English", "en", i2p::i18n::english::GetLocale} }, - { "french", {"Français", "fr", i2p::i18n::french::GetLocale} }, - { "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} }, - { "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} }, - { "polish", {"Polski", "pl", i2p::i18n::polish::GetLocale} }, - { "portuguese", {"Português", "pt", i2p::i18n::portuguese::GetLocale} }, - { "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} }, - { "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} }, - { "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} }, - { "turkish", {"Türk dili", "tr", i2p::i18n::turkish::GetLocale} }, - { "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, - { "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, - { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, - }; - -} // i18n -} // i2p - -#endif // __I18N_LANGS_H__ diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp deleted file mode 100644 index 0ae26f21..00000000 --- a/i18n/Italian.cpp +++ /dev/null @@ -1,223 +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; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "in costruzione"}, - {"failed", "fallito"}, - {"expiring", "in scadenza"}, - {"established", "stabilita"}, - {"unknown", "sconosciuto"}, - {"exploratory", "esplorativo"}, - {"Purple I2P Webconsole", "Terminale web Purple I2P"}, - {"i2pd webconsole", "Terminal web i2pd"}, - {"Main page", "Pagina principale"}, - {"Router commands", "Comandi router"}, - {"Local Destinations", "Destinazioni locali"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Tunnel"}, - {"Transit Tunnels", "Tunnel di transito"}, - {"Transports", "Trasporti"}, - {"I2P tunnels", "Tunnel I2P"}, - {"SAM sessions", "Sessioni SAM"}, - {"ERROR", "ERRORE"}, - {"OK", "OK"}, - {"Testing", "Testando"}, - {"Firewalled", "Protetto da firewall"}, - {"Unknown", "Sconosciuto"}, - {"Proxy", "Proxy"}, - {"Mesh", "Mesh"}, - {"Clock skew", "Orologio disallineato"}, - {"Offline", "Disconnesso"}, - {"Symmetric NAT", "NAT simmetrico"}, - {"Full cone NAT", "Cono completo NAT"}, - {"No Descriptors", "Nessun descrittore"}, - {"Uptime", "In funzione da"}, - {"Network status", "Stato della rete"}, - {"Network status v6", "Stato della rete v6"}, - {"Stopping in", "Arresto in"}, - {"Family", "Famiglia"}, - {"Tunnel creation success rate", "Percentuale di tunnel creati con successo"}, - {"Total tunnel creation success rate", "Percentuale di successo totale nella creazione del tunnel"}, - {"Received", "Ricevuti"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Inviati"}, - {"Transit", "Transitati"}, - {"Data path", "Percorso dati"}, - {"Hidden content. Press on text to see.", "Contenuto nascosto. Premi sul testo per vedere."}, - {"Router Ident", "Identificativo del router"}, - {"Router Family", "Famiglia del router"}, - {"Router Caps", "Limiti del router"}, - {"Version", "Versione"}, - {"Our external address", "Il nostro indirizzo esterno"}, - {"supported", "supportato"}, - {"Routers", "Router"}, - {"Floodfills", "Floodfill"}, - {"Client Tunnels", "Tunnel client"}, - {"Services", "Servizi"}, - {"Enabled", "Abilitato"}, - {"Disabled", "Disabilitato"}, - {"Encrypted B33 address", "Indirizzo criptato B33"}, - {"Address registration line", "Linea di registrazione indirizzo"}, - {"Domain", "Dominio"}, - {"Generate", "Genera"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la stringa risultante può essere utilizzata solo per registrare domini 2LD (example.i2p). Per registrare i sottodomini, si prega di utilizzare i2pd-tools."}, - {"Address", "Indirizzo"}, - {"Type", "Tipologia"}, - {"EncType", "Tipo di crittografia"}, - {"Expire LeaseSet", "Scadenza LeaseSet"}, - {"Inbound tunnels", "Tunnel in entrata"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Tunnel in uscita"}, - {"Tags", "Tag"}, - {"Incoming", "In entrata"}, - {"Outgoing", "In uscita"}, - {"Destination", "Destinazione"}, - {"Amount", "Quantità"}, - {"Incoming Tags", "Tag in entrata"}, - {"Tags sessions", "Sessioni dei tag"}, - {"Status", "Stato"}, - {"Local Destination", "Destinazione locale"}, - {"Streams", "Flussi"}, - {"Close stream", "Interrompi il flusso"}, - {"Such destination is not found", "Questa destinazione non è stata trovata"}, - {"I2CP session not found", "Sessione I2CP non trovata"}, - {"I2CP is not enabled", "I2CP non è abilitato"}, - {"Invalid", "Invalido"}, - {"Store type", "Tipologia di archivio"}, - {"Expires", "Scade"}, - {"Non Expired Leases", "Lease non scaduti"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Data di fine"}, - {"floodfill mode is disabled", "la modalità floodfill è disabilitata"}, - {"Queue size", "Dimensione della coda"}, - {"Run peer test", "Esegui il test dei peer"}, - {"Reload tunnels configuration", "Ricarica la configurazione dei tunnel"}, - {"Decline transit tunnels", "Rifiuta tunnel di transito"}, - {"Accept transit tunnels", "Accetta tunnel di transito"}, - {"Cancel graceful shutdown", "Annulla l'interruzione controllata"}, - {"Start graceful shutdown", "Avvia l'interruzione controllata"}, - {"Force shutdown", "Forza l'arresto"}, - {"Reload external CSS styles", "Ricarica gli stili CSS esterni"}, - {"Note: any action done here are not persistent and not changes your config files.", "Nota: qualsiasi azione effettuata qui non è persistente e non modifica i file di configurazione."}, - {"Logging level", "Livello di log"}, - {"Transit tunnels limit", "Limite di tunnel di transito"}, - {"Change", "Modifica"}, - {"Change language", "Modifica linguaggio"}, - {"no transit tunnels currently built", "Attualmente non ci sono tunnel di transito instaurati"}, - {"SAM disabled", "SAM disabilitato"}, - {"no sessions currently running", "Attualmente non ci sono sessioni attive"}, - {"SAM session not found", "Sessione SAM non trovata"}, - {"SAM Session", "Sessione SAM"}, - {"Server Tunnels", "Tunnel server"}, - {"Client Forwards", "Client di inoltro"}, - {"Server Forwards", "Server di inoltro"}, - {"Unknown page", "Pagina sconosciuta"}, - {"Invalid token", "Token non valido"}, - {"SUCCESS", "SUCCESSO"}, - {"Stream closed", "Flusso terminato"}, - {"Stream not found or already was closed", "Il flusso non è stato trovato oppure è già stato terminato"}, - {"Destination not found", "Destinazione non trovata"}, - {"StreamID can't be null", "Lo StreamID non può essere null"}, - {"Return to destination page", "Ritorna alla pagina di destinazione"}, - {"You will be redirected in %d seconds", "Sarai reindirizzato tra %d secondi"}, - {"LeaseSet expiration time updated", "Tempo di scadenza LeaseSet aggiornato"}, - {"LeaseSet is not found or already expired", "LeaseSet non trovato o già scaduto"}, - {"Transit tunnels count must not exceed %d", "Il conteggio dei tunnel di transito non deve superare %d"}, - {"Back to commands list", "Ritorna alla lista dei comandi"}, - {"Register at reg.i2p", "Registra a reg.i2p"}, - {"Description", "Descrizione"}, - {"A bit information about service on domain", "Alcune informazioni riguardo il servizio sul dominio"}, - {"Submit", "Invia"}, - {"Domain can't end with .b32.i2p", "I domini non possono terminare con .b32.i2p"}, - {"Domain must end with .i2p", "I domini devono terminare con .i2p"}, - {"Unknown command", "Comando sconosciuto"}, - {"Command accepted", "Comando accettato"}, - {"Proxy error", "Errore del proxy"}, - {"Proxy info", "Informazioni del proxy"}, - {"Proxy error: Host not found", "Errore del proxy: Host non trovato"}, - {"Remote host not found in router's addressbook", "L'host remoto non è stato trovato nella rubrica del router"}, - {"You may try to find this host on jump services below", "Si può provare a trovare questo host sui servizi di salto qui sotto"}, - {"Invalid request", "Richiesta non valida"}, - {"Proxy unable to parse your request", "Il proxy non è in grado di elaborare la tua richiesta"}, - {"Addresshelper is not supported", "Addresshelper non è supportato"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Attenzione: la fonte di questo URL potrebbe essere dannosa! Fai clic qui per aggiornare il record: Continua."}, - {"Addresshelper forced update rejected", "Aggiornamento forzato dell'helper degli indirizzi rifiutato"}, - {"To add host %s in router's addressbook, click here: Continue.", "Per aggiungere host %s nella rubrica del router, clicca qui: Continua."}, - {"Addresshelper request", "Richiesta di indirizzo helper"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'host %s viene aggiunto alla rubrica del router dall'helper. Fai clic qui per procedere: Continua."}, - {"Addresshelper adding", "Aggiunta di Addresshelper"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Clicca qui per aggiornare il record: Continua."}, - {"Addresshelper update", "Aggiornamento dell'helper degli indirizzi"}, - {"Invalid request URI", "URI della richiesta non valido"}, - {"Can't detect destination host from request", "Impossibile determinare l'host di destinazione dalla richiesta"}, - {"Outproxy failure", "Fallimento del proxy di uscita"}, - {"Bad outproxy settings", "Impostazioni errate del proxy di uscita"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s non all'interno della rete I2P, ma il proxy di uscita non è abilitato"}, - {"Unknown outproxy URL", "URL del proxy di uscita sconosciuto"}, - {"Cannot resolve upstream proxy", "Impossibile identificare il flusso a monte del proxy"}, - {"Hostname is too long", "Il nome dell'host è troppo lungo"}, - {"Cannot connect to upstream SOCKS proxy", "Impossibile connettersi al flusso a monte del proxy SOCKS"}, - {"Cannot negotiate with SOCKS proxy", "Impossibile negoziare con il proxy SOCKS"}, - {"CONNECT error", "Errore di connessione"}, - {"Failed to connect", "Connessione fallita"}, - {"SOCKS proxy error", "Errore del proxy SOCKS"}, - {"Failed to send request to upstream", "Invio della richiesta a monte non riuscito"}, - {"No reply from SOCKS proxy", "Nessuna risposta dal proxy SOCKS"}, - {"Cannot connect", "Impossibile connettersi"}, - {"HTTP out proxy not implemented", "Proxy HTTP di uscita non implementato"}, - {"Cannot connect to upstream HTTP proxy", "Impossibile connettersi al flusso a monte del proxy HTTP"}, - {"Host is down", "L'host è offline"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Impossibile creare la connessione all'host richiesto, probabilmente è offline. Riprova più tardi."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d giorno", "%d giorni"}}, - {"%d hours", {"%d ora", "%d ore"}}, - {"%d minutes", {"%d minuto", "%d minuti"}}, - {"%d seconds", {"%d secondo", "%d secondi"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp deleted file mode 100644 index 0e8df096..00000000 --- a/i18n/Polish.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" - -// Polish localization file - -namespace i2p -{ -namespace i18n -{ -namespace polish // language namespace -{ - // language name in lowercase - static std::string language = "polish"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "Kompilowanie"}, - {"failed", "nieudane"}, - {"expiring", "wygasający"}, - {"established", "ustanowiony"}, - {"unknown", "nieznany"}, - {"exploratory", "eksploracyjny"}, - {"Purple I2P Webconsole", "Konsola webowa Purple I2P"}, - {"i2pd webconsole", "i2pd konsola webowa"}, - {"Main page", "Strona główna"}, - {"Router commands", "Komendy routera"}, - {"Local Destinations", "Lokalne miejsca docelowe"}, - {"LeaseSets", "ZestawyNajmu"}, - {"Tunnels", "Tunele"}, - {"Transit Tunnels", "Tunele Tranzytu"}, - {"Transports", "Transportery"}, - {"I2P tunnels", "Tunele I2P"}, - {"SAM sessions", "Sesje SAM"}, - {"ERROR", "BŁĄD"}, - {"OK", "Ok"}, - {"Testing", "Testowanie"}, - {"Firewalled", "Za zaporą sieciową"}, - {"Unknown", "Nieznany"}, - {"Proxy", "Proxy"}, - {"Mesh", "Sieć"}, - {"Clock skew", "Przesunięcie czasu"}, - {"Offline", "Offline"}, - {"Symmetric NAT", "Symetryczny NAT"}, - {"Full cone NAT", "Pełny stożek NAT"}, - {"No Descriptors", "Brak deskryptorów"}, - {"Uptime", "Czas pracy"}, - {"Network status", "Stan sieci"}, - {"Network status v6", "Stan sieci v6"}, - {"Stopping in", "Zatrzymywanie za"}, - {"Family", "Rodzina"}, - {"Tunnel creation success rate", "Wskaźnik sukcesu tworzenia tunelu"}, - {"Total tunnel creation success rate", "Całkowity wskaźnik sukcesu tworzenia tunelu"}, - {"Received", "Odebrano"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Wysłane"}, - {"Transit", "Tranzyt"}, - {"Data path", "Ścieżka do danych"}, - {"Hidden content. Press on text to see.", "Ukryta zawartość. Naciśnij tekst, aby zobaczyć."}, - {"Router Ident", "Identyfikator routera"}, - {"Router Family", "Rodzina routera"}, - {"Router Caps", "Możliwości routera"}, - {"Version", "Wersja"}, - {"Our external address", "Nasz zewnętrzny adres"}, - {"supported", "wspierane"}, - {"Routers", "Routery"}, - {"Floodfills", "Floodfille"}, - {"Client Tunnels", "Tunele Klienta"}, - {"Services", "Usługi"}, - {"Enabled", "Aktywny"}, - {"Disabled", "Wyłączony"}, - {"Encrypted B33 address", "Zaszyfrowany adres B33"}, - {"Address registration line", "Linia rejestracji adresu"}, - {"Domain", "Domena"}, - {"Generate", "Generuj"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uwaga: wynik string może być używany tylko do rejestracji domen 2LD (przykład.i2p). Do rejestracji subdomen należy użyć narzędzi i2pd."}, - {"Address", "Adres"}, - {"Type", "Typ"}, - {"EncType", "TypEnkrypcji"}, - {"Expire LeaseSet", "Wygaśnij LeaseSet"}, - {"Inbound tunnels", "Tunele przychodzące"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Tunele wychodzące"}, - {"Tags", "Tagi"}, - {"Incoming", "Przychodzące"}, - {"Outgoing", "Wychodzące"}, - {"Destination", "Miejsce docelowe"}, - {"Amount", "Ilość"}, - {"Incoming Tags", "Przychodzące tagi"}, - {"Tags sessions", "Sesje tagów"}, - {"Status", "Status"}, - {"Local Destination", "Lokalne miejsce docelowe"}, - {"Streams", "Strumienie"}, - {"Close stream", "Zamknij strumień"}, - {"Such destination is not found", "Nie znaleziono takiego miejsca docelowego"}, - {"I2CP session not found", "Sesja I2CP nie została znaleziona"}, - {"I2CP is not enabled", "I2CP nie jest włączone"}, - {"Invalid", "Niepoprawny"}, - {"Store type", "Rodzaj przechowywania"}, - {"Expires", "Wygasa za"}, - {"Non Expired Leases", "Leasingi niewygasłe"}, - {"Gateway", "Brama"}, - {"TunnelID", "IDTunelu"}, - {"EndDate", "DataZakończenia"}, - {"floodfill mode is disabled", "tryb floodfill jest wyłączony"}, - {"Queue size", "Wielkość kolejki"}, - {"Run peer test", "Wykonaj test peer"}, - {"Reload tunnels configuration", "Załaduj ponownie konfigurację tuneli"}, - {"Decline transit tunnels", "Odrzuć tunele tranzytowe"}, - {"Accept transit tunnels", "Akceptuj tunele tranzytowe"}, - {"Cancel graceful shutdown", "Anuluj łagodne wyłączenie"}, - {"Start graceful shutdown", "Rozpocznij łagodne wyłączenie"}, - {"Force shutdown", "Wymuś wyłączenie"}, - {"Reload external CSS styles", "Odśwież zewnętrzne style CSS"}, - {"Note: any action done here are not persistent and not changes your config files.", "Uwaga: każda akcja wykonana tutaj nie jest trwała i nie zmienia Twoich plików konfiguracyjnych."}, - {"Logging level", "Poziom logowania"}, - {"Transit tunnels limit", "Limit tuneli tranzytowych"}, - {"Change", "Zmień"}, - {"Change language", "Zmień język"}, - {"no transit tunnels currently built", "brak obecnie zbudowanych tuneli tranzytowych"}, - {"SAM disabled", "SAM wyłączony"}, - {"no sessions currently running", "brak aktualnie uruchomionych sesji"}, - {"SAM session not found", "Sesja SAM nie została znaleziona"}, - {"SAM Session", "Sesja SAM"}, - {"Server Tunnels", "Tunele Serwera"}, - {"Client Forwards", "Przekierowania Klienta"}, - {"Server Forwards", "Przekierowania Serwera"}, - {"Unknown page", "Nieznana strona"}, - {"Invalid token", "Nieprawidłowy token"}, - {"SUCCESS", "SUKCES"}, - {"Stream closed", "Strumień zamknięty"}, - {"Stream not found or already was closed", "Strumień nie został znaleziony lub został już zamknięty"}, - {"Destination not found", "Nie znaleziono punktu docelowego"}, - {"StreamID can't be null", "StreamID nie może być null"}, - {"Return to destination page", "Wróć do strony miejsca docelowego"}, - {"You will be redirected in %d seconds", "Zostaniesz prekierowany za %d sekund"}, - {"LeaseSet expiration time updated", "Zaktualizowano czas wygaśnięcia LeaseSet"}, - {"LeaseSet is not found or already expired", "LeaseSet nie został znaleziony lub już wygasł"}, - {"Transit tunnels count must not exceed %d", "Liczba tuneli tranzytowych nie może przekraczać %d"}, - {"Back to commands list", "Powrót do listy poleceń"}, - {"Register at reg.i2p", "Zarejestruj się na reg.i2p"}, - {"Description", "Opis"}, - {"A bit information about service on domain", "Trochę informacji o usłudze w domenie"}, - {"Submit", "Zatwierdź"}, - {"Domain can't end with .b32.i2p", "Domena nie może kończyć się na .b32.i2p"}, - {"Domain must end with .i2p", "Domena musi kończyć się na .i2p"}, - {"Unknown command", "Nieznana komenda"}, - {"Command accepted", "Polecenie zaakceptowane"}, - {"Proxy error", "Błąd serwera proxy"}, - {"Proxy info", "Informacje o proxy"}, - {"Proxy error: Host not found", "Błąd proxy: Nie znaleziono hosta"}, - {"Remote host not found in router's addressbook", "Nie znaleziono zdalnego hosta w książce adresowej routera"}, - {"You may try to find this host on jump services below", "Możesz znaleźć tego hosta na poniższych usługach skoku"}, - {"Invalid request", "Nieprawidłowe żądanie"}, - {"Proxy unable to parse your request", "Serwer proxy nie może przetworzyć Twojego żądania"}, - {"Addresshelper is not supported", "Adresshelper nie jest obsługiwany"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Host %s jest już w książce adresowej routera. Uważaj: źródło tego adresu URL może być szkodliwe! Kliknij tutaj, aby zaktualizować rekord: Kontynuuj."}, - {"Addresshelper forced update rejected", "Wymuszona aktualizacja Addreshelper odrzucona"}, - {"To add host %s in router's addressbook, click here: Continue.", "Aby dodać host %s w książce adresowej routera, kliknij tutaj: Kontynuuj."}, - {"Addresshelper request", "Prośba Addresshelper"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Host %s dodany do książki adresowej routera od pomocnika. Kliknij tutaj, aby kontynuować: Kontynuuj."}, - {"Addresshelper adding", "Dodawanie Addresshelper"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Host %s jest już w książce adresowej routera. Kliknij tutaj, aby zaktualizować rekord: Kontynuuj."}, - {"Addresshelper update", "Aktualizacja Adresshelper"}, - {"Invalid request URI", "Nieprawidłowe URI żądania"}, - {"Can't detect destination host from request", "Nie można wykryć hosta docelowego z żądania"}, - {"Outproxy failure", "Błąd proxy wyjściowego"}, - {"Bad outproxy settings", "Błędne ustawienia proxy wyjściowych"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s nie jest wewnątrz sieci I2P, a proxy wyjściowe nie jest włączone"}, - {"Unknown outproxy URL", "Nieznany adres URL proxy wyjściowego"}, - {"Cannot resolve upstream proxy", "Nie można rozwiązać serwera proxy upstream"}, - {"Hostname is too long", "Nazwa hosta jest zbyt długa"}, - {"Cannot connect to upstream SOCKS proxy", "Nie można połączyć się z proxy SOCKS upstream"}, - {"Cannot negotiate with SOCKS proxy", "Nie można negocjować z proxy SOCKS"}, - {"CONNECT error", "Błąd POŁĄCZENIE"}, - {"Failed to connect", "Nie udało się połączyć"}, - {"SOCKS proxy error", "Błąd proxy SOCKS"}, - {"Failed to send request to upstream", "Nie udało się wysłać żądania do upstream"}, - {"No reply from SOCKS proxy", "Brak odpowiedzi od serwera proxy SOCKS"}, - {"Cannot connect", "Nie można się połączyć"}, - {"HTTP out proxy not implemented", "Serwer wyjściowy proxy HTTP nie został zaimplementowany"}, - {"Cannot connect to upstream HTTP proxy", "Nie można połączyć się z proxy HTTP upstream"}, - {"Host is down", "Host jest niedostępny"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Nie można utworzyć połączenia z żądanym hostem, może być wyłączony. Spróbuj ponownie później."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d dzień", "%d dni", "%d dni", "%d dni"}}, - {"%d hours", {"%d godzina", "%d godziny", "%d godzin", "%d godzin"}}, - {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, - {"%d seconds", {"%d sekunda", "%d sekundy", "%d sekund", "%d sekund"}}, - {"", {"", "", "", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp deleted file mode 100644 index 26204dc3..00000000 --- a/i18n/Portuguese.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" - -// Portuguese localization file - -namespace i2p -{ -namespace i18n -{ -namespace portuguese // language namespace -{ - // language name in lowercase - static std::string language = "portuguese"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "construindo"}, - {"failed", "falhou"}, - {"expiring", "expirando"}, - {"established", "estabelecido"}, - {"unknown", "desconhecido"}, - {"exploratory", "exploratório"}, - {"Purple I2P Webconsole", "Webconsole Purple I2P"}, - {"i2pd webconsole", "webconsole i2pd"}, - {"Main page", "Página Principal"}, - {"Router commands", "Comandos do Roteador"}, - {"Local Destinations", "Destinos Locais"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Túneis"}, - {"Transit Tunnels", "Túneis de Trânsito"}, - {"Transports", "Transportes"}, - {"I2P tunnels", "Túneis I2P"}, - {"SAM sessions", "Sessões do SAM"}, - {"ERROR", "ERRO"}, - {"OK", "OK"}, - {"Testing", "Testando"}, - {"Firewalled", "Sob Firewall"}, - {"Unknown", "Desconhecido"}, - {"Proxy", "Proxy"}, - {"Mesh", "Malha"}, - {"Clock skew", "Desvio de Relógio"}, - {"Offline", "Desligado"}, - {"Symmetric NAT", "NAT Simétrico"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Sem Descritores"}, - {"Uptime", "Tempo Ativo"}, - {"Network status", "Estado da rede"}, - {"Network status v6", "Estado da rede v6"}, - {"Stopping in", "Parando em"}, - {"Family", "Família"}, - {"Tunnel creation success rate", "Taxa de sucesso na criação de túneis"}, - {"Total tunnel creation success rate", "Taxa total de sucesso na criação de túneis"}, - {"Received", "Recebido"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Enviado"}, - {"Transit", "Trânsito"}, - {"Data path", "Diretório de dados"}, - {"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."}, - {"Router Ident", "Identidade do Roteador"}, - {"Router Family", "Família do Roteador"}, - {"Router Caps", "Limites do Roteador"}, - {"Version", "Versão"}, - {"Our external address", "Nosso endereço externo"}, - {"supported", "suportado"}, - {"Routers", "Roteadores"}, - {"Floodfills", "Modo Inundação"}, - {"Client Tunnels", "Túneis de Clientes"}, - {"Services", "Serviços"}, - {"Enabled", "Ativado"}, - {"Disabled", "Desativado"}, - {"Encrypted B33 address", "Endereço B33 criptografado"}, - {"Address registration line", "Linha de cadastro de endereço"}, - {"Domain", "Domínio"}, - {"Generate", "Gerar"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Nota: A string resultante só pode ser usada para registrar domínios 2LD (exemplo.i2p). Para registrar subdomínios por favor utilize o i2pd-tools."}, - {"Address", "Endereço"}, - {"Type", "Tipo"}, - {"EncType", "Tipo de Criptografia"}, - {"Expire LeaseSet", "Expirar LeaseSet"}, - {"Inbound tunnels", "Túneis de Entrada"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Túneis de Saída"}, - {"Tags", "Etiquetas"}, - {"Incoming", "Entradas"}, - {"Outgoing", "Saídas"}, - {"Destination", "Destinos"}, - {"Amount", "Quantidade"}, - {"Incoming Tags", "Etiquetas de Entrada"}, - {"Tags sessions", "Sessões de Etiquetas"}, - {"Status", "Estado"}, - {"Local Destination", "Destino Local"}, - {"Streams", "Fluxos"}, - {"Close stream", "Fechar fluxo"}, - {"Such destination is not found", "Tal destino não foi encontrado"}, - {"I2CP session not found", "Sessão do I2CP não encontrada"}, - {"I2CP is not enabled", "I2CP não está ativado"}, - {"Invalid", "Inválido"}, - {"Store type", "Tipo de armazenamento"}, - {"Expires", "Expira em"}, - {"Non Expired Leases", "Sessões não expiradas"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Data final"}, - {"floodfill mode is disabled", "Mode de inundação está desativado"}, - {"Queue size", "Tamanho da fila"}, - {"Run peer test", "Executar teste de peers"}, - {"Reload tunnels configuration", "Recarregar a configuração dos túneis"}, - {"Decline transit tunnels", "Negar túneis de trânsito"}, - {"Accept transit tunnels", "Aceitar túneis de trânsito"}, - {"Cancel graceful shutdown", "Cancelar desligamento gracioso"}, - {"Start graceful shutdown", "Iniciar desligamento gracioso"}, - {"Force shutdown", "Forçar desligamento"}, - {"Reload external CSS styles", "Recarregar estilos CSS externos"}, - {"Note: any action done here are not persistent and not changes your config files.", " Nota: Qualquer ação feita aqui não será permanente e não altera os seus arquivos de configuração."}, - {"Logging level", "Nível de registro"}, - {"Transit tunnels limit", "Limite de túneis de trânsito"}, - {"Change", "Mudar"}, - {"Change language", "Trocar idioma"}, - {"no transit tunnels currently built", "Nenhum túnel de trânsito construido no momento"}, - {"SAM disabled", "SAM desativado"}, - {"no sessions currently running", "Nenhuma sessão funcionando no momento"}, - {"SAM session not found", "Nenhuma sessão do SAM encontrada"}, - {"SAM Session", "Sessão do SAM"}, - {"Server Tunnels", "Túneis de Servidor"}, - {"Client Forwards", "Túneis de Cliente"}, - {"Server Forwards", "Encaminhamentos de Servidor"}, - {"Unknown page", "Página desconhecida"}, - {"Invalid token", "Token Inválido"}, - {"SUCCESS", "SUCESSO"}, - {"Stream closed", "Fluxo fechado"}, - {"Stream not found or already was closed", "Fluxo não encontrado ou já fechado"}, - {"Destination not found", "Destino não encontrado"}, - {"StreamID can't be null", "StreamID não pode ser nulo"}, - {"Return to destination page", "Retornar para à página de destino"}, - {"You will be redirected in %d seconds", "Você será redirecionado em %d segundos"}, - {"LeaseSet expiration time updated", "Tempo de validade do LeaseSet atualizado"}, - {"LeaseSet is not found or already expired", "LeaseSet não foi encontrado ou já expirou"}, - {"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"}, - {"Back to commands list", "Voltar para a lista de comandos"}, - {"Register at reg.i2p", "Registrar em reg.i2p"}, - {"Description", "Descrição"}, - {"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"}, - {"Submit", "Enviar"}, - {"Domain can't end with .b32.i2p", "O domínio não pode terminar com .b32.i2p"}, - {"Domain must end with .i2p", "O domínio não pode terminar com .i2p"}, - {"Unknown command", "Comando desconhecido"}, - {"Command accepted", "Comando aceito"}, - {"Proxy error", "Erro no proxy"}, - {"Proxy info", "Informações do proxy"}, - {"Proxy error: Host not found", "Erro no proxy: Host não encontrado"}, - {"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"}, - {"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos serviços de jump abaixo"}, - {"Invalid request", "Requisição inválida"}, - {"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"}, - {"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador. Cuidado: a fonte desta URL pode ser perigosa! Clique aqui para atualizar o registro: Continuar."}, - {"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"}, - {"To add host %s in router's addressbook, click here: Continue.", "Para adicionar o host %s ao catálogo de endereços do roteador, clique aqui: Continuar ."}, - {"Addresshelper request", "Requisição ao Auxiliar de Endereços"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para prosseguir: Continuar ."}, - {"Addresshelper adding", "Auxiliar de Endereço adicionando"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador . Clique aqui para atualizar o registro: Continuar."}, - {"Addresshelper update", "Atualização do Auxiliar de Endereços"}, - {"Invalid request URI", "A URI de requisição é inválida"}, - {"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"}, - {"Outproxy failure", "Falha no outproxy"}, - {"Bad outproxy settings", "Má configurações do outproxy"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"}, - {"Unknown outproxy URL", "URL de outproxy desconhecida"}, - {"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"}, - {"Hostname is too long", "O hostname é muito longo"}, - {"Cannot connect to upstream SOCKS proxy", "Não é possível se conectar ao proxy SOCKS de entrada"}, - {"Cannot negotiate with SOCKS proxy", "Não é possível negociar com o proxy SOCKS"}, - {"CONNECT error", "Erro de CONEXÃO"}, - {"Failed to connect", "Falha ao conectar"}, - {"SOCKS proxy error", "Erro no proxy SOCKS"}, - {"Failed to send request to upstream", "Falha ao enviar requisição para o fluxo de entrada"}, - {"No reply from SOCKS proxy", "Sem resposta do proxy SOCKS"}, - {"Cannot connect", "Impossível conectar"}, - {"HTTP out proxy not implemented", "proxy de saída HTTP não implementado"}, - {"Cannot connect to upstream HTTP proxy", "Não é possível conectar ao proxy HTTP de entrada"}, - {"Host is down", "Host está desligado"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Não é possível se conectar ao host requisitado, talvez ele esteja for do ar. Por favor, tente novamente mais tarde."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d Dia", "%d Dias"}}, - {"%d hours", {"%d hora", "%d horas"}}, - {"%d minutes", {"%d minuto", "%d minutos"}}, - {"%d seconds", {"%d Segundo", "%d segundos"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp deleted file mode 100644 index 235cc0ae..00000000 --- a/i18n/Russian.cpp +++ /dev/null @@ -1,223 +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 -#include -#include -#include "I18N.h" - -// Russian localization file - -namespace i2p -{ -namespace i18n -{ -namespace russian // language namespace -{ - // language name in lowercase - static std::string language = "russian"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f КиБ"}, - {"%.2f MiB", "%.2f МиБ"}, - {"%.2f GiB", "%.2f ГиБ"}, - {"building", "строится"}, - {"failed", "неудачный"}, - {"expiring", "истекает"}, - {"established", "работает"}, - {"unknown", "неизвестно"}, - {"exploratory", "исследовательский"}, - {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, - {"i2pd webconsole", "Веб-консоль i2pd"}, - {"Main page", "Главная"}, - {"Router commands", "Команды роутера"}, - {"Local Destinations", "Локальные назначения"}, - {"LeaseSets", "Лизсеты"}, - {"Tunnels", "Туннели"}, - {"Transit Tunnels", "Транзитные туннели"}, - {"Transports", "Транспорты"}, - {"I2P tunnels", "I2P туннели"}, - {"SAM sessions", "SAM сессии"}, - {"ERROR", "ОШИБКА"}, - {"OK", "OK"}, - {"Testing", "Тестирование"}, - {"Firewalled", "Заблокировано извне"}, - {"Unknown", "Неизвестно"}, - {"Proxy", "Прокси"}, - {"Mesh", "MESH-сеть"}, - {"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 КиБ/с"}, - {"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", "%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", "Не истекшие Lease-ы"}, - {"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 обновлено"}, - {"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 уже в адресной книге роутера. Будьте осторожны: источник данной ссылки может быть вредоносным! Нажмите здесь, чтобы обновить запись: Продолжить."}, - {"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", "Ошибка 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 прокси серверу"}, - {"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, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp deleted file mode 100644 index 0e657fb4..00000000 --- a/i18n/Spanish.cpp +++ /dev/null @@ -1,204 +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; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "pendiente"}, - {"failed", "fallido"}, - {"expiring", "expiró"}, - {"established", "establecido"}, - {"unknown", "desconocido"}, - {"exploratory", "exploratorio"}, - {"Purple I2P Webconsole", "Consola web de Purple I2P"}, - {"i2pd webconsole", "Consola web de i2pd"}, - {"Main page", "Inicio"}, - {"Router commands", "Comandos de enrutador"}, - {"Local Destinations", "Destinos locales"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Túneles"}, - {"Transit Tunnels", "Túneles de Tránsito"}, - {"Transports", "Transportes"}, - {"I2P tunnels", "Túneles I2P"}, - {"SAM sessions", "Sesiones SAM"}, - {"ERROR", "ERROR"}, - {"OK", "VALE"}, - {"Testing", "Probando"}, - {"Firewalled", "Con cortafuegos"}, - {"Unknown", "Desconocido"}, - {"Proxy", "Proxy"}, - {"Mesh", "Malla"}, - {"Clock skew", "Reloj desfasado"}, - {"Offline", "Desconectado"}, - {"Symmetric NAT", "NAT simétrico"}, - {"Uptime", "Tiempo en línea"}, - {"Network status", "Estado de red"}, - {"Network status v6", "Estado de red v6"}, - {"Stopping in", "Parando en"}, - {"Family", "Familia"}, - {"Tunnel creation success rate", "Tasa de éxito de creación de túneles"}, - {"Received", "Recibido"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Enviado"}, - {"Transit", "Tránsito"}, - {"Data path", "Ruta de datos"}, - {"Hidden content. Press on text to see.", "Contenido oculto. Presione para ver."}, - {"Router Ident", "Ident del Enrutador"}, - {"Router Family", "Familia de enrutador"}, - {"Router Caps", "Atributos del Enrutador"}, - {"Version", "Versión"}, - {"Our external address", "Nuestra dirección externa"}, - {"supported", "soportado"}, - {"Routers", "Enrutadores"}, - {"Floodfills", "Inundaciones"}, - {"Client Tunnels", "Túneles de cliente"}, - {"Services", "Servicios"}, - {"Enabled", "Activado"}, - {"Disabled", "Desactivado"}, - {"Encrypted B33 address", "Dirección encriptada B33"}, - {"Address registration line", "Línea para registrar direcciones"}, - {"Domain", "Dominio"}, - {"Generate", "Generar"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la cadena resultante solo se puede usar para registrar dominios 2LD (ejemplo.i2p). Para registrar subdominios, por favor utilice i2pd-tools."}, - {"Address", "Dirección"}, - {"Type", "Tipo"}, - {"EncType", "TipoEncrip"}, - {"Inbound tunnels", "Túneles entrantes"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Túneles salientes"}, - {"Tags", "Etiquetas"}, - {"Incoming", "Entrante"}, - {"Outgoing", "Saliente"}, - {"Destination", "Destino"}, - {"Amount", "Cantidad"}, - {"Incoming Tags", "Etiquetas entrantes"}, - {"Tags sessions", "Sesiones de etiquetas"}, - {"Status", "Estado"}, - {"Local Destination", "Destino Local"}, - {"Streams", "Flujos"}, - {"Close stream", "Cerrar flujo"}, - {"I2CP session not found", "Sesión I2CP no encontrada"}, - {"I2CP is not enabled", "I2CP no está activado"}, - {"Invalid", "Inválido"}, - {"Store type", "Tipo de almacenamiento"}, - {"Expires", "Caduca"}, - {"Non Expired Leases", "Sesiones No Expiradas"}, - {"Gateway", "Puerta de enlace"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "FechaVenc"}, - {"Queue size", "Tamaño de cola"}, - {"Run peer test", "Ejecutar prueba de par"}, - {"Decline transit tunnels", "Rechazar túneles de tránsito"}, - {"Accept transit tunnels", "Aceptar túneles de tránsito"}, - {"Cancel graceful shutdown", "Cancelar apagado con gracia"}, - {"Start graceful shutdown", "Iniciar apagado con gracia"}, - {"Force shutdown", "Forzar apagado"}, - {"Reload external CSS styles", "Recargar estilos CSS externos"}, - {"Note: any action done here are not persistent and not changes your config files.", "Nota: cualquier acción hecha aquí no es persistente y no cambia tus archivos de configuración."}, - {"Logging level", "Nivel de registro de errores"}, - {"Transit tunnels limit", "Límite de túneles de tránsito"}, - {"Change", "Cambiar"}, - {"Change language", "Cambiar idioma"}, - {"no transit tunnels currently built", "no hay túneles de tránsito actualmente construidos"}, - {"SAM disabled", "SAM desactivado"}, - {"no sessions currently running", "no hay sesiones ejecutándose ahora"}, - {"SAM session not found", "Sesión SAM no encontrada"}, - {"SAM Session", "Sesión SAM"}, - {"Server Tunnels", "Túneles de Servidor"}, - {"Client Forwards", "Redirecciones de Cliente"}, - {"Server Forwards", "Redirecciones de Servidor"}, - {"Unknown page", "Página desconocida"}, - {"Invalid token", "Token inválido"}, - {"SUCCESS", "ÉXITO"}, - {"Stream closed", "Transmisión cerrada"}, - {"Stream not found or already was closed", "No se encontró la transmisión o ya se cerró"}, - {"Destination not found", "Destino no encontrado"}, - {"StreamID can't be null", "StreamID no puede ser nulo"}, - {"Return to destination page", "Volver a la página de destino"}, - {"Back to commands list", "Volver a lista de comandos"}, - {"Register at reg.i2p", "Registrar en reg.i2p"}, - {"Description", "Descripción"}, - {"A bit information about service on domain", "Un poco de información sobre el servicio en el dominio"}, - {"Submit", "Enviar"}, - {"Domain can't end with .b32.i2p", "El dominio no puede terminar con .b32.i2p"}, - {"Domain must end with .i2p", "El dominio debe terminar con .i2p"}, - {"Such destination is not found", "No se encontró el destino"}, - {"Unknown command", "Comando desconocido"}, - {"Command accepted", "Comando aceptado"}, - {"Proxy error", "Error de proxy"}, - {"Proxy info", "Información del proxy"}, - {"Proxy error: Host not found", "Error de proxy: Host no encontrado"}, - {"Remote host not found in router's addressbook", "Servidor remoto no encontrado en la libreta de direcciones del enrutador"}, - {"You may try to find this host on jump services below", "Puede intentar encontrar este dominio en los siguientes servicios de salto"}, - {"Invalid request", "Solicitud inválida"}, - {"Proxy unable to parse your request", "Proxy no puede procesar su solicitud"}, - {"Invalid request URI", "URI de solicitud inválida"}, - {"Can't detect destination host from request", "No se puede detectar el host de destino de la solicitud"}, - {"Outproxy failure", "Fallo en el proxy saliente"}, - {"Bad outproxy settings", "Configuración de outproxy incorrecta"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Dominio %s no está dentro de la red I2P, pero el proxy de salida no está activado"}, - {"Unknown outproxy URL", "URL de proxy outproxy desconocido"}, - {"Cannot resolve upstream proxy", "No se puede resolver el proxy de upstream"}, - {"Hostname is too long", "Nombre de dominio muy largo"}, - {"Cannot connect to upstream SOCKS proxy", "No se puede conectar al proxy SOCKS principal"}, - {"Cannot negotiate with SOCKS proxy", "No se puede negociar con el proxy SOCKS"}, - {"CONNECT error", "Error de CONNECT"}, - {"Failed to connect", "Error al conectar"}, - {"SOCKS proxy error", "Error de proxy SOCKS"}, - {"Failed to send request to upstream", "No se pudo enviar petición al principal"}, - {"No reply from SOCKS proxy", "Sin respuesta del proxy SOCKS"}, - {"Cannot connect", "No se puede conectar"}, - {"HTTP out proxy not implemented", "Proxy externo HTTP no implementado"}, - {"Cannot connect to upstream HTTP proxy", "No se puede conectar al proxy HTTP principal"}, - {"Host is down", "Servidor caído"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "No se puede crear la conexión al servidor solicitado, puede estar caído. Intente de nuevo más tarde."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d día", "%d días"}}, - {"%d hours", {"%d hora", "%d horas"}}, - {"%d minutes", {"%d minuto", "%d minutos"}}, - {"%d seconds", {"%d segundo", "%d segundos"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp deleted file mode 100644 index df13d22f..00000000 --- a/i18n/Swedish.cpp +++ /dev/null @@ -1,220 +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; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "bygger"}, - {"failed", "misslyckad"}, - {"expiring", "utgår"}, - {"established", "upprättad"}, - {"unknown", "okänt"}, - {"exploratory", "utforskande"}, - {"Purple I2P Webconsole", "Purple I2P Webbkonsoll"}, - {"i2pd webconsole", "i2pd-Webbkonsoll"}, - {"Main page", "Huvudsida"}, - {"Router commands", "Routerkommandon"}, - {"Local Destinations", "Lokala Platser"}, - {"LeaseSets", "Hyresuppsättningar"}, - {"Tunnels", "Tunnlar"}, - {"Transit Tunnels", "Förmedlande Tunnlar"}, - {"Transports", "Transporter"}, - {"I2P tunnels", "I2P-tunnlar"}, - {"SAM sessions", "SAM-perioder"}, - {"ERROR", "FEL"}, - {"OK", "OK"}, - {"Testing", "Prövar"}, - {"Firewalled", "Bakom Brandvägg"}, - {"Unknown", "Okänt"}, - {"Proxy", "Proxy"}, - {"Mesh", "Mesh"}, - {"Clock skew", "Tidsförskjutning"}, - {"Offline", "Nedkopplad"}, - {"Symmetric NAT", "Symmetrisk NAT"}, - {"Full cone NAT", "Full kon NAT"}, - {"No Descriptors", "Inga Beskrivningar"}, - {"Uptime", "Upptid"}, - {"Network status", "Nätverkstillstånd"}, - {"Network status v6", "Nätverkstillstånd v6"}, - {"Stopping in", "Avstängd om"}, - {"Family", "Familj"}, - {"Tunnel creation success rate", "Andel framgångsrika tunnlar"}, - {"Received", "Mottaget"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Skickat"}, - {"Transit", "Förmedlat"}, - {"Data path", "Sökväg"}, - {"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."}, - {"Router Ident", "Routeridentitet"}, - {"Router Family", "Routerfamilj"}, - {"Router Caps", "Routerbegränsningar"}, - {"Version", "Version"}, - {"Our external address", "Vår externa adress"}, - {"supported", "stöds"}, - {"Routers", "Routrar"}, - {"Floodfills", "Översvämningsfyllare"}, - {"Client Tunnels", "Klienttunnlar"}, - {"Services", "Tjänster"}, - {"Enabled", "Påslaget"}, - {"Disabled", "Avslaget"}, - {"Encrypted B33 address", "Krypterad B33-Adress"}, - {"Address registration line", "Adressregistreringsrad"}, - {"Domain", "Domän"}, - {"Generate", "Skapa"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uppmärksamma: den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."}, - {"Address", "Adress"}, - {"Type", "Typ"}, - {"EncType", "EncTyp"}, - {"Inbound tunnels", "Ingående Tunnlar"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Utgående Tunnlar"}, - {"Tags", "Taggar"}, - {"Incoming", "Ingående"}, - {"Outgoing", "Utgående"}, - {"Destination", "Plats"}, - {"Amount", "Mängd"}, - {"Incoming Tags", "Ingående Taggar"}, - {"Tags sessions", "Tagg-perioder"}, - {"Status", "Tillstånd"}, - {"Local Destination", "Lokal Plats"}, - {"Streams", "Strömmar"}, - {"Close stream", "Stäng strömmen"}, - {"Such destination is not found", "En sådan plats hittas ej"}, - {"I2CP session not found", "I2CP-period hittades inte"}, - {"I2CP is not enabled", "I2CP är inte påslaget"}, - {"Invalid", "Ogiltig"}, - {"Store type", "Lagringstyp"}, - {"Expires", "Utgångsdatum"}, - {"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"}, - {"Gateway", "Gateway"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "EndDate"}, - {"floodfill mode is disabled", "Floodfill läget är inaktiverat"}, - {"Queue size", "Köstorlek"}, - {"Run peer test", "Utför utsiktstest"}, - {"Reload tunnels configuration", "Ladda om tunnelkonfiguration"}, - {"Decline transit tunnels", "Avvisa förmedlande tunnlar"}, - {"Accept transit tunnels", "Tillåt förmedlande tunnlar"}, - {"Cancel graceful shutdown", "Avbryt välvillig avstängning"}, - {"Start graceful shutdown", "Påbörja välvillig avstängning"}, - {"Force shutdown", "Tvingad avstängning"}, - {"Reload external CSS styles", "Ladda om externa CSS-stilar"}, - {"Note: any action done here are not persistent and not changes your config files.", "Uppmärksamma: inga ändringar här är beständiga eller påverkar dina inställningsfiler."}, - {"Logging level", "Protokollförningsnivå"}, - {"Transit tunnels limit", "Begränsa förmedlande tunnlar"}, - {"Change", "Ändra"}, - {"Change language", "Ändra språk"}, - {"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"}, - {"SAM disabled", "SAM avslaget"}, - {"no sessions currently running", "inga perioder igång"}, - {"SAM session not found", "SAM-perioder hittades ej"}, - {"SAM Session", "SAM-period"}, - {"Server Tunnels", "Värdtunnlar"}, - {"Client Forwards", "Klientförpassningar"}, - {"Server Forwards", "Värdförpassningar"}, - {"Unknown page", "Okänd sida"}, - {"Invalid token", "Ogiltig polett"}, - {"SUCCESS", "FRAMGÅNG"}, - {"Stream closed", "Ström stängd"}, - {"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"}, - {"Destination not found", "Plats hittades ej"}, - {"StreamID can't be null", "Ström-ID kan inte vara null"}, - {"Return to destination page", "Återvänd till platssidan"}, - {"You will be redirected in %d seconds", "Du omdirigeras inom %d sekunder"}, - {"Transit tunnels count must not exceed %d", "Förmedlande tunnlar får inte överstiga %d"}, - {"Back to commands list", "Tillbaka till kommandolistan"}, - {"Register at reg.i2p", "Registrera vid reg.i2p"}, - {"Description", "Beskrivning"}, - {"A bit information about service on domain", "Ett stycke information om domänens tjänst"}, - {"Submit", "Skicka"}, - {"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"}, - {"Domain must end with .i2p", "Domänen måste sluta med .i2p"}, - {"Unknown command", "Okänt kommando"}, - {"Command accepted", "Kommando accepterades"}, - {"Proxy error", "Proxyfel"}, - {"Proxy info", "Proxyinfo"}, - {"Proxy error: Host not found", "Proxyfel: Värden hittades ej"}, - {"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"}, - {"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"}, - {"Invalid request", "Ogiltig förfrågan"}, - {"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"}, - {"Addresshelper is not supported", "Adresshjälparen stöds ej"}, - {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Var försiktig: källan till denna URL kan vara skadlig! Klicka här för att uppdatera registreringen: Fortsätt."}, - {"Addresshelper forced update rejected", "Tvingad uppdatering av adresshjälparen nekad"}, - {"To add host %s in router's addressbook, click here: Continue.", "För att lägga till värd %s i routerns adressbok, klicka här: Fortsätt."}, - {"Addresshelper request", "Adresshjälpare förfrågan"}, - {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Värd %s tillagd i routerns adressbok från hjälparen. Klicka här för att fortsätta: Fortsätt."}, - {"Addresshelper adding", "Adresshjälpare tilläggning"}, - {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Klicka här för att uppdatera registreringen: Fortsätt."}, - {"Addresshelper update", "Adresshjälpare uppdatering"}, - {"Invalid request URI", "Ogiltig förfrågnings-URI"}, - {"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"}, - {"Outproxy failure", "Utproxyfel"}, - {"Bad outproxy settings", "Ogiltig utproxyinställning"}, - {"Host %s is not inside I2P network, but outproxy is not enabled", "Värd %s är inte inom I2P-näverket, men utproxy är inte påslaget"}, - {"Unknown outproxy URL", "okänt Utproxy-URL"}, - {"Cannot resolve upstream proxy", "Hittar inte uppströmsproxyt"}, - {"Hostname is too long", "Värdnamnet är för långt"}, - {"Cannot connect to upstream SOCKS proxy", "Kan inte ansluta till uppström SOCKS-proxy"}, - {"Cannot negotiate with SOCKS proxy", "Kan inte förhandla med SOCKSproxyt"}, - {"CONNECT error", "CONNECT-fel"}, - {"Failed to connect", "Anslutningen misslyckades"}, - {"SOCKS proxy error", "SOCKSproxyfel"}, - {"Failed to send request to upstream", "Förfrågan uppströms kunde ej skickas"}, - {"No reply from SOCKS proxy", "Fick inget svar från SOCKSproxyt"}, - {"Cannot connect", "Kan inte ansluta"}, - {"HTTP out proxy not implemented", "HTTP-Utproxy ej implementerat"}, - {"Cannot connect to upstream HTTP proxy", "Kan inte ansluta till uppströms HTTP-proxy"}, - {"Host is down", "Värden är nere"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d dag", "%d dagar"}}, - {"%d hours", {"%d timme", "%d timmar"}}, - {"%d minutes", {"%d minut", "%d minuter"}}, - {"%d seconds", {"%d sekund", "%d sekunder"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p - diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp deleted file mode 100644 index 9946b336..00000000 --- a/i18n/Turkish.cpp +++ /dev/null @@ -1,114 +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; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "kuruluyor"}, - {"failed", "başarısız"}, - {"expiring", "süresi geçiyor"}, - {"established", "kurulmuş"}, - {"unknown", "bilinmeyen"}, - {"Purple I2P Webconsole", "Mor I2P Webkonsolu"}, - {"i2pd webconsole", "i2pd webkonsolu"}, - {"Main page", "Ana sayfa"}, - {"Router commands", "Router komutları"}, - {"Local Destinations", "Yerel Hedefler"}, - {"Tunnels", "Tüneller"}, - {"Transit Tunnels", "Transit Tünelleri"}, - {"Transports", "Taşıma"}, - {"I2P tunnels", "I2P tünelleri"}, - {"SAM sessions", "SAM oturumları"}, - {"ERROR", "HATA"}, - {"OK", "TAMAM"}, - {"Testing", "Test ediliyor"}, - {"Firewalled", "Güvenlik Duvarı Kısıtlaması"}, - {"Unknown", "Bilinmeyen"}, - {"Proxy", "Proxy"}, - {"Clock skew", "Saat sorunu"}, - {"Offline", "Çevrimdışı"}, - {"Symmetric NAT", "Simetrik NAT"}, - {"Full cone NAT", "Full cone NAT"}, - {"No Descriptors", "Tanımlayıcı Yok"}, - {"Uptime", "Bağlantı süresi"}, - {"Network status", "Ağ durumu"}, - {"Network status v6", "Ağ durumu v6"}, - {"Family", "Aile"}, - {"Tunnel creation success rate", "Tünel oluşturma başarı oranı"}, - {"Received", "Alındı"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Gönderildi"}, - {"Transit", "Transit"}, - {"Data path", "Veri yolu"}, - {"Hidden content. Press on text to see.", "Gizlenmiş içerik. Görmek için yazıya tıklayınız."}, - {"Router Family", "Router Familyası"}, - {"Decline transit tunnels", "Transit tünellerini reddet"}, - {"Accept transit tunnels", "Transit tünellerini kabul et"}, - {"Cancel graceful shutdown", "Düzgün durdurmayı iptal Et"}, - {"Start graceful shutdown", "Düzgün durdurmayı başlat"}, - {"Force shutdown", "Durdurmaya zorla"}, - {"Reload external CSS styles", "Harici CSS stilini yeniden yükle"}, - {"Note: any action done here are not persistent and not changes your config files.", "Not: burada yapılan ayarların hiçbiri kalıcı değildir ve ayar dosyalarınızı değiştirmez."}, - {"Logging level", "Kayıt tutma seviyesi"}, - {"Transit tunnels limit", "Transit tünel limiti"}, - {"Change", "Değiştir"}, - {"Change language", "Dil değiştir"}, - {"no transit tunnels currently built", "kurulmuş bir transit tüneli bulunmamakta"}, - {"SAM disabled", "SAM devre dışı"}, - {"no sessions currently running", "hiçbir oturum şu anda çalışmıyor"}, - {"SAM session not found", "SAM oturumu bulunamadı"}, - {"SAM Session", "SAM oturumu"}, - {"Server Tunnels", "Sunucu Tünelleri"}, - {"Unknown page", "Bilinmeyen sayfa"}, - {"Invalid token", "Geçersiz token"}, - {"SUCCESS", "BAŞARILI"}, - {"", ""}, - }; - - static std::map> plurals - { - {"%d days", {"%d gün", "%d gün"}}, - {"%d hours", {"%d saat", "%d saat"}}, - {"%d minutes", {"%d dakika", "%d dakika"}}, - {"%d seconds", {"%d saniye", "%d saniye"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp deleted file mode 100644 index 7efb8891..00000000 --- a/i18n/Turkmen.cpp +++ /dev/null @@ -1,204 +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 -#include -#include -#include "I18N.h" - -// Turkmen localization file - -namespace i2p -{ -namespace i18n -{ -namespace turkmen // language namespace -{ - // language name in lowercase - static std::string language = "turkmen"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n != 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "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"}, - {"Local Destinations", "Ýerli ýerler"}, - {"LeaseSets", "Lizset"}, - {"Tunnels", "Tuneller"}, - {"Transit Tunnels", "Tranzit Tunelleri"}, - {"Transports", "Daşamak"}, - {"I2P tunnels", "I2P tuneller"}, - {"SAM sessions", "SAM Sessiýasy"}, - {"ERROR", "Ýalňyşlyk"}, - {"OK", "OK"}, - {"Testing", "Synag etmek"}, - {"Firewalled", "Daşynda petiklendi"}, - {"Unknown", "Näbelli"}, - {"Proxy", "Proksi"}, - {"Mesh", "MESH-tor"}, - {"Clock skew", "Takyk wagt däl"}, - {"Offline", "Awtonom"}, - {"Symmetric NAT", "Simmetriklik NAT"}, - {"Uptime", "Onlaýn onlaýn sözlügi"}, - {"Network status", "Tor ýagdaýy"}, - {"Network status v6", "Tor ýagdaýy v6"}, - {"Stopping in", "Soň duruň"}, - {"Family", "Maşgala"}, - {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, - {"Received", "Alnan"}, - {"%.2f KiB/s", "%.2f KiB/s"}, - {"Sent", "Ýerleşdirildi"}, - {"Transit", "Tranzit"}, - {"Data path", "Maglumat ýoly"}, - {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, - {"Router Ident", "Marşrutly kesgitleýji"}, - {"Router Family", "Marşrutler maşgalasy"}, - {"Router Caps", "Baýdaklar marşruteri"}, - {"Version", "Wersiýasy"}, - {"Our external address", "Daşarky salgymyz"}, - {"supported", "goldanýar"}, - {"Routers", "Marşrutizatorlar"}, - {"Floodfills", "Fludfillar"}, - {"Client Tunnels", "Müşderi tunelleri"}, - {"Services", "Hyzmatlar"}, - {"Enabled", "Goşuldy"}, - {"Disabled", "Öçürildi"}, - {"Encrypted B33 address", "Şifrlenen B33 salgylar"}, - {"Address registration line", "Hasaba alyş salgysy"}, - {"Domain", "Domen"}, - {"Generate", "Öndürmek"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner (example.i2p). Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, - {"Address", "Salgysy"}, - {"Type", "Görnüş"}, - {"EncType", "Şifrlemek görnüşi"}, - {"Inbound tunnels", "Gelýän tuneller"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Çykýan tuneller"}, - {"Tags", "Bellikler"}, - {"Incoming", "Gelýän"}, - {"Outgoing", "Çykýan"}, - {"Destination", "Maksat"}, - {"Amount", "Sany"}, - {"Incoming Tags", "Gelýän bellikler"}, - {"Tags sessions", "Sapaklar bellikler"}, - {"Status", "Ýagdaýy"}, - {"Local Destination", "Ýerli maksat"}, - {"Streams", "Strimlary"}, - {"Close stream", "Yap strim"}, - {"I2CP session not found", "I2CP Sessiýa tapylmady"}, - {"I2CP is not enabled", "I2CP goşulmaýar"}, - {"Invalid", "Nädogry"}, - {"Store type", "Ammar görnüşi"}, - {"Expires", "Möhleti gutarýar"}, - {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, - {"Gateway", "Derweze"}, - {"TunnelID", "Tuneliň ID"}, - {"EndDate", "Gutarýar"}, - {"Queue size", "Nobatyň ululygy"}, - {"Run peer test", "Synag başlaň"}, - {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, - {"Accept transit tunnels", "Tranzit tunellerini alyň"}, - {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, - {"Start graceful shutdown", "Tekiz durmak"}, - {"Force shutdown", "Mejbury duralga"}, - {"Reload external CSS styles", "Daşarky CSS stillerini täzeden ýükläň"}, - {"Note: any action done here are not persistent and not changes your config files.", "Bellik: Bu ýerde öndürilen islendik çäre hemişelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, - {"Logging level", "Giriş derejesi"}, - {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, - {"Change", "Üýtgetmek"}, - {"Change language", "Dil üýtgetmek"}, - {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, - {"SAM disabled", "SAM öçürilen"}, - {"no sessions currently running", "başlamagyň sessiýalary ýok"}, - {"SAM session not found", "SAM Sessiýa tapylmady"}, - {"SAM Session", "SAM Sessiýa"}, - {"Server Tunnels", "Serwer tunelleri"}, - {"Client Forwards", "Müşderi gönükdirýär"}, - {"Server Forwards", "Serweriň täzeden düzlüleri"}, - {"Unknown page", "Näbelli sahypa"}, - {"Invalid token", "Nädogry token"}, - {"SUCCESS", "Üstünlikli"}, - {"Stream closed", "Strim ýapyk"}, - {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, - {"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ň"}, - {"Back to commands list", "Topar sanawyna dolan"}, - {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, - {"Description", "Beýany"}, - {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, - {"Submit", "Iber"}, - {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, - {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, - {"Such destination is not found", "Bu barmaly ýer tapylmady"}, - {"Unknown command", "Näbelli topar"}, - {"Command accepted", "Topar kabul edilýär"}, - {"Proxy error", "Proksi ýalňyşlygy"}, - {"Proxy info", "Proksi maglumat"}, - {"Proxy error: Host not found", "Proksi ýalňyşlygy: Host tapylmady"}, - {"Remote host not found in router's addressbook", "Uzakdaky öý eýesi marşruteriň salgy kitabynda tapylmady"}, - {"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"}, - {"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"}, - {"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"}, - {"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ň."}, - {"", ""}, - }; - - 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"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp deleted file mode 100644 index c1b6c772..00000000 --- a/i18n/Ukrainian.cpp +++ /dev/null @@ -1,223 +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 -#include -#include -#include "I18N.h" - -// Ukrainian localization file - -namespace i2p -{ -namespace i18n -{ -namespace ukrainian // language namespace -{ - // language name in lowercase - static std::string language = "ukrainian"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f КіБ"}, - {"%.2f MiB", "%.2f МіБ"}, - {"%.2f GiB", "%.2f ГіБ"}, - {"building", "будується"}, - {"failed", "невдалий"}, - {"expiring", "завершується"}, - {"established", "працює"}, - {"unknown", "невідомо"}, - {"exploratory", "дослідницький"}, - {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, - {"i2pd webconsole", "Веб-консоль i2pd"}, - {"Main page", "Головна"}, - {"Router commands", "Команди маршрутизатора"}, - {"Local Destinations", "Локальні Призначення"}, - {"LeaseSets", "Лізсети"}, - {"Tunnels", "Тунелі"}, - {"Transit Tunnels", "Транзитні Тунелі"}, - {"Transports", "Транспорти"}, - {"I2P tunnels", "I2P тунелі"}, - {"SAM sessions", "SAM сесії"}, - {"ERROR", "ПОМИЛКА"}, - {"OK", "OK"}, - {"Testing", "Тестування"}, - {"Firewalled", "Заблоковано ззовні"}, - {"Unknown", "Невідомо"}, - {"Proxy", "Проксі"}, - {"Mesh", "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.", "Примітка: отриманий рядок може бути використаний тільки для реєстрації доменів другого рівня (example.i2p). Для реєстрації піддоменів використовуйте i2pd-tools."}, - {"Address", "Адреса"}, - {"Type", "Тип"}, - {"EncType", "ТипШифр"}, - {"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", "Не завершені Lease-и"}, - {"Gateway", "Шлюз"}, - {"TunnelID", "ID тунеля"}, - {"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", "Серверні Тунелі"}, - {"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", "Ідентифікатор потоку не може бути порожнім"}, - {"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 вже в адресній книзі маршрутизатора. Будьте обережні: джерело цієї адреси може зашкодити! Натисніть тут, щоб оновити запис: Продовжити."}, - {"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", "Невідомий URL зовнішнього проксі"}, - {"Cannot resolve upstream proxy", "Не вдається визначити висхідний проксі"}, - {"Hostname is too long", "Ім'я вузла надто довге"}, - {"Cannot connect to upstream SOCKS proxy", "Не вдалося підключитися до висхідного SOCKS проксі сервера"}, - {"Cannot negotiate with SOCKS proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, - {"CONNECT error", "Помилка CONNECT запиту"}, - {"Failed to connect", "Не вдалося підключитися"}, - {"SOCKS proxy error", "Помилка SOCKS проксі"}, - {"Failed to send request to upstream", "Не вдалося відправити запит висхідному проксі"}, - {"No reply from SOCKS proxy", "Немає відповіді від SOCKS проксі сервера"}, - {"Cannot connect", "Не вдалося підключитися"}, - {"HTTP out proxy not implemented", "Підтримка зовнішнього HTTP проксі сервера не реалізована"}, - {"Cannot connect to upstream HTTP proxy", "Не вдалося підключитися до висхідного HTTP проксі сервера"}, - {"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, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp deleted file mode 100644 index 8e870772..00000000 --- a/i18n/Uzbek.cpp +++ /dev/null @@ -1,223 +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 -#include -#include -#include "I18N.h" - -// Ukrainian localization file - -namespace i2p -{ -namespace i18n -{ -namespace uzbek // language namespace -{ - // language name in lowercase - static std::string language = "uzbek"; - - // See for language plural forms here: - // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - static int plural (int n) { - return n > 1 ? 1 : 0; - } - - static const LocaleStrings strings - { - {"%.2f KiB", "%.2f KiB"}, - {"%.2f MiB", "%.2f MiB"}, - {"%.2f GiB", "%.2f GiB"}, - {"building", "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"}, - {"Local Destinations", "Mahalliy joylanishlar"}, - {"LeaseSets", "LeaseSets"}, - {"Tunnels", "Tunnellar"}, - {"Transit Tunnels", "Tranzit Tunellari"}, - {"Transports", "Transportlar"}, - {"I2P tunnels", "I2P tunnellari"}, - {"SAM sessions", "SAM sessiyalari"}, - {"ERROR", "XATO"}, - {"OK", "OK"}, - {"Testing", "Testlash"}, - {"Firewalled", "Xavfsizlik devori bilan himoyalangan"}, - {"Unknown", "Notanish"}, - {"Proxy", "Proksi"}, - {"Mesh", "Mesh To'r"}, - {"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"}, - {"Sent", "Yuborilgan"}, - {"Transit", "Tranzit"}, - {"Data path", "Ma'lumotlar joylanishi"}, - {"Hidden content. Press on text to see.", "Yashirin tarkib. Ko'rish uchun matn ustida bosing."}, - {"Router Ident", "Router identifikatori"}, - {"Router Family", "Router oilasi"}, - {"Router Caps", "Router Bayroqlari"}, - {"Version", "Versiya"}, - {"Our external address", "Bizning tashqi manzilimiz"}, - {"supported", "qo'llab-quvvatlanadi"}, - {"Routers", "Routerlar"}, - {"Floodfills", "Floodfills"}, - {"Client Tunnels", "Mijoz Tunellari"}, - {"Services", "Xizmatlar"}, - {"Enabled", "Yoqilgan"}, - {"Disabled", "O'chirilgan"}, - {"Encrypted B33 address", "Shifrlangan B33 manzil"}, - {"Address registration line", "Manzilni ro'yxatga olish liniyasi"}, - {"Domain", "Domen"}, - {"Generate", "Yaratish"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Eslatma: natija satridan faqat 2LD domenlarini ro'yxatdan o'tkazish uchun foydalanish mumkin (example.i2p). Subdomenlarni ro'yxatdan o'tkazish uchun 'i2pd-tools'dan foydalaning."}, - {"Address", "Manzil"}, - {"Type", "Turi"}, - {"EncType", "ShifrlashTuri"}, - {"Expire LeaseSet", "LeaseSet muddati tugaydi"}, - {"Inbound tunnels", "Kirish tunnellari"}, - {"%dms", "%dms"}, - {"Outbound tunnels", "Chiquvchi tunnellar"}, - {"Tags", "Teglar"}, - {"Incoming", "Kiruvchi"}, - {"Outgoing", "Chiquvchi"}, - {"Destination", "Manzilgoh"}, - {"Amount", "Soni"}, - {"Incoming Tags", "Kiruvchi teglar"}, - {"Tags sessions", "Teglar sessiyalari"}, - {"Status", "Holat"}, - {"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"}, - {"Store type", "Saqlash turi"}, - {"Expires", "Muddati tugaydi"}, - {"Non Expired Leases", "Muddati O'tmagan Leases"}, - {"Gateway", "Kirish yo'li"}, - {"TunnelID", "TunnelID"}, - {"EndDate", "Tugash Sanasi"}, - {"floodfill mode is disabled", "floodfill rejimi o'chirilgan"}, - {"Queue size", "Navbat hajmi"}, - {"Run peer test", "Sinovni boshlang"}, - {"Reload tunnels configuration", "Tunnel konfiguratsiyasini qayta yuklash"}, - {"Decline transit tunnels", "Tranzit tunnellarini rad etish"}, - {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, - {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qilish"}, - {"Start graceful shutdown", "Yumshoq to'xtashni boshlash"}, - {"Force shutdown", "Majburiy to'xtatish"}, - {"Reload external CSS styles", "Tashqi CSS uslublarini qayta yuklang"}, - {"Note: any action done here are not persistent and not changes your config files.", "Eslatma: shu yerda qilingan har qanday harakat doimiy emas va konfiguratsiya fayllarini o'zgartirmaydi."}, - {"Logging level", "Jurnal darajasi"}, - {"Transit tunnels limit", "Tranzit tunellarning chegarasi"}, - {"Change", "O'zgartirish"}, - {"Change language", "Tilni o'zgartirish"}, - {"no transit tunnels currently built", "qurilgan tranzit tunnellari yo'q"}, - {"SAM disabled", "SAM o'chirilgan"}, - {"no sessions currently running", "hech qanday ishlaydigan sessiyalar yo'q"}, - {"SAM session not found", "SAM sessiyasi topilmadi"}, - {"SAM Session", "SAM sessiyasi"}, - {"Server Tunnels", "Server Tunellari"}, - {"Client Forwards", "Mijozlarni Yo'naltirish"}, - {"Server Forwards", "Serverni Yo'naltirish"}, - {"Unknown page", "Noma'lum sahifa"}, - {"Invalid token", "Noto‘g‘ri belgi"}, - {"SUCCESS", "Muvaffaqiyat"}, - {"Stream closed", "Strim yopiq"}, - {"Stream not found or already was closed", "Strim topilmadi yoki allaqachon yopilgan"}, - {"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"}, - {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, - {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, - {"Description", "Tavsif"}, - {"A bit information about service on domain", "Domen xizmatlari haqida bir oz ma'lumot"}, - {"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"}, - {"Unknown command", "Noma'lum buyruq"}, - {"Command accepted", "Buyruq qabul qilindi"}, - {"Proxy error", "Proksi xatosi"}, - {"Proxy info", "Proksi ma'lumotlari"}, - {"Proxy error: Host not found", "Proksi xatosi: Xost topilmadi"}, - {"Remote host not found in router's addressbook", "Masofaviy xost yo'riqnoma manzillar kitobida topilmadi"}, - {"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"}, - {"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"}, - {"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"}, - {"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."}, - {"", ""}, - }; - - 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"}}, - {"", {"", ""}}, - }; - - std::shared_ptr GetLocale() - { - return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); - } - -} // language -} // i18n -} // i2p diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index bc9da4fb..f80f2751 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include @@ -15,8 +7,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', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', @@ -27,7 +18,7 @@ namespace data { return T32; } - + static void iT64Build(void); /* @@ -38,33 +29,34 @@ namespace data * Direct Substitution Table */ - static constexpr char T64[64] = - { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '-', '~' + static const char T64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '~' }; const char * GetBase64SubstitutionTable () { 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,143 +66,163 @@ 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 /* Number of bytes in the encoded buffer */ + ByteStreamToBase64 ( + 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); - - std::string out; - out.reserve (outCount); - for ( i = 0; i < n; i++ ) - { - acc_1 = *ps++; - acc_2 = (acc_1 << 4) & 0x30; - acc_1 >>= 2; // base64 digit #1 - out.push_back (T64[acc_1]); - acc_1 = *ps++; - acc_2 |= acc_1 >> 4; // base64 digit #2 - out.push_back (T64[acc_2]); - acc_1 &= 0x0f; - acc_1 <<= 2; - acc_2 = *ps++; - acc_1 |= acc_2 >> 6; // base64 digit #3 - out.push_back (T64[acc_1]); - acc_2 &= 0x3f; // base64 digit #4 - out.push_back (T64[acc_2]); + n = InCount/3; + m = InCount%3; + if (!m) + outCount = 4*n; + else + outCount = 4*(n+1); + if (outCount > len) return 0; + pd = (unsigned char *)OutBuffer; + for ( i = 0; i>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + acc_1 = *ps++; + 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 */ + *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); + if ( m == 1 ){ + acc_1 = *ps++; + acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */ + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + *pd++ = T64[acc_2]; + *pd++ = P64; + *pd++ = P64; } - 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 = *ps++; - acc_2 |= acc_1 >> 4; // base64 digit #2 - out.push_back (T64[acc_2]); - acc_1 &= 0x0f; - acc_1 <<= 2; // base64 digit #3 - out.push_back (T64[acc_1]); - out.push_back (P64); + else if ( m == 2 ){ + acc_1 = *ps++; + acc_2 = (acc_1<<4)&0x3f; + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + acc_1 = *ps++; + acc_2 |= acc_1 >> 4; /* base64 digit #2 */ + *pd++ = T64[acc_2]; + acc_1 &= 0x0f; + 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 /* Number of output bytes */ + Base64ToByteStream ( + 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; - 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; - else - return 0; + int i; + int n; + int m; + size_t outCount; if (isFirstTime) iT64Build(); + n = InCount/4; + m = InCount%4; + if (InCount && !m) + outCount = 3*n; + else { + outCount = 0; + 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 (); + ps = (unsigned char *)(InBuffer + InCount - 1); + while ( *ps-- == P64 ) outCount--; + ps = (unsigned char *)InBuffer; + + if (outCount > len) return -1; pd = OutBuffer; auto endOfOutBuffer = OutBuffer + outCount; - for (int i = 0; i < d.quot; i++) - { - acc_1 = iT64[int(*ps++)]; - acc_2 = iT64[int(*ps++)]; - acc_1 <<= 2; - acc_1 |= acc_2 >> 4; - *pd++ = acc_1; - if (pd >= endOfOutBuffer) - break; + for ( i = 0; i < n; i++ ){ + acc_1 = iT64[*ps++]; + acc_2 = iT64[*ps++]; + acc_1 <<= 2; + acc_1 |= acc_2>>4; + *pd++ = acc_1; + if (pd >= endOfOutBuffer) break; - acc_2 <<= 4; - acc_1 = iT64[int(*ps++)]; - acc_2 |= acc_1 >> 2; - *pd++ = acc_2; - if (pd >= endOfOutBuffer) - break; + acc_2 <<= 4; + acc_1 = iT64[*ps++]; + acc_2 |= acc_1 >> 2; + *pd++ = acc_2; + if (pd >= endOfOutBuffer) break; - acc_2 = iT64[int(*ps++)]; - acc_2 |= acc_1 << 6; - *pd++ = acc_2; + 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; } /* @@ -224,19 +236,20 @@ namespace data static void iT64Build() { - int i; + int i; isFirstTime = 0; - for ( i = 0; i < 256; i++ ) iT64[i] = -1; - for ( i = 0; i < 64; i++ ) iT64[(int)T64[i]] = i; + for ( i=0; i<256; i++ ) iT64[i] = -1; + for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i; 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 +269,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 +295,11 @@ 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..a273f468 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,51 +1,26 @@ -/* -* 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 BASE_H__ #define BASE_H__ #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; - } - - std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization + /** + Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes + */ + size_t Base64EncodingBufferSize(const size_t input_size); + + std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization } // data } // i2p diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp deleted file mode 100644 index a661b428..00000000 --- a/libi2pd/Blinding.cpp +++ /dev/null @@ -1,334 +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 // for crc32 -#include -#include -#include -#include -#include "Base.h" -#include "Crypto.h" -#include "Log.h" -#include "Timestamp.h" -#include "I2PEndian.h" -#include "Ed25519.h" -#include "Signature.h" -#include "Blinding.h" - -namespace i2p -{ -namespace data -{ - static EC_POINT * BlindPublicKeyECDSA (const EC_GROUP * group, const EC_POINT * pub, const uint8_t * seed) - { - BN_CTX * ctx = BN_CTX_new (); - BN_CTX_start (ctx); - BIGNUM * q = BN_CTX_get (ctx); - EC_GROUP_get_order (group, q, ctx); - // calculate alpha = seed mod q - BIGNUM * alpha = BN_CTX_get (ctx); - BN_bin2bn (seed, 64, alpha); // seed is in BigEndian - BN_mod (alpha, alpha, q, ctx); // % q - // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) - auto p = EC_POINT_new (group); - EC_POINT_mul (group, p, alpha, nullptr, nullptr, ctx); // B*alpha - EC_POINT_add (group, p, pub, p, ctx); // pub + B*alpha - BN_CTX_end (ctx); - BN_CTX_free (ctx); - return p; - } - - static void BlindPrivateKeyECDSA (const EC_GROUP * group, const BIGNUM * priv, const uint8_t * seed, BIGNUM * blindedPriv) - { - BN_CTX * ctx = BN_CTX_new (); - BN_CTX_start (ctx); - BIGNUM * q = BN_CTX_get (ctx); - EC_GROUP_get_order (group, q, ctx); - // calculate alpha = seed mod q - BIGNUM * alpha = BN_CTX_get (ctx); - BN_bin2bn (seed, 64, alpha); // seed is in BigEndian - BN_mod (alpha, alpha, q, ctx); // % q - BN_add (alpha, alpha, priv); // alpha = alpha + priv - // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod q - BN_mod (blindedPriv, alpha, q, ctx); // % q - BN_CTX_end (ctx); - BN_CTX_free (ctx); - } - - static void BlindEncodedPublicKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * pub, const uint8_t * seed, uint8_t * blindedPub) - { - BIGNUM * x = BN_bin2bn (pub, publicKeyLen/2, NULL); - BIGNUM * y = BN_bin2bn (pub + publicKeyLen/2, publicKeyLen/2, NULL); - EC_POINT * p = EC_POINT_new (group); - EC_POINT_set_affine_coordinates_GFp (group, p, x, y, NULL); - EC_POINT * p1 = BlindPublicKeyECDSA (group, p, seed); - EC_POINT_free (p); - 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); - BN_free (x); BN_free (y); - } - - static void BlindEncodedPrivateKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub) - { - BIGNUM * a = BN_bin2bn (priv, publicKeyLen/2, NULL); - BIGNUM * a1 = BN_new (); - BlindPrivateKeyECDSA (group, a, seed, a1); - BN_free (a); - i2p::crypto::bn2buf (a1, blindedPriv, publicKeyLen/2); - auto p = EC_POINT_new (group); - BN_CTX * ctx = BN_CTX_new (); - EC_POINT_mul (group, p, a1, nullptr, nullptr, ctx); // B*a1 - BN_CTX_free (ctx); - BN_free (a1); - BIGNUM * x = BN_new(), * y = BN_new(); - EC_POINT_get_affine_coordinates_GFp (group, p, x, y, NULL); - EC_POINT_free (p); - i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); - i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); - BN_free (x); BN_free (y); - } - - template - static size_t BlindECDSA (i2p::data::SigningKeyType sigType, const uint8_t * key, const uint8_t * seed, Fn blind, Args&&...args) - // blind is BlindEncodedPublicKeyECDSA or BlindEncodedPrivateKeyECDSA - { - size_t publicKeyLength = 0; - EC_GROUP * group = nullptr; - switch (sigType) - { - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - { - publicKeyLength = i2p::crypto::ECDSAP256_KEY_LENGTH; - group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1); - break; - } - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - { - publicKeyLength = i2p::crypto::ECDSAP384_KEY_LENGTH; - group = EC_GROUP_new_by_curve_name (NID_secp384r1); - break; - } - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - { - publicKeyLength = i2p::crypto::ECDSAP521_KEY_LENGTH; - group = EC_GROUP_new_by_curve_name (NID_secp521r1); - break; - } - default: - LogPrint (eLogError, "Blinding: Signature type ", (int)sigType, " is not ECDSA"); - } - if (group) - { - blind (publicKeyLength, group, key, seed, std::forward(args)...); - EC_GROUP_free (group); - } - return publicKeyLength; - } - -//---------------------------------------------------------- - - 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_CLIENT_AUTH_FLAG = 0x04; - - BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, bool clientAuth): - m_IsClientAuth (clientAuth) - { - if (!identity) return; - auto len = identity->GetSigningPublicKeyLen (); - m_PublicKey.resize (len); - memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); - m_SigType = identity->GetSigningKeyType (); - if (m_SigType == i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) - m_BlindedSigType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519; // 7 -> 11 - else - m_BlindedSigType = m_SigType; - } - - BlindedPublicKey::BlindedPublicKey (std::string_view b33): - m_SigType (0) // 0 means invalid, we can't blind DSA, set it later - { - uint8_t addr[40]; // TODO: define length from b33 - size_t l = i2p::data::Base32ToByteStream (b33, addr, 40); - if (l < 32) - { - LogPrint (eLogError, "Blinding: Malformed b33 ", b33); - return; - } - uint32_t checksum = crc32 (0, addr + 3, l - 3); - // checksum is Little Endian - addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); - uint8_t flags = addr[0]; - size_t offset = 1; - if (flags & B33_TWO_BYTES_SIGTYPE_FLAG) // two bytes signatures - { - m_SigType = bufbe16toh (addr + offset); offset += 2; - m_BlindedSigType = bufbe16toh (addr + offset); offset += 2; - } - else // one byte sig - { - m_SigType = addr[offset]; offset++; - m_BlindedSigType = addr[offset]; offset++; - } - m_IsClientAuth = flags & B33_PER_CLIENT_AUTH_FLAG; - - std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType)); - if (blindedVerifier) - { - auto len = blindedVerifier->GetPublicKeyLen (); - if (offset + len <= l) - { - m_PublicKey.resize (len); - memcpy (m_PublicKey.data (), addr + offset, len); - } - else - LogPrint (eLogError, "Blinding: Public key in b33 address is too short for signature type ", (int)m_SigType); - } - else - LogPrint (eLogError, "Blinding: Unknown signature type ", (int)m_SigType, " in b33"); - } - - std::string BlindedPublicKey::ToB33 () const - { - if (m_PublicKey.size () > 32) return ""; // assume 25519 - uint8_t addr[35]; - uint8_t flags = 0; - if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; - addr[0] = flags; // flags - addr[1] = m_SigType; // sig type - addr[2] = m_BlindedSigType; // blinded sig type - memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ()); - 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); - } - - void BlindedPublicKey::GetCredential (uint8_t * credential) const - { - // A = destination's signing public key - // stA = signature type of A, 2 bytes big endian - uint16_t stA = htobe16 (GetSigType ()); - // stA1 = signature type of blinded A, 2 bytes big endian - uint16_t stA1 = htobe16 (GetBlindedSigType ()); - // credential = H("credential", A || stA || stA1) - H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential); - } - - void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const - { - uint8_t credential[32]; - GetCredential (credential); - // subcredential = H("subcredential", credential || blindedPublicKey) - H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential); - } - - void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const - { - uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ()); - uint8_t salt[32]; - //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) - H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt); - i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed); - } - - size_t BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const - { - uint8_t seed[64]; - GenerateAlpha (date, seed); - - size_t publicKeyLength = 0; - switch (m_SigType) - { - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - publicKeyLength = BlindECDSA (m_SigType, GetPublicKey (), seed, BlindEncodedPublicKeyECDSA, blindedKey); - break; - case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: - case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); - publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; - break; - default: - LogPrint (eLogError, "Blinding: Can't blind signature type ", (int)m_SigType); - } - return publicKeyLength; - } - - size_t BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const - { - uint8_t seed[64]; - GenerateAlpha (date, seed); - size_t publicKeyLength = 0; - switch (m_SigType) - { - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - publicKeyLength = BlindECDSA (m_SigType, priv, seed, BlindEncodedPrivateKeyECDSA, blindedPriv, blindedPub); - break; - case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: - i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); - publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; - break; - case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - { - uint8_t exp[64]; - i2p::crypto::Ed25519::ExpandPrivateKey (priv, exp); - i2p::crypto::GetEd25519 ()->BlindPrivateKey (exp, seed, blindedPriv, blindedPub); - publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; - break; - } - default: - LogPrint (eLogError, "Blinding: Can't blind signature type ", (int)m_SigType); - } - return publicKeyLength; - } - - void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, p.c_str (), p.length ()); - for (const auto& it: bufs) - SHA256_Update (&ctx, it.first, it.second); - SHA256_Final (hash, &ctx); - } - - i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const - { - i2p::data::IdentHash hash; - uint8_t blinded[128]; - size_t publicKeyLength = 0; - if (date) - publicKeyLength = GetBlindedKey (date, blinded); - else - { - char currentDate[9]; - i2p::util::GetCurrentDate (currentDate); - publicKeyLength = GetBlindedKey (currentDate, blinded); - } - if (publicKeyLength) - { - auto stA1 = htobe16 (m_BlindedSigType); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, (const uint8_t *)&stA1, 2); - SHA256_Update (&ctx, blinded, publicKeyLength); - SHA256_Final ((uint8_t *)hash, &ctx); - } - else - LogPrint (eLogError, "Blinding: Blinded key type ", (int)m_BlindedSigType, " is not supported"); - return hash; - } - -} -} diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h deleted file mode 100644 index fc11f613..00000000 --- a/libi2pd/Blinding.h +++ /dev/null @@ -1,56 +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 BLINDING_H__ -#define BLINDING_H__ - -#include -#include -#include -#include -#include "Identity.h" - -namespace i2p -{ -namespace data -{ - class BlindedPublicKey // for encrypted LS2 - { - public: - - BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p - std::string ToB33 () const; - - const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; - size_t GetPublicKeyLen () const { return m_PublicKey.size (); }; - SigningKeyType GetSigType () const { return m_SigType; }; - SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; - bool IsValid () const { return GetSigType (); }; // signature type 0 means invalid - - void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes - size_t GetBlindedKey (const char * date, uint8_t * blindedKey) const; // date is 8 chars "YYYYMMDD", return public key length - size_t BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // date is 8 chars "YYYYMMDD", return public key length - i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null - - private: - - void GetCredential (uint8_t * credential) const; // 32 bytes - void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" - void H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const; - - private: - - std::vector m_PublicKey; - i2p::data::SigningKeyType m_SigType, m_BlindedSigType; - bool m_IsClientAuth = false; - }; -} -} - -#endif diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp new file mode 100644 index 00000000..b92039df --- /dev/null +++ b/libi2pd/BloomFilter.cpp @@ -0,0 +1,69 @@ +#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..2745fbf5 --- /dev/null +++ b/libi2pd/BloomFilter.h @@ -0,0 +1,31 @@ +#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..a707c3dc --- /dev/null +++ b/libi2pd/CPU.cpp @@ -0,0 +1,55 @@ +#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() + { +#if defined(__AES__) || defined(__AVX__) + +#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]); +#ifdef __AES__ + aesni = info[2] & bit_AES; // AESNI +#endif // __AES__ +#ifdef __AVX__ + avx = info[2] & bit_AVX; // AVX +#endif // __AVX__ + } +#endif // defined(__x86_64__) || defined(__i386__) + +#ifdef __AES__ + if(aesni) + { + LogPrint(eLogInfo, "AESNI enabled"); + } +#endif // __AES__ +#ifdef __AVX__ + if(avx) + { + LogPrint(eLogInfo, "AVX enabled"); + } +#endif // __AVX__ +#endif // defined(__AES__) || defined(__AVX__) + } +} +} diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index 3fc38d47..b4c19607 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,24 +1,15 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #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(); +} +} #endif diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp new file mode 100644 index 00000000..222111b7 --- /dev/null +++ b/libi2pd/ChaCha20.cpp @@ -0,0 +1,138 @@ +/* +* Copyright (c) 2013-2018, 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 + +} +} +#endif + diff --git a/libi2pd/ChaCha20.h b/libi2pd/ChaCha20.h new file mode 100644 index 00000000..b2eec320 --- /dev/null +++ b/libi2pd/ChaCha20.h @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2013-2018, 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 +} +} +} +#endif + +#endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 939cd9ff..52f40089 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2017, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,7 +19,6 @@ #include "Identity.h" #include "Config.h" #include "version.h" -#include "Log.h" using namespace boost::program_options; @@ -36,38 +35,34 @@ namespace config { ("version", "Show i2pd version") ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") - ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") - ("certsdir", value()->default_value(""), "Path to certificates used for verifying .su3, families (default: ~/.i2pd/certificates or /var/lib/i2pd/certificates") + ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") - ("loglevel", value()->default_value("warn"), "Set the minimal level of log messages (debug, info, warn, error, none)") + ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error, none)") ("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") - ("nat", bool_switch()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") + ("nat", value()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") - ("ipv4", bool_switch()->default_value(true), "Enable communication through ipv4 (default: enabled)") - ("address4", value()->default_value(""), "Local address to bind ipv4 transport sockets to") + ("ipv4", value()->default_value(true), "Enable communication through ipv4 (default: enabled)") ("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)") - ("address6", value()->default_value(""), "Local address to bind ipv6 transport sockets to") - ("reservedrange", bool_switch()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)") ("service", bool_switch()->default_value(false), "Router will use system folders like '/var/lib/i2pd' (default: disabled)") ("notransit", bool_switch()->default_value(false), "Router will not accept transit tunnels at startup (default: disabled)") ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") - ("bandwidth", value()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") + ("bandwidth", value()->default_value(""), "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") - ("ntcpproxy", value()->default_value(""), "Ignored") + ("ntcp", value()->default_value(true), "Enable NTCP transport (default: enabled)") + ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") + ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") #ifdef _WIN32 - ("svcctl", value()->default_value(""), "Ignored") + ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") #endif @@ -77,11 +72,10 @@ namespace config { limits.add_options() ("limits.coresize", value()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") ("limits.openfiles", value()->default_value(0), "Maximum number of open files (0 - use system default)") - ("limits.transittunnels", value()->default_value(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"); @@ -94,9 +88,7 @@ namespace config { ("http.pass", value()->default_value(""), "Password for basic auth (default: random, see logs)") ("http.strictheaders", value()->default_value(true), "Enable strict host checking on WebUI") ("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.webroot", value()->default_value("/"), "WebUI root path (default: / )") ; options_description httpproxy("HTTP Proxy options"); @@ -104,27 +96,16 @@ namespace config { ("httpproxy.enabled", value()->default_value(true), "Enable or disable HTTP Proxy") ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") ("httpproxy.port", value()->default_value(4444), "HTTP Proxy listen port") - ("httpproxy.keys", value()->default_value("transient-proxy"), "File to persist HTTP Proxy keys. Transient by default") - ("httpproxy.signaturetype", value()-> - default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") + ("httpproxy.keys", value()->default_value(""), "File to persist HTTP Proxy keys") + ("httpproxy.signaturetype", value()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("httpproxy.inbound.length", value()->default_value("3"), "HTTP proxy inbound tunnel length") ("httpproxy.outbound.length", value()->default_value("3"), "HTTP proxy outbound tunnel length") ("httpproxy.inbound.quantity", value()->default_value("5"), "HTTP proxy inbound tunnels quantity") ("httpproxy.outbound.quantity", value()->default_value("5"), "HTTP proxy outbound tunnels quantity") - ("httpproxy.inbound.lengthVariance", value()->default_value("0"), "HTTP proxy inbound tunnels length variance") - ("httpproxy.outbound.lengthVariance", value()->default_value("0"), "HTTP proxy outbound tunnels length variance") ("httpproxy.latency.min", value()->default_value("0"), "HTTP proxy min latency for tunnels") ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") - ("httpproxy.senduseragent", value()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by default") - ("httpproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") - ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") - ("httpproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") - ("httpproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec") - ("httpproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec") - ("httpproxy.i2p.streaming.profile", value()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") - ; options_description socksproxy("SOCKS Proxy options"); @@ -132,46 +113,24 @@ namespace config { ("socksproxy.enabled", value()->default_value(true), "Enable or disable SOCKS Proxy") ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") - ("socksproxy.keys", value()->default_value("transient-proxy"), "File to persist SOCKS Proxy keys. Transient by default") - ("socksproxy.signaturetype", value()-> - default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") + ("socksproxy.keys", value()->default_value(""), "File to persist SOCKS Proxy keys") + ("socksproxy.signaturetype", value()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("socksproxy.inbound.length", value()->default_value("3"), "SOCKS proxy inbound tunnel length") ("socksproxy.outbound.length", value()->default_value("3"), "SOCKS proxy outbound tunnel length") ("socksproxy.inbound.quantity", value()->default_value("5"), "SOCKS proxy inbound tunnels quantity") ("socksproxy.outbound.quantity", value()->default_value("5"), "SOCKS proxy outbound tunnels quantity") - ("socksproxy.inbound.lengthVariance", value()->default_value("0"), "SOCKS proxy inbound tunnels length variance") - ("socksproxy.outbound.lengthVariance", value()->default_value("0"), "SOCKS proxy outbound tunnels length variance") ("socksproxy.latency.min", value()->default_value("0"), "SOCKS proxy min latency for tunnels") ("socksproxy.latency.max", value()->default_value("0"), "SOCKS proxy max latency for tunnels") ("socksproxy.outproxy.enabled", value()->default_value(false), "Enable or disable SOCKS outproxy") ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") - ("socksproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") - ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") - ("socksproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") - ("socksproxy.i2p.streaming.maxOutboundSpeed", value()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec") - ("socksproxy.i2p.streaming.maxInboundSpeed", value()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec") - ("socksproxy.i2p.streaming.profile", value()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") ; - options_description shareddest("Shared local destination options"); - shareddest.add_options() - ("shareddest.inbound.length", value()->default_value("3"), "Shared local destination inbound tunnel length") - ("shareddest.outbound.length", value()->default_value("3"), "Shared local destination outbound tunnel length") - ("shareddest.inbound.quantity", value()->default_value("3"), "Shared local destination inbound tunnels quantity") - ("shareddest.outbound.quantity", value()->default_value("3"), "Shared local destination outbound tunnels quantity") - ("shareddest.i2cp.leaseSetType", value()->default_value("3"), "Shared local destination's LeaseSet type") - ("shareddest.i2cp.leaseSetEncType", value()->default_value("0,4"), "Shared local destination's LeaseSet encryption type") - ("shareddest.i2p.streaming.profile", value()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") - ; - options_description sam("SAM bridge options"); sam.add_options() ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") - ("sam.port", value()->default_value(7656), "SAM listen TCP port") - ("sam.portudp", value()->default_value(0), "SAM listen UDP port") - ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") + ("sam.port", value()->default_value(7656), "SAM listen port") ; options_description bob("BOB options"); @@ -186,9 +145,6 @@ namespace config { ("i2cp.enabled", value()->default_value(false), "Enable or disable I2CP") ("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"); @@ -214,7 +170,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), @@ -226,58 +182,48 @@ 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") ("reseed.urls", value()->default_value( - "https://reseed2.i2p.net/," - "https://reseed.diva.exchange/," + "https://reseed.i2p-projekt.de/," + "https://i2p.mooo.com/netDb/," + "https://netdb.i2p2.no/," + // "https://us.reseed.i2p2.no:444/," // mamoth's shit + // "https://uk.reseed.i2p2.no:444/," // mamoth's shit + "https://reseed.i2p.net.in/," + "https://download.xxlspeed.com/," "https://reseed-fr.i2pd.xyz/," + "https://reseed.atomike.ninja/," + "https://reseed.memcpy.io/," "https://reseed.onion.im/," + "https://itoopie.atomike.ninja/," "https://i2pseed.creativecowpat.net:8443/," - "https://reseed.i2pgit.org/," - "https://coconut.incognet.io/," - "https://reseed-pl.i2pd.xyz/," - "https://www2.mk16.de/," - "https://i2p.ghativega.in/," - "https://i2p.novg.net/," - "https://reseed.stormycloud.org/," - "https://cubicchaos.net:8443/" + "https://i2p.novg.net/" ), "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://[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" + "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt" ), "AddressBook subscription URL for initial setup") - ("addressbook.subscriptions", value()->default_value( - "http://reg.i2p/hosts.txt" - ), "AddressBook subscriptions URLs, separated by comma") - ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); + ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma"); options_description trust("Trust options"); trust.add_options() ("trust.enabled", value()->default_value(false), "Enable explicit trust options") - ("trust.family", value()->default_value(""), "Router Family to trust for first hops") + ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") ("trust.routers", value()->default_value(""), "Only Connect to these routers") ("trust.hidden", value()->default_value(false), "Should we hide our router from other routers?") ; - // Save deprecated websocket options for compatibility options_description websocket("Websocket Options"); websocket.add_options() - ("websockets.enabled", value()->default_value(false), "Deprecated option") - ("websockets.address", value()->default_value(""), "Deprecated option") - ("websockets.port", value()->default_value(0), "Deprecated option") + ("websockets.enabled", value()->default_value(false), "Enable websocket server") + ("websockets.address", value()->default_value("127.0.0.1"), "Address to bind websocket server on") + ("websockets.port", value()->default_value(7666), "Port to bind websocket server on") ; options_description exploratory("Exploratory Options"); @@ -290,69 +236,36 @@ namespace config { options_description ntcp2("NTCP2 Options"); ntcp2.add_options() - ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") - ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") - ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") - ("ntcp2.addressv6", value()->default_value("::"), "Address to publish NTCP2 with") - ("ntcp2.proxy", value()->default_value(""), "Proxy URL for NTCP2 transport") - ; - - 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)") - ("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") + ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") + ("ntcp2.published", value()->default_value(false), "Publish NTCP2 (default: disabled)") + ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") + ("ntcp2.addressv6", value()->default_value("::"), "Address to bind NTCP2 on") ; 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," "2.pool.ntp.org," "3.pool.ntp.org" - ), "Comma separated list of NTP servers") - ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") - ("nettime.frompeers", value()->default_value(true), "Sync clock from transport peers (default: enabled)") + ), "Comma separated list of NTCP servers") + ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") ; options_description persist("Network information persisting options"); persist.add_options() - ("persist.profiles", value()->default_value(true), "Persist peer profiles (default: true)") - ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") + ("persist.profiles", value()->default_value(true), "Persist peer profiles (default: true)") + ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; - options_description cpuext("CPU encryption extensions options. Deprecated"); - 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") - ; - - options_description meshnets("Meshnet transports options"); - meshnets.add_options() - ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (default: false)") - ("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) @@ -362,17 +275,11 @@ namespace config { .add(reseed) .add(addressbook) .add(trust) - .add(websocket) // deprecated + .add(websocket) .add(exploratory) .add(ntcp2) - .add(ssu2) .add(nettime) .add(persist) - .add(cpuext) - .add(meshnets) -#ifdef __linux__ - .add(unix_specific) -#endif ; } @@ -381,7 +288,7 @@ namespace config { try { auto style = boost::program_options::command_line_style::unix_style - | boost::program_options::command_line_style::allow_long_disguise; + | boost::program_options::command_line_style::allow_long_disguise; style &= ~ boost::program_options::command_line_style::allow_guessing; if (ignoreUnknown) store(command_line_parser(argc, argv).options(m_OptionsDesc).style (style).allow_unregistered().run(), m_Options); @@ -390,7 +297,6 @@ namespace config { } catch (boost::program_options::error& e) { - ThrowFatal ("Error while parsing arguments: ", e.what()); std::cerr << "args: " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -400,16 +306,16 @@ namespace config { std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl; std::cout << m_OptionsDesc; exit(EXIT_SUCCESS); - } + } else if (m_Options.count("version")) { std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl; - std::cout << "Boost version " + std::cout << "Boost version " << BOOST_VERSION / 100000 << "." // maj. version << BOOST_VERSION / 100 % 1000 << "." // min. version << BOOST_VERSION % 100 // patch version << std::endl; -#if defined(OPENSSL_VERSION_TEXT) +#if defined(OPENSSL_VERSION_TEXT) std::cout << OPENSSL_VERSION_TEXT << std::endl; #endif #if defined(LIBRESSL_VERSION_TEXT) @@ -428,7 +334,6 @@ namespace config { if (!config.is_open()) { - ThrowFatal ("Missing or unreadable config file: ", path); std::cerr << "missing/unreadable config file: " << path << std::endl; exit(EXIT_FAILURE); } @@ -439,7 +344,6 @@ namespace config { } catch (boost::program_options::error& e) { - ThrowFatal ("Error while parsing config file: ", e.what()); std::cerr << e.what() << std::endl; exit(EXIT_FAILURE); }; diff --git a/libi2pd/Config.h b/libi2pd/Config.h index 79463e65..679ae3bb 100644 --- a/libi2pd/Config.h +++ b/libi2pd/Config.h @@ -1,11 +1,3 @@ -/* -* 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 CONFIG_H #define CONFIG_H @@ -26,101 +18,98 @@ namespace i2p { namespace config { - extern boost::program_options::variables_map m_Options; + extern boost::program_options::variables_map m_Options; - /** - * @brief Initialize list of acceptable parameters - * - * Should be called before any Parse* functions. - */ - void Init(); + /** + * @brief Initialize list of acceptable parameters + * + * Should be called before any Parse* functions. + */ + void Init(); - /** - * @brief Parse cmdline parameters, and show help if requested - * @param argc Cmdline arguments count, should be passed from main(). - * @param argv Cmdline parameters array, should be passed from main() - * - * If --help is given in parameters, shows its list with description - * and terminates the program with exitcode 0. - * - * In case of parameter misuse boost throws an exception. - * We internally handle type boost::program_options::unknown_option, - * and then terminate the program with exitcode 1. - * - * Other exceptions will be passed to higher level. - */ - void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false); + /** + * @brief Parse cmdline parameters, and show help if requested + * @param argc Cmdline arguments count, should be passed from main(). + * @param argv Cmdline parameters array, should be passed from main() + * + * If --help is given in parameters, shows its list with description + * and terminates the program with exitcode 0. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate the program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false); - /** - * @brief Load and parse given config file - * @param path Path to config file - * - * If error occurred when opening file path is points to, - * we show the error message and terminate program. - * - * In case of parameter misuse boost throws an exception. - * We internally handle type boost::program_options::unknown_option, - * and then terminate program with exitcode 1. - * - * Other exceptions will be passed to higher level. - */ - void ParseConfig(const std::string& path); + /** + * @brief Load and parse given config file + * @param path Path to config file + * + * If error occurred when opening file path is points to, + * we show the error message and terminate program. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseConfig(const std::string& path); - /** - * @brief Used to combine options from cmdline, config and default values - */ - void Finalize(); + /** + * @brief Used to combine options from cmdline, config and default values + */ + void Finalize(); - /** - * @brief Accessor to parameters by name - * @param name Name of the requested parameter - * @param value Variable where to store option - * @return this function returns false if parameter not found - * - * Example: uint16_t port; GetOption("sam.port", port); - */ - template - bool GetOption(const char *name, T& value) - { - if (!m_Options.count(name)) - return false; - value = m_Options[name].as(); - return true; - } + /* @brief Accessor to parameters by name + * @param name Name of the requested parameter + * @param value Variable where to store option + * @return this function returns false if parameter not found + * + * Example: uint16_t port; GetOption("sam.port", port); + */ + template + bool GetOption(const char *name, T& value) { + if (!m_Options.count(name)) + return false; + value = m_Options[name].as(); + return true; + } - template - bool GetOption(const std::string& name, T& value) - { - return GetOption (name.c_str (), value); - } + template + bool GetOption(const std::string& name, T& value) + { + return GetOption (name.c_str (), value); + } - bool GetOptionAsAny(const char *name, boost::any& value); - bool GetOptionAsAny(const std::string& name, boost::any& value); + bool GetOptionAsAny(const char *name, boost::any& value); + bool GetOptionAsAny(const std::string& name, boost::any& value); - /** - * @brief Set value of given parameter - * @param name Name of settable parameter - * @param value New parameter value - * @return true if value set up successful, false otherwise - * - * Example: uint16_t port = 2827; SetOption("bob.port", port); - */ - template - bool SetOption(const char *name, const T& value) - { - if (!m_Options.count(name)) - return false; - m_Options.at(name).value() = value; - notify(m_Options); - return true; - } + /** + * @brief Set value of given parameter + * @param name Name of settable parameter + * @param value New parameter value + * @return true if value set up successful, false otherwise + * + * Example: uint16_t port = 2827; SetOption("bob.port", port); + */ + template + bool SetOption(const char *name, const T& value) { + if (!m_Options.count(name)) + return false; + m_Options.at(name).value() = value; + notify(m_Options); + return true; + } - /** - * @brief Check is value explicitly given or default - * @param name Name of checked parameter - * @return true if value set to default, false otherwise - */ - bool IsDefault(const char *name); + /** + * @brief Check is value explicitly given or default + * @param name Name of checked parameter + * @return true if value set to default, false otherwise + */ + bool IsDefault(const char *name); } } diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index c41b4c10..cd6d5b8e 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -19,11 +11,10 @@ #if OPENSSL_HKDF #include #endif -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#include +#if !OPENSSL_AEAD_CHACHA20_POLY1305 +#include "ChaCha20.h" +#include "Poly1305.h" #endif -#include "CPU.h" #include "Crypto.h" #include "Ed25519.h" #include "I2PEndian.h" @@ -33,7 +24,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, @@ -53,9 +44,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, @@ -67,13 +58,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, @@ -85,7 +76,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 { @@ -120,7 +111,7 @@ namespace crypto ~CryptoConstants () { - BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); + BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); } }; @@ -150,37 +141,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 (); @@ -188,14 +148,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; @@ -274,37 +231,95 @@ 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) - memcpy (m_PublicKey, pub, 32); // TODO: verify against m_Pkey - else - { - size_t len = 32; - EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); - } - } - + memcpy (m_PublicKey, pub, 32); // TODO: verify against m_Pkey +#else + memcpy (m_PrivateKey, priv, 32); + memcpy (m_PublicKey, pub, 32); + m_Ctx = BN_CTX_new (); +#endif + } + X25519Keys::~X25519Keys () { +#if OPENSSL_X25519 EVP_PKEY_CTX_free (m_Ctx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); - } + 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); + { + EVP_PKEY_free (m_Pkey); m_Pkey = nullptr; } EVP_PKEY_keygen_init (m_Ctx); @@ -313,44 +328,39 @@ 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) + void 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; EVP_PKEY_derive_set_peer (m_Ctx, pkey); size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); EVP_PKEY_free (pkey); - return true; - } - +#else + GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); +#endif + } + void X25519Keys::GetPrivateKey (uint8_t * priv) const { +#if OPENSSL_X25519 size_t len = 32; EVP_PKEY_get_raw_private_key (m_Pkey, priv, &len); - } - - void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic) - { - 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); - m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); - if (calculatePublic) - { - size_t len = 32; - EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); - } +#else + memcpy (priv, m_PrivateKey, 32); +#endif } // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted) + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { - BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); // everything, but a, because a might come from table BIGNUM * k = BN_CTX_get (ctx); @@ -358,7 +368,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 @@ -386,32 +396,37 @@ namespace crypto BN_bin2bn (m, 255, b); BN_mod_mul (b, b1, b, elgp, ctx); // copy a and b - encrypted[0] = 0; - bn2buf (a, encrypted + 1, 256); - encrypted[257] = 0; - bn2buf (b, encrypted + 258, 256); - + if (zeroPadding) + { + encrypted[0] = 0; + bn2buf (a, encrypted + 1, 256); + encrypted[257] = 0; + bn2buf (b, encrypted + 258, 256); + } + else + { + bn2buf (a, encrypted, 256); + bn2buf (b, encrypted + 256, 256); + } BN_free (a); BN_CTX_end (ctx); - BN_CTX_free (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data) + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, + uint8_t * data, BN_CTX * ctx, bool zeroPadding) { - BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx); BN_bin2bn (key, 256, x); BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 - BN_bin2bn (encrypted + 1, 256, a); - BN_bin2bn (encrypted + 258, 256, b); + BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); + BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); // m = b*(a^x mod p) mod p BN_mod_exp (x, a, x, elgp, ctx); BN_mod_mul (b, b, x, elgp, ctx); uint8_t m[255]; bn2buf (b, m, 255); BN_CTX_end (ctx); - BN_CTX_free (ctx); uint8_t hash[32]; SHA256 (m + 33, 222, hash); if (memcmp (m + 1, hash, 32)) @@ -425,7 +440,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 @@ -445,9 +460,8 @@ namespace crypto } // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted) + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { - BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -459,11 +473,20 @@ namespace crypto EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); - 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 + if (zeroPadding) + { + encrypted[0] = 0; + bn2buf (x, encrypted + 1, len); + bn2buf (y, encrypted + 1 + len, len); + RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); + } + else + { + bn2buf (x, encrypted, len); + bn2buf (y, encrypted + len, len); + RAND_bytes (encrypted + 2*len, 256 - 2*len); + } + // ecryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); uint8_t keyBuf[64], iv[64], shared[32]; @@ -478,25 +501,37 @@ namespace crypto // encrypt CBCEncryption encryption; encryption.SetKey (shared); - encrypted[257] = 0; - encryption.Encrypt (m, 256, iv, encrypted + 258); + encryption.SetIV (iv); + if (zeroPadding) + { + encrypted[257] = 0; + encryption.Encrypt (m, 256, encrypted + 258); + } + else + encryption.Encrypt (m, 256, encrypted + 256); EC_POINT_free (p); BN_CTX_end (ctx); - BN_CTX_free (ctx); } - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data) + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { bool ret = true; - BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); int len = BN_num_bytes (q); // point for shared secret BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - BN_bin2bn (encrypted + 1, len, x); - BN_bin2bn (encrypted + 1 + len, len, y); + if (zeroPadding) + { + BN_bin2bn (encrypted + 1, len, x); + BN_bin2bn (encrypted + 1 + len, len, y); + } + else + { + BN_bin2bn (encrypted, len, x); + BN_bin2bn (encrypted + len, len, y); + } auto p = EC_POINT_new (curve); if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) { @@ -512,7 +547,11 @@ namespace crypto uint8_t m[256]; CBCDecryption decryption; decryption.SetKey (shared); - decryption.Decrypt (encrypted + 258, 256, iv, m); + decryption.SetIV (iv); + if (zeroPadding) + decryption.Decrypt (encrypted + 258, 256, m); + else + decryption.Decrypt (encrypted + 256, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -532,7 +571,6 @@ namespace crypto EC_POINT_free (p); BN_CTX_end (ctx); - BN_CTX_free (ctx); return ret; } @@ -549,115 +587,515 @@ 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 +#ifdef __AVX__ + 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 () +#ifdef __AES__ + #ifdef ARM64AES + void init_aesenc(void){ + // TODO: Implementation + } + + #endif + + #define KeyExpansion256(round0,round1) \ + "pshufd $0xff, %%xmm2, %%xmm2 \n" \ + "movaps %%xmm1, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm1 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm1 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm1 \n" \ + "pxor %%xmm2, %%xmm1 \n" \ + "movaps %%xmm1, "#round0"(%[sched]) \n" \ + "aeskeygenassist $0, %%xmm1, %%xmm4 \n" \ + "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ + "movaps %%xmm3, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm3 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm3 \n" \ + "pslldq $4, %%xmm4 \n" \ + "pxor %%xmm4, %%xmm3 \n" \ + "pxor %%xmm2, %%xmm3 \n" \ + "movaps %%xmm3, "#round1"(%[sched]) \n" +#endif + +#ifdef __AES__ + void ECBCryptoAESNI::ExpandKey (const AESKey& key) { - m_Ctx = EVP_CIPHER_CTX_new (); + __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 + ); } - - ECBEncryption::~ECBEncryption () +#endif + + +#ifdef __AES__ + #define EncryptAES256(sched) \ + "pxor (%["#sched"]), %%xmm0 \n" \ + "aesenc 16(%["#sched"]), %%xmm0 \n" \ + "aesenc 32(%["#sched"]), %%xmm0 \n" \ + "aesenc 48(%["#sched"]), %%xmm0 \n" \ + "aesenc 64(%["#sched"]), %%xmm0 \n" \ + "aesenc 80(%["#sched"]), %%xmm0 \n" \ + "aesenc 96(%["#sched"]), %%xmm0 \n" \ + "aesenc 112(%["#sched"]), %%xmm0 \n" \ + "aesenc 128(%["#sched"]), %%xmm0 \n" \ + "aesenc 144(%["#sched"]), %%xmm0 \n" \ + "aesenc 160(%["#sched"]), %%xmm0 \n" \ + "aesenc 176(%["#sched"]), %%xmm0 \n" \ + "aesenc 192(%["#sched"]), %%xmm0 \n" \ + "aesenc 208(%["#sched"]), %%xmm0 \n" \ + "aesenclast 224(%["#sched"]), %%xmm0 \n" +#endif + + void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out) { - 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__ + 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); + } } - ECBDecryption::ECBDecryption () +#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) { - m_Ctx = EVP_CIPHER_CTX_new (); +#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); + } } - - ECBDecryption::~ECBDecryption () + +#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) { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out) +#ifdef __AES__ + if(i2p::cpu::aesni) + { + ExpandKey (key); + } + else +#endif + { + AES_set_encrypt_key (key, 256, &m_Key); + } + } + + void ECBDecryption::SetKey (const AESKey& key) { - EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); - EVP_CIPHER_CTX_set_padding (m_Ctx, 0); - int len; - EVP_DecryptUpdate (m_Ctx, out, &len, in, 16); - EVP_DecryptFinal_ex (m_Ctx, out + len, &len); +#ifdef __AES__ + 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); + } } - CBCEncryption::CBCEncryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - CBCEncryption::~CBCEncryption () + void CBCEncryption::Encrypt (int numBlocks, 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 (%[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); @@ -665,20 +1103,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)); @@ -687,267 +1116,167 @@ 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(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) - 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); + { + 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); - } - - - ChaCha20Context::ChaCha20Context () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - ChaCha20Context::~ChaCha20Context () - { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) - { - ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out); - } - - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, - uint8_t * out, size_t outLen) - { -#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()); - if (key && keyLen) - { - EVP_PKEY_CTX_set1_hkdf_salt (pctx, salt, 32); - EVP_PKEY_CTX_set1_hkdf_key (pctx, key, keyLen); - } - else - { - // zerolen - EVP_PKEY_CTX_hkdf_mode (pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY); - uint8_t tempKey[32]; unsigned int len; - HMAC(EVP_sha256(), salt, 32, nullptr, 0, tempKey, &len); - EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); - } - if (info.length () > 0) - EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), info.length ()); - EVP_PKEY_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); - } + chacha::Chacha20State state; + chacha::Chacha20Init (state, nonce, key, 1); + if (out != msg) memcpy (out, msg, msgLen); + chacha::Chacha20Encrypt (state, out, msgLen); #endif } -// Noise - - void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub) + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out) { - // pub is Bob's public static key, hh = SHA256(h) - memcpy (m_CK, ck, 32); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, pub, 32); - SHA256_Final (m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) - m_N = 0; - } - - void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); +#if OPENSSL_HKDF + EVP_PKEY_CTX * pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_HKDF, NULL); + EVP_PKEY_derive_init (pctx); + EVP_PKEY_CTX_set_hkdf_md (pctx, EVP_sha256()); + EVP_PKEY_CTX_set1_hkdf_salt (pctx, salt, 32); + EVP_PKEY_CTX_set1_hkdf_key (pctx, key, keyLen); + if (info.length () > 0) + EVP_PKEY_CTX_add1_hkdf_info (pctx, info.c_str (), info.length ()); + size_t outlen = 64; + 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); + memcpy (out + 32, info.c_str (), l); out[l + 32] = 0x02; + HMAC(EVP_sha256(), prk, 32, out, l + 33, out + 32, &len); +#endif } - void NoiseSymmetricState::MixHash (const std::vector >& bufs) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - for (const auto& it: bufs) - 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) - { - uint8_t nonce[12]; - if (m_N) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, m_N); - } - else - memset (nonce, 0, 12); - auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len + 16, true); - if (ret) m_N++; - return ret; - } - - bool NoiseSymmetricState::Decrypt (const uint8_t * in, uint8_t * out, size_t len) - { - uint8_t nonce[12]; - if (m_N) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, m_N); - } - else - memset (nonce, 0, 12); - auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len, false); - if (ret) m_N++; - return ret; - } - - void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) - { - static constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - static constexpr uint8_t hh[32] = - { - 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, - 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 - }; // hh = SHA256(protocol_name || 0) - state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 - } - - void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) - { - static constexpr uint8_t protocolNameHash[32] = - { - 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, - 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 - }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") - static constexpr uint8_t hh[32] = - { - 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, - 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e - }; // SHA256 (protocolNameHash) - state.Init (protocolNameHash, hh, pub); - } - - void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) - { - static constexpr uint8_t protocolNameHash[32] = - { - 0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4, - 0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32 - }; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256") - static constexpr uint8_t hh[32] = - { - 0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53, - 0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33 - }; // SHA256 (protocolNameHash) - state.Init (protocolNameHash, hh, pub); - } - - void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) - { - static constexpr uint8_t protocolNameHash[32] = - { - 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, - 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c - }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes - static constexpr uint8_t hh[32] = - { - 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, - 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c - }; // SHA256 (protocolNameHash) - state.Init (protocolNameHash, hh, pub); - } - // init and terminate -/* std::vector > m_OpenSSLMutexes; +/* std::vector > m_OpenSSLMutexes; static void OpensslLockingCallback(int mode, int type, const char * file, int line) { if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ()) @@ -959,15 +1288,20 @@ namespace crypto } }*/ + void InitCrypto (bool precomputation) { + i2p::cpu::Detect (); +#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 @@ -982,7 +1316,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 @@ -995,3 +1329,4 @@ namespace crypto } } } + diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 125a217c..5afc4c52 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,11 +1,3 @@ -/* -* 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 CRYPTO_H__ #define CRYPTO_H__ @@ -21,21 +13,29 @@ #include #include #include +#include #include #include "Base.h" #include "Tag.h" +#include "CPU.h" // recognize openssl version and features -#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 +#if ((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 # define OPENSSL_HKDF 1 -# define OPENSSL_EDDSA 1 -# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL -# define OPENSSL_SIPHASH 1 -# endif -# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0 -# define OPENSSL_PQ 1 -# endif +# if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 +# define OPENSSL_EDDSA 1 +# define OPENSSL_X25519 1 +# define OPENSSL_SIPHASH 1 +# endif +# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them +# define OPENSSL_AEAD_CHACHA20_POLY1305 1 +# endif #endif namespace i2p @@ -45,116 +45,209 @@ 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 { public: X25519Keys (); - X25519Keys (const uint8_t * priv, const uint8_t * pub); // if pub is null, derive from priv + X25519Keys (const uint8_t * priv, const uint8_t * pub); // for RouterContext ~X25519Keys (); void GenerateKeys (); const uint8_t * GetPublicKey () const { return m_PublicKey; }; void GetPrivateKey (uint8_t * priv) const; - void SetPrivateKey (const uint8_t * priv, bool calculatePublic = false); - bool Agree (const uint8_t * pub, uint8_t * shared); - - bool IsElligatorIneligible () const { return m_IsElligatorIneligible; } - void SetElligatorIneligible () { m_IsElligatorIneligible = true; } + void Agree (const uint8_t * pub, uint8_t * shared); private: - uint8_t m_PublicKey[32]; + uint8_t m_PublicKey[32]; +#if OPENSSL_X25519 EVP_PKEY_CTX * m_Ctx; EVP_PKEY * m_Pkey; - bool m_IsElligatorIneligible = false; // true if definitely ineligible +#else + BN_CTX * m_Ctx; + uint8_t m_PrivateKey[32]; +#endif }; - + // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); 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__ + #ifdef ARM64AES + void init_aesenc(void) __attribute__((constructor)); + #endif + 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 @@ -194,88 +287,96 @@ 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, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out); // salt - 32, out - 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 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 e37d4039..711d4ce6 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "Log.h" #include "Gost.h" @@ -20,9 +12,9 @@ namespace crypto memcpy (m_PublicKey, pub, 256); } - void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) + void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { - ElGamalEncrypt (m_PublicKey, data, encrypted); + ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding); } ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) @@ -30,9 +22,9 @@ namespace crypto memcpy (m_PrivateKey, priv, 256); } - bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) + bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { - return ElGamalDecrypt (m_PrivateKey, encrypted, data); + return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding); } ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub) @@ -52,10 +44,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) + void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding); } ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) @@ -70,10 +62,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) + bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding); return false; } @@ -112,10 +104,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) + void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding); } ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) @@ -128,10 +120,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) + bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding); return false; } @@ -154,48 +146,6 @@ namespace crypto BN_free (x); BN_free (y); } - ECIESX25519AEADRatchetEncryptor::ECIESX25519AEADRatchetEncryptor (const uint8_t * pub) - { - memcpy (m_PublicKey, pub, 32); - } - - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub) - { - memcpy (pub, m_PublicKey, 32); - } - - ECIESX25519AEADRatchetDecryptor::ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic) - { - m_StaticKeys.SetPrivateKey (priv, calculatePublic); - } - - bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret) - { - return m_StaticKeys.Agree (epub, sharedSecret); - } - - void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub) - { - X25519Keys k; - k.GenerateKeys (); - 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 b6c37ddf..5584b687 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,17 +1,8 @@ -/* -* 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 CRYPTO_KEY_H__ #define CRYPTO_KEY_H__ #include #include "Crypto.h" -#include "Identity.h" namespace i2p { @@ -22,7 +13,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) = 0; + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted }; class CryptoKeyDecryptor @@ -30,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data) = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 }; @@ -40,7 +31,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted) override; // 222 bytes data, 514 bytes encrypted + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); private: @@ -52,9 +43,9 @@ namespace crypto public: ElGamalDecryptor (const uint8_t * priv); - bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; // 514 bytes encrypted, 222 bytes data - size_t GetPublicKeyLen () const override { return 256; }; - + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); + size_t GetPublicKeyLen () const { return 256; }; + private: uint8_t m_PrivateKey[256]; @@ -68,7 +59,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); private: @@ -83,9 +74,9 @@ namespace crypto ECIESP256Decryptor (const uint8_t * priv); ~ECIESP256Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; - size_t GetPublicKeyLen () const override { return 64; }; - + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); + size_t GetPublicKeyLen () const { return 64; }; + private: EC_GROUP * m_Curve; @@ -102,7 +93,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); private: @@ -116,93 +107,17 @@ namespace crypto ECIESGOSTR3410Decryptor (const uint8_t * priv); ~ECIESGOSTR3410Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; - size_t GetPublicKeyLen () const override { return 64; }; - + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); + size_t GetPublicKeyLen () const { return 64; }; + private: BIGNUM * m_PrivateKey; }; void CreateECIESGOSTR3410RandomKeys (uint8_t * priv, uint8_t * pub); - -// ECIES-X25519-AEAD-Ratchet - - class ECIESX25519AEADRatchetEncryptor: public CryptoKeyEncryptor - { - public: - - ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); - ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub) override; - // copies m_PublicKey to pub - - private: - - uint8_t m_PublicKey[32]; - }; - - class ECIESX25519AEADRatchetDecryptor: public CryptoKeyDecryptor - { - public: - - ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); - ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret) override; - // agree with static and return in sharedSecret (32 bytes) - size_t GetPublicKeyLen () const override { return 32; }; - const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; - - private: - - 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 32; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; - // ML-KEM hybrid - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: - return 32; - }; - return 0; - } - - constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type) - { - switch (type) - { - case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; - // ML-KEM hybrid - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: - return 32; - }; - return 0; - } - - struct LocalEncryptionKey - { - std::vector pub, priv; - i2p::data::CryptoKeyType keyType; - std::shared_ptr decryptor; - - LocalEncryptionKey (i2p::data::CryptoKeyType t); - void GenerateKeys (); - void CreateDecryptor (); - }; } } #endif + diff --git a/libi2pd/CryptoWorker.h b/libi2pd/CryptoWorker.h new file mode 100644 index 00000000..d43e356c --- /dev/null +++ b/libi2pd/CryptoWorker.h @@ -0,0 +1,81 @@ +#ifndef CRYPTO_WORKER_H_ +#define CRYPTO_WORKER_H_ + +#include +#include +#include +#include +#include +#include + +namespace i2p +{ +namespace worker +{ + template + struct ThreadPool + { + typedef std::function ResultFunc; + typedef std::function WorkFunc; + typedef std::pair, WorkFunc> Job; + typedef std::mutex mtx_t; + typedef std::unique_lock lock_t; + typedef std::condition_variable cond_t; + ThreadPool(int workers) + { + stop = false; + if(workers > 0) + { + while(workers--) + { + threads.emplace_back([this] { + for (;;) + { + Job job; + { + lock_t lock(this->queue_mutex); + this->condition.wait( + lock, [this] { return this->stop || !this->jobs.empty(); }); + if (this->stop && this->jobs.empty()) return; + job = std::move(this->jobs.front()); + this->jobs.pop_front(); + } + ResultFunc result = job.second(); + job.first->GetService().post(result); + } + }); + } + } + }; + + void Offer(const Job & job) + { + { + lock_t lock(queue_mutex); + if (stop) return; + jobs.emplace_back(job); + } + condition.notify_one(); + } + + ~ThreadPool() + { + { + lock_t lock(queue_mutex); + stop = true; + } + condition.notify_all(); + for(auto &t: threads) t.join(); + } + + std::vector threads; + std::deque jobs; + mtx_t queue_mutex; + cond_t condition; + bool stop; + }; +} +} + + +#endif diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 732efca7..ed87a054 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,12 +1,5 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include +#include #include "Crypto.h" #include "Log.h" #include "TunnelBase.h" @@ -18,16 +11,11 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): - m_Owner (owner), m_DefaultReceiver (nullptr), m_DefaultRawReceiver (nullptr), m_Gzip (gzip) + DatagramDestination::DatagramDestination (std::shared_ptr owner): + m_Owner (owner.get()), + m_Receiver (nullptr) { - if (m_Gzip) - m_Deflator.reset (new i2p::data::GzipDeflator); - - auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); - m_From.resize (identityLen); - m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); - m_Signature.resize (m_Owner->GetIdentity ()->GetSignatureLen ()); + m_Identity.FromBase64 (owner->GetIdentity()->ToBase64()); } DatagramDestination::~DatagramDestination () @@ -37,53 +25,30 @@ namespace datagram void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - auto session = ObtainSession(identity); - SendDatagram (session, payload, len, fromPort, toPort); - FlushSendQueue (session); - } + auto owner = m_Owner; + std::vector v(MAX_DATAGRAM_SIZE); + uint8_t * buf = v.data(); + auto identityLen = m_Identity.ToBuffer (buf, MAX_DATAGRAM_SIZE); + uint8_t * signature = buf + identityLen; + auto signatureLen = m_Identity.GetSignatureLen (); + uint8_t * buf1 = signature + signatureLen; + size_t headerLen = identityLen + signatureLen; - void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) - { - auto session = ObtainSession(identity); - SendRawDatagram (session, payload, len, fromPort, toPort); - FlushSendQueue (session); - } - - std::shared_ptr DatagramDestination::GetSession(const i2p::data::IdentHash & ident) - { - return ObtainSession(ident); - } - - void DatagramDestination::SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) - { - if (session) + memcpy (buf1, payload, len); + if (m_Identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { - 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 ()); - - 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); + uint8_t hash[32]; + SHA256(buf1, len, hash); + owner->Sign (hash, 32, signature); } + else + owner->Sign (buf1, len, signature); + + auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); + auto session = ObtainSession(identity); + session->SendMsg(msg); } - void DatagramDestination::SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) - { - if (session) - session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw - } - - void DatagramDestination::FlushSendQueue (std::shared_ptr session) - { - if (session) - session->FlushSendQueue (); - } void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { @@ -104,7 +69,8 @@ namespace datagram if (verified) { - auto session = ObtainSession (identity.GetIdentHash()); + auto h = identity.GetIdentHash(); + auto session = ObtainSession(h); session->Ack(); auto r = FindReceiver(toPort); if(r) @@ -116,122 +82,41 @@ namespace datagram LogPrint (eLogWarning, "Datagram signature verification failed"); } - 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); - else - LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram"); - } - - void DatagramDestination::SetReceiver (const Receiver& receiver, uint16_t port) - { - std::lock_guard lock(m_ReceiversMutex); - m_ReceiversByPorts[port] = receiver; - if (!m_DefaultReceiver) { - m_DefaultReceiver = receiver; - m_DefaultReceiverPort = port; - } - } - - void DatagramDestination::ResetReceiver (uint16_t port) - { - std::lock_guard lock(m_ReceiversMutex); - m_ReceiversByPorts.erase (port); - if (m_DefaultReceiverPort == port) { - m_DefaultReceiver = nullptr; - m_DefaultReceiverPort = 0; - } - } - - - void DatagramDestination::SetRawReceiver (const RawReceiver& receiver, uint16_t port) - { - std::lock_guard lock(m_RawReceiversMutex); - m_RawReceiversByPorts[port] = receiver; - if (!m_DefaultRawReceiver) { - m_DefaultRawReceiver = receiver; - m_DefaultRawReceiverPort = port; - } - } - - void DatagramDestination::ResetRawReceiver (uint16_t port) - { - std::lock_guard lock(m_RawReceiversMutex); - m_RawReceiversByPorts.erase (port); - if (m_DefaultRawReceiverPort == port) { - m_DefaultRawReceiver = nullptr; - m_DefaultRawReceiverPort = 0; - } - } - - DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port) { std::lock_guard lock(m_ReceiversMutex); - Receiver r = 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, bool isRaw) + void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { // unzip it uint8_t uncompressed[MAX_DATAGRAM_SIZE]; size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE); if (uncompressedLen) - { - if (isRaw) - HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen); - else - HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); - } + HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); else LogPrint (eLogWarning, "Datagram: decompression failed"); } - std::shared_ptr DatagramDestination::CreateDataMessage ( - const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) + std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { - size_t size; - auto msg = m_I2NPMsgsPool.AcquireShared (); + auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - - if (m_Gzip && m_Deflator) - size = m_Deflator->Deflate (payloads, buf, msg->maxLen - msg->len); - else - size = i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); - + size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, fromPort); // source port htobe16buf (buf + 6, toPort); // destination port - buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol + buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol msg->len += size + 4; - msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); + msg->FillI2NPMessageHeader (eI2NPData); } else msg = nullptr; @@ -285,10 +170,11 @@ namespace datagram return nullptr; } - DatagramSession::DatagramSession(std::shared_ptr localDestination, - const i2p::data::IdentHash & remoteIdent) : - m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), - m_LastUse (0), m_LastFlush (0), + DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, + const i2p::data::IdentHash & remoteIdent) : + m_LocalDestination(localDestination), + m_RemoteIdent(remoteIdent), + m_SendQueueTimer(localDestination->GetService()), m_RequestingLS(false) { } @@ -296,25 +182,21 @@ namespace datagram void DatagramSession::Start () { m_LastUse = i2p::util::GetMillisecondsSinceEpoch (); + ScheduleFlushSendQueue(); } void DatagramSession::Stop () { + m_SendQueueTimer.cancel (); } void DatagramSession::SendMsg(std::shared_ptr msg) { // we used this session m_LastUse = i2p::util::GetMillisecondsSinceEpoch(); - 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) - { - FlushSendQueue(); - m_LastFlush = m_LastUse; - } + // schedule send + auto self = shared_from_this(); + m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, self, msg)); } DatagramSession::Info DatagramSession::GetSessionInfo() const @@ -346,140 +228,97 @@ namespace datagram auto path = GetSharedRoutingPath(); 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 } std::shared_ptr DatagramSession::GetSharedRoutingPath () { - if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) - { - m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); - if (!m_RemoteLeaseSet) - { - if(!m_RequestingLS) - { + if(!m_RoutingSession) { + if(!m_RemoteLeaseSet) { + m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); + } + if(!m_RemoteLeaseSet) { + // no remote lease set + if(!m_RequestingLS) { m_RequestingLS = true; m_LocalDestination->RequestDestination(m_RemoteIdent, std::bind(&DatagramSession::HandleLeaseSetUpdated, this, std::placeholders::_1)); } return nullptr; } + m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); } - - if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) - { - bool found = false; - for (auto& it: m_PendingRoutingSessions) - if (it->GetOwner () && m_RoutingSession->IsReadyToSend ()) // found established session - { - m_RoutingSession = it; - m_PendingRoutingSessions.clear (); - found = true; - break; - } - 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); - } - } - } - auto path = m_RoutingSession->GetSharedRoutingPath(); - if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) - { - LogPrint (eLogDebug, "Datagram: path reset"); - m_RoutingSession->SetSharedRoutingPath (nullptr); - path = nullptr; - } - - if (path) - { - if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) - { + if(path) { + if (m_CurrentOutboundTunnel && !m_CurrentOutboundTunnel->IsEstablished()) { // bad outbound tunnel, switch outbound tunnel - path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(path->outboundTunnel); - if (!path->outboundTunnel) - m_RoutingSession->SetSharedRoutingPath (nullptr); + m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel); + path->outboundTunnel = m_CurrentOutboundTunnel; } - - if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) - { + if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { // bad lease, switch to next one - if (m_RemoteLeaseSet) - { - auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( - [&](const i2p::data::Lease& l) -> bool - { - return l.tunnelID == path->remoteLease->tunnelID; - }); + if(m_RemoteLeaseSet && m_RemoteLeaseSet->IsExpired()) + m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); + if(m_RemoteLeaseSet) { + auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&](const i2p::data::Lease& l) -> bool { + return l.tunnelID == m_CurrentRemoteLease->tunnelID; + }); 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; - path->remoteLease = ls[idx]; + if (sz) { + auto idx = rand() % sz; + m_CurrentRemoteLease = ls[idx]; } - else - m_RoutingSession->SetSharedRoutingPath (nullptr); - } - else - { + } else { // no remote lease set? LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32()); - m_RoutingSession->SetSharedRoutingPath (nullptr); } + path->remoteLease = m_CurrentRemoteLease; } - } - else - { + } else { // no current path, make one path = std::make_shared(); - - if (m_RemoteLeaseSet) - { - // pick random next good lease - auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); - 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; - path->remoteLease = ls[idx]; + // switch outbound tunnel if bad + if(m_CurrentOutboundTunnel == nullptr || ! m_CurrentOutboundTunnel->IsEstablished()) { + m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel); + } + // switch lease if bad + if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { + if(!m_RemoteLeaseSet) { + m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); } - else + if(m_RemoteLeaseSet) { + // pick random next good lease + auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&] (const i2p::data::Lease & l) -> bool { + if(m_CurrentRemoteLease) + return l.tunnelGateway == m_CurrentRemoteLease->tunnelGateway; + return false; + }); + auto sz = ls.size(); + if(sz) { + auto idx = rand() % sz; + m_CurrentRemoteLease = ls[idx]; + } + } else { + // no remote lease set currently, bail + LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); return nullptr; - - auto leaseRouter = i2p::data::netdb.FindRouter (path->remoteLease->tunnelGateway); - path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(nullptr, - leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); - if (!path->outboundTunnel) return nullptr; - } - else - { - // no remote lease set currently, bail - LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); - return nullptr; + } + } else if (!m_CurrentRemoteLease) { + if(!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); + if (m_RemoteLeaseSet) + { + auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); + auto sz = ls.size(); + if (sz) { + auto idx = rand() % sz; + m_CurrentRemoteLease = ls[idx]; + } + } } + path->outboundTunnel = m_CurrentOutboundTunnel; + path->remoteLease = m_CurrentRemoteLease; m_RoutingSession->SetSharedRoutingPath(path); } return path; + } void DatagramSession::HandleLeaseSetUpdated(std::shared_ptr ls) @@ -492,9 +331,16 @@ namespace datagram if(ls && ls->GetExpirationTime() > oldExpire) m_RemoteLeaseSet = ls; } + void DatagramSession::HandleSend(std::shared_ptr msg) + { + m_SendQueue.push_back(msg); + // flush queue right away if full + if(m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); + } + void DatagramSession::FlushSendQueue () { - if (m_SendQueue.empty ()) return; + std::vector send; auto routingPath = GetSharedRoutingPath(); // if we don't have a routing path we will drop all queued messages @@ -503,12 +349,21 @@ namespace datagram for (const auto & msg : m_SendQueue) { auto m = m_RoutingSession->WrapSingleMessage(msg); - if (m) - send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, 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(); + ScheduleFlushSendQueue(); + } + + void DatagramSession::ScheduleFlushSendQueue() + { + boost::posix_time::milliseconds dlt(10); + m_SendQueueTimer.expires_from_now(dlt); + auto self = shared_from_this(); + m_SendQueueTimer.async_wait([self](const boost::system::error_code & ec) { if(ec) return; self->FlushSendQueue(); }); } } } + diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index dd358434..039417ea 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,11 +1,3 @@ -/* -* 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 DATAGRAM_H__ #define DATAGRAM_H__ @@ -13,9 +5,7 @@ #include #include #include -#include #include "Base.h" -#include "Gzip.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" @@ -31,6 +21,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 @@ -39,32 +31,25 @@ namespace datagram const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000; // milliseconds minimum time between path switches 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 + // max 64 messages buffered in send queue for each datagram session + const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; class DatagramSession : public std::enable_shared_from_this { + public: + DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent); - public: - - DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent); - - void Start (); - void Stop (); + void Start (); + void Stop (); - /** @brief ack the garlic routing path */ - void Ack(); + /** @brief ack the garlic routing path */ + void Ack(); - /** send an i2np message to remote endpoint for this session */ - void SendMsg(std::shared_ptr msg); - void FlushSendQueue(); - /** 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 (); } + /** send an i2np message to remote endpoint for this session */ + void SendMsg(std::shared_ptr msg); + /** get the last time in milliseconds for when we used this datagram session */ + uint64_t LastActivity() const { return m_LastUse; } struct Info { @@ -72,34 +57,40 @@ namespace datagram std::shared_ptr OBEP; const uint64_t activity; - Info() : IBGW(nullptr), OBEP(nullptr), activity(0) {} - Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a) : - activity(a) { - if(ibgw) IBGW = std::make_shared(ibgw); - else IBGW = nullptr; - if(obep) OBEP = std::make_shared(obep); - else OBEP = nullptr; - } - }; + Info() : IBGW(nullptr), OBEP(nullptr), activity(0) {} + Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a) : + activity(a) { + if(ibgw) IBGW = std::make_shared(ibgw); + else IBGW = nullptr; + if(obep) OBEP = std::make_shared(obep); + else OBEP = nullptr; + } + }; - Info GetSessionInfo() const; + Info GetSessionInfo() const; - private: + private: - std::shared_ptr GetSharedRoutingPath(); + void FlushSendQueue(); + void ScheduleFlushSendQueue(); - void HandleLeaseSetUpdated(std::shared_ptr ls); + void HandleSend(std::shared_ptr msg); - private: + std::shared_ptr GetSharedRoutingPath(); - std::shared_ptr m_LocalDestination; - i2p::data::IdentHash m_RemoteIdent; - std::shared_ptr m_RemoteLeaseSet; - std::shared_ptr m_RoutingSession; - std::vector > m_PendingRoutingSessions; - std::vector > m_SendQueue; - uint64_t m_LastUse, m_LastFlush; // milliseconds - bool m_RequestingLS; + void HandleLeaseSetUpdated(std::shared_ptr ls); + + private: + i2p::client::ClientDestination * m_LocalDestination; + i2p::data::IdentHash m_RemoteIdent; + std::shared_ptr m_RemoteLeaseSet; + std::shared_ptr m_RoutingSession; + std::shared_ptr m_CurrentRemoteLease; + std::shared_ptr m_CurrentOutboundTunnel; + boost::asio::deadline_timer m_SendQueueTimer; + std::vector > m_SendQueue; + uint64_t m_LastUse; + bool m_RequestingLS; }; typedef std::shared_ptr DatagramSession_ptr; @@ -108,30 +99,21 @@ namespace datagram class DatagramDestination { typedef std::function Receiver; - typedef std::function RawReceiver; public: - DatagramDestination (std::shared_ptr owner, bool gzip); + + DatagramDestination (std::shared_ptr owner); ~DatagramDestination (); - void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); - void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); - // TODO: implement calls from other thread from SAM + void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - std::shared_ptr GetSession(const i2p::data::IdentHash & ident); - void SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); - 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 SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; + void ResetReceiver () { m_Receiver = nullptr; }; - 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 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); }; std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); @@ -140,38 +122,26 @@ namespace datagram private: - std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); + std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); - std::shared_ptr CreateDataMessage (const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); - void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + /** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */ Receiver FindReceiver(uint16_t port); - RawReceiver FindRawReceiver(uint16_t port); private: - - std::shared_ptr m_Owner; - + i2p::client::ClientDestination * m_Owner; + i2p::data::IdentityEx m_Identity; + Receiver m_Receiver; // default 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 i2p::data::GzipInflator m_Inflator; - std::unique_ptr m_Deflator; - std::vector m_From, m_Signature; - i2p::util::MemoryPool > m_I2NPMsgsPool; + i2p::data::GzipDeflator m_Deflator; }; } } diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index fd23e228..ba90342c 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,44 +1,29 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include -#include -#include -#include #include "Crypto.h" -#include "ECIESX25519AEADRatchetSession.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" #include "NetDb.hpp" #include "Destination.h" +#include "util.h" namespace i2p { namespace client { - LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service, - bool isPublic, const std::map * params): - m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), - m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), + LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map * params): + m_IsRunning (false), m_Thread (nullptr), m_IsPublic (isPublic), + m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_PublishDelayTimer (m_Service), m_CleanupTimer (m_Service), - m_LeaseSetType (DEFAULT_LEASESET_TYPE), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE) + m_LeaseSetType (DEFAULT_LEASESET_TYPE) { int inLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int inQty = DEFAULT_INBOUND_TUNNELS_QUANTITY; int outLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int outQty = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; - 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 { @@ -56,19 +41,10 @@ namespace client it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); if (it != params->end ()) outQty = std::stoi(it->second); - it = params->find (I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE); - if (it != params->end ()) - inVar = std::stoi(it->second); - it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE); - if (it != params->end ()) - outVar = std::stoi(it->second); it = params->find (I2CP_PARAM_TAGS_TO_SEND); if (it != params->end ()) numTags = std::stoi(it->second); - LogPrint (eLogInfo, "Destination: Parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); - it = params->find (I2CP_PARAM_RATCHET_INBOUND_TAGS); - if (it != params->end ()) - SetNumRatchetInboundTags (std::stoi(it->second)); + LogPrint (eLogInfo, "Destination: parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); it = params->find (I2CP_PARAM_EXPLICIT_PEERS); if (it != params->end ()) { @@ -91,49 +67,17 @@ namespace client if (it != params->end ()) m_Nickname = it->second; // otherwise we set default nickname in Start when we know local address } - it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); - if (it != params->end ()) - { - // override isPublic - m_IsPublic = (it->second != "true"); - } it = params->find (I2CP_PARAM_LEASESET_TYPE); if (it != params->end ()) m_LeaseSetType = std::stoi(it->second); - if (m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - { - // authentication for encrypted LeaseSet - it = params->find (I2CP_PARAM_LEASESET_AUTH_TYPE); - if (it != params->end ()) - { - auto authType = std::stoi (it->second); - 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); - } - } - it = params->find (I2CP_PARAM_LEASESET_PRIV_KEY); - if (it != params->end ()) - { - m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>()); - if (m_LeaseSetPrivKey->FromBase64 (it->second) != 32) - { - LogPrint(eLogCritical, "Destination: Invalid value i2cp.leaseSetPrivKey: ", it->second); - m_LeaseSetPrivKey.reset (nullptr); - } - } - it = params->find (I2CP_PARAM_STREAMING_PROFILE); - if (it != params->end ()) - isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE; } } catch (std::exception & ex) { - LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); + 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); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -146,7 +90,7 @@ namespace client auto minlatency = std::stoi(itr->second); if ( minlatency > 0 && maxlatency > 0 ) { // set tunnel pool latency - LogPrint(eLogInfo, "Destination: Requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]"); + LogPrint(eLogInfo, "Destination: requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]"); m_Pool->RequireLatency(minlatency, maxlatency); } } @@ -156,47 +100,89 @@ namespace client LeaseSetDestination::~LeaseSetDestination () { + if (m_IsRunning) + Stop (); if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); for (auto& it: m_LeaseSetRequests) it.second->Complete (nullptr); } - void LeaseSetDestination::Start () + void LeaseSetDestination::Run () { - if (m_Nickname.empty ()) - m_Nickname = i2p::data::GetIdentHashAbbreviation (GetIdentHash ()); // set default nickname - 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.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, - shared_from_this (), std::placeholders::_1)); + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Destination: runtime exception: ", ex.what ()); + } + } } - void LeaseSetDestination::Stop () + bool LeaseSetDestination::Start () { - m_CleanupTimer.cancel (); - m_PublishConfirmationTimer.cancel (); - m_PublishVerificationTimer.cancel (); - if (m_Pool) + if (!m_IsRunning) { - m_Pool->SetLocalDestination (nullptr); - i2p::tunnel::tunnels.StopTunnelPool (m_Pool); + if (m_Nickname.empty ()) + m_Nickname = i2p::data::GetIdentHashAbbreviation (GetIdentHash ()); // set default nickname + LoadTags (); + m_IsRunning = true; + m_Pool->SetLocalDestination (shared_from_this ()); + m_Pool->SetActive (true); + 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)); + m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); + + return true; } - SaveTags (); - CleanUp (); // GarlicDestination + else + return false; + } + + bool LeaseSetDestination::Stop () + { + if (m_IsRunning) + { + m_CleanupTimer.cancel (); + m_PublishConfirmationTimer.cancel (); + m_PublishVerificationTimer.cancel (); + + m_IsRunning = false; + if (m_Pool) + { + m_Pool->SetLocalDestination (nullptr); + i2p::tunnel::tunnels.StopTunnelPool (m_Pool); + } + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = 0; + } + SaveTags (); + CleanUp (); // GarlicDestination + return true; + } + else + return false; } bool LeaseSetDestination::Reconfigure(std::map params) { + auto itr = params.find("i2cp.dontPublishLeaseSet"); if (itr != params.end()) { 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}, @@ -214,7 +200,7 @@ namespace client outQuant = pool->GetNumOutboundTunnels(); minLatency = 0; maxLatency = 0; - + for (auto & opt : intOpts) { itr = params.find(opt.first); @@ -226,7 +212,7 @@ namespace client pool->RequireLatency(minLatency, maxLatency); return pool->Reconfigure(inLen, outLen, inQuant, outQuant); } - + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { std::shared_ptr remoteLS; @@ -261,12 +247,23 @@ namespace client } else { - LogPrint (eLogWarning, "Destination: Remote LeaseSet expired"); + LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); std::lock_guard lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets.erase (ident); 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; } @@ -284,7 +281,7 @@ namespace client std::lock_guard l(m_LeaseSetMutex); return m_LeaseSet; } - + void LeaseSetDestination::SetLeaseSet (std::shared_ptr newLeaseSet) { { @@ -295,11 +292,11 @@ 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 (); - }); + }); } } @@ -307,11 +304,7 @@ namespace client { int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum - auto tunnels = m_Pool->GetInboundTunnels (numTunnels); - if (!tunnels.empty ()) - CreateNewLeaseSet (tunnels); - else - LogPrint (eLogInfo, "Destination: No inbound tunnels for LeaseSet"); + CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels)); } bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) @@ -323,104 +316,48 @@ 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); }); return true; } - void LeaseSetDestination::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) - { - struct - { - uint8_t k[32]; - uint64_t t; - } data; - memcpy (data.k, key, 32); - data.t = tag; - auto s = shared_from_this (); - boost::asio::post (m_Service, [s,data](void) - { - s->AddECIESx25519Key (data.k, data.t); - }); - } - 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 (), msg)); } - 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); - } - - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, - size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) + void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { + uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) { case eI2NPData: - HandleDataMessage (payload, len); + HandleDataMessage (buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); break; case eI2NPDeliveryStatus: - 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)); + // we assume tunnel tests non-encrypted + HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); break; case eI2NPDatabaseStore: - HandleDatabaseStoreMessage (payload, len, from); + HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); break; case eI2NPDatabaseSearchReply: - HandleDatabaseSearchReplyMessage (payload, len); - break; - case eI2NPShortTunnelBuildReply: // might come as garlic encrypted - i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID)); + HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); break; default: - LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID); - return false; + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); } - 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) @@ -428,14 +365,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 @@ -444,14 +375,13 @@ namespace client LogPrint (eLogDebug, "Destination: Remote LeaseSet"); std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (key); - if (it != m_RemoteLeaseSets.end () && - it->second->GetStoreType () == buf[DATABASE_STORE_TYPE_OFFSET]) // update only if same type + if (it != m_RemoteLeaseSets.end ()) { leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) { leaseSet->Update (buf + offset, len - offset); - if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else { @@ -465,25 +395,11 @@ namespace client } else { - // add or replace 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, 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); // LeaseSet2 + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) { if (leaseSet->GetIdentHash () != GetIdentHash ()) { @@ -504,60 +420,30 @@ 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); + if (ls2->IsValid ()) { - auto ls2 = std::make_shared (buf + offset, len - offset, - request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, - GetPreferredCryptoType ()); - if (ls2->IsValid () && !ls2->IsExpired ()) - { - leaseSet = ls2; - std::lock_guard lock(m_RemoteLeaseSetsMutex); - m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key - m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup - } - else - LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed"); - } - else - { - // publishing verification doesn't have requestedBlindedKey - auto localLeaseSet = GetLeaseSetMt (); - if (localLeaseSet->GetStoreHash () == key) - { - auto ls = std::make_shared (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, - localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false); - leaseSet = ls; - } - else - LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key"); + 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); } } @@ -570,66 +456,58 @@ 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) + { + for (int i = 0; i < num; i++) { i2p::data::IdentHash peerHash (buf + 33 + i*32); if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); - i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message + i2p::data::netdb.RequestDestination (peerHash); } } - SendNextLeaseSetRequest (key, 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); + } } 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) + void LeaseSetDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { + uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) { LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed for ", GetIdentHash().ToBase32()); 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)); } else - i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); + i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg); } - 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 () @@ -655,70 +533,33 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills); + auto outbound = m_Pool->GetNextOutboundTunnel (); + if (!outbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); + return; + } + auto inbound = m_Pool->GetNextInboundTunnel (); + if (!inbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + return; + } + auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); m_ExcludedFloodfills.clear (); return; } - auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); - 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->GetStoreHash (), m_ExcludedFloodfills); - if (floodfill) - { - outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); - if (outbound) - { - inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); - if (!inbound) - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); - } - else - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); - } - else - LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); - } - else - LogPrint (eLogDebug, "Destination: No tunnels in pool"); - - if (!floodfill || !outbound || !inbound) - { - // we can't publish now - m_ExcludedFloodfills.clear (); - m_PublishReplyToken = 1; // dummy non-zero value - // try again after a while - LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds"); - m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); - m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, - shared_from_this (), std::placeholders::_1)); - return; - } - } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); 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)); + auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); + 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; } @@ -731,15 +572,15 @@ namespace client m_PublishReplyToken = 0; if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); + 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, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + 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 + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max + 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)); @@ -755,19 +596,20 @@ namespace client auto ls = GetLeaseSetMt (); if (!ls) { - LogPrint (eLogWarning, "Destination: Couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); + LogPrint (eLogWarning, "Destination: couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); return; - } + } auto s = shared_from_this (); + // we must capture this for gcc 4.7 due the bug RequestLeaseSet (ls->GetStoreHash (), - [s, ls](std::shared_ptr leaseSet) + [s, ls, this](std::shared_ptr leaseSet) { if (leaseSet) { if (*ls == *leaseSet) { // we got latest LeasetSet - LogPrint (eLogDebug, "Destination: Published LeaseSet verified for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", s->GetIdentHash().ToBase32()); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; @@ -776,7 +618,7 @@ namespace client LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", s->GetIdentHash().ToBase32()); } else - LogPrint (eLogWarning, "Destination: Couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32()); // we have to publish again s->Publish (); }); @@ -794,10 +636,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; } @@ -806,25 +648,25 @@ 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 (); - auto leaseSet = FindLeaseSet (storeHash); + auto leaseSet = FindLeaseSet (storeHash); 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 ()) @@ -844,7 +686,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) { @@ -852,24 +694,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 @@ -893,41 +727,29 @@ namespace client } bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, - std::shared_ptr nextFloodfill, std::shared_ptr request) + 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 (); + 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 (); + if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) { request->excluded.insert (nextFloodfill->GetIdentHash ()); request->requestTimeoutTimer.cancel (); - bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) && - nextFloodfill->GetVersion () >= MAKE_VERSION_NUMBER(0, 9, 46); // >= 0.9.46; uint8_t replyKey[32], replyTag[32]; RAND_bytes (replyKey, 32); // random session key - RAND_bytes (replyTag, isECIES ? 8 : 32); // random session tag - if (isECIES) - AddECIESx25519Key (replyKey, replyTag); - else - AddSessionKey (replyKey, replyTag); + RAND_bytes (replyTag, 32); // random session tag + 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 = WrapMessage (nextFloodfill, + CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, + request->replyTunnel, replyKey, replyTag)); + request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock { @@ -935,7 +757,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)); } @@ -952,7 +774,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); @@ -968,7 +790,7 @@ namespace client } else { - LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); done = true; } @@ -989,8 +811,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)); } @@ -1004,7 +825,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 @@ -1012,120 +833,37 @@ namespace client } } - ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, - bool isPublic, const std::map * params): - LeaseSetDestination (service, isPublic, params), - m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), - m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED), - m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED), - m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS), - m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0), - m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0), - m_ReadyChecker(service) + ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): + LeaseSetDestination (isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_DatagramDestination (nullptr), m_RefCounter (0), + m_ReadyChecker(GetService()) { 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 + m_EncryptionKeyType = GetIdentity ()->GetCryptoKeyType (); // extract encryption type params for LS2 - std::set encryptionKeyTypes; - if (params) + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2 && params) { auto it = params->find (I2CP_PARAM_LEASESET_ENCRYPTION_TYPE); if (it != params->end ()) - { - // comma-separated values - std::vector values; - boost::split(values, it->second, boost::is_any_of(",")); - for (auto& it1: values) - { - 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); - } - } - catch (std::exception& ex) - { - LogPrint (eLogInfo, "Destination: Unexpected crypto type ", it1, ". ", ex.what ()); - continue; - } - } - } - } - // if no param or valid crypto type use from identity - if (encryptionKeyTypes.empty ()) - encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (), - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4 - - for (auto& it: encryptionKeyTypes) - { - auto encryptionKey = std::make_shared (it); - if (IsPublic ()) - PersistTemporaryKeys (encryptionKey); - 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 (IsPublic ()) + m_EncryptionKeyType = std::stoi(it->second); + } + + if (isPublic && m_EncryptionKeyType == GetIdentity ()->GetCryptoKeyType ()) // TODO: presist key type + PersistTemporaryKeys (); + else + i2p::data::PrivateKeys::GenerateCryptoKeyPair (m_EncryptionKeyType, m_EncryptionPrivateKey, m_EncryptionPublicKey); + m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_EncryptionKeyType, m_EncryptionPrivateKey); + if (isPublic) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); - try + // extract streaming params + if (params) { - if (params) - { - // extract streaming params - auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); - if (it != params->end ()) - m_StreamingAckDelay = std::stoi(it->second); - it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED); - if (it != params->end ()) - m_StreamingOutboundSpeed = std::stoi(it->second); - it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED); - if (it != params->end ()) - m_StreamingInboundSpeed = std::stoi(it->second); - if (it != params->end ()) - m_StreamingMaxConcurrentStreams = std::stoi(it->second); - it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); - if (it != params->end ()) - m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true - - if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - { - // authentication for encrypted LeaseSet - auto authType = GetAuthType (); - if (authType > 0) - { - m_AuthKeys = std::make_shared >(); - if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_DH) - ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params); - 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); - 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); - m_AuthKeys = nullptr; - } - } - } - } - } - catch (std::exception & ex) - { - LogPrint(eLogCritical, "Destination: Unable to parse parameters for destination: ", ex.what()); + auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); + if (it != params->end ()) + m_StreamingAckDelay = std::stoi(it->second); } } @@ -1133,43 +871,71 @@ namespace client { } - void ClientDestination::Start () + bool ClientDestination::Start () { - LeaseSetDestination::Start (); - m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: - m_StreamingDestination->Start (); - for (auto& it: m_StreamingDestinationsByPorts) - it.second->Start (); + if (LeaseSetDestination::Start ()) + { + m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: + m_StreamingDestination->Start (); + for (auto& it: m_StreamingDestinationsByPorts) + it.second->Start (); + return true; + } + else + return false; } - void ClientDestination::Stop () + bool ClientDestination::Stop () { - LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p"); - m_ReadyChecker.cancel(); - LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination"); - m_StreamingDestination->Stop (); - //m_StreamingDestination->SetOwner (nullptr); - m_StreamingDestination = nullptr; - - LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination by ports"); - for (auto& it: m_StreamingDestinationsByPorts) + if (LeaseSetDestination::Stop ()) { - it.second->Stop (); - //it.second->SetOwner (nullptr); + m_ReadyChecker.cancel(); + m_StreamingDestination->Stop (); + //m_StreamingDestination->SetOwner (nullptr); + m_StreamingDestination = nullptr; + for (auto& it: m_StreamingDestinationsByPorts) + { + it.second->Stop (); + //it.second->SetOwner (nullptr); + } + m_StreamingDestinationsByPorts.clear (); + if (m_DatagramDestination) + { + delete m_DatagramDestination; + m_DatagramDestination = nullptr; + } + return true; } - 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"); + else + return false; } +#ifdef I2LUA + void ClientDestination::Ready(ReadyPromise & p) + { + ScheduleCheckForReady(&p); + } + + void ClientDestination::ScheduleCheckForReady(ReadyPromise * p) + { + // tick every 100ms + m_ReadyChecker.expires_from_now(boost::posix_time::milliseconds(100)); + m_ReadyChecker.async_wait([&, p] (const boost::system::error_code & ecode) { + HandleCheckForReady(ecode, p); + }); + } + + void ClientDestination::HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p) + { + if(ecode) // error happened + p->set_value(nullptr); + else if(IsReady()) // we are ready + p->set_value(std::shared_ptr(this)); + else // we are not ready + ScheduleCheckForReady(p); + } +#endif + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); @@ -1187,15 +953,9 @@ 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); + auto dest = GetStreamingDestination (toPort); + if (dest) + dest->HandleDataMessagePayload (buf, length); else LogPrint (eLogError, "Destination: Missing streaming destination"); } @@ -1207,34 +967,21 @@ namespace client 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]); + 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) { - LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream"); + LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); return; } 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 (); @@ -1249,11 +996,11 @@ 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) { - LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream"); + LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); return; } auto s = GetSharedFromThis (); @@ -1267,42 +1014,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); @@ -1310,36 +1022,7 @@ namespace client return nullptr; } - void ClientDestination::SendPing (const i2p::data::IdentHash& to) - { - if (m_StreamingDestination) - { - auto leaseSet = FindLeaseSet (to); - if (leaseSet) - m_StreamingDestination->SendPing (leaseSet); - else - { - auto s = m_StreamingDestination; - RequestDestination (to, - [s](std::shared_ptr ls) - { - if (ls) s->SendPing (ls); - }); - } - } - } - - void ClientDestination::SendPing (std::shared_ptr to) - { - auto s = m_StreamingDestination; - RequestDestinationWithEncryptedLeaseSet (to, - [s](std::shared_ptr ls) - { - if (ls) s->SendPing (ls); - }); - } - - std::shared_ptr ClientDestination::GetStreamingDestination (uint16_t port) const + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const { if (port) { @@ -1347,9 +1030,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) @@ -1377,7 +1059,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) @@ -1387,25 +1069,10 @@ namespace client return dest; } - std::shared_ptr ClientDestination::RemoveStreamingDestination (uint16_t port) - { - if (port) - { - auto it = m_StreamingDestinationsByPorts.find (port); - if (it != m_StreamingDestinationsByPorts.end ()) - { - auto ret = it->second; - m_StreamingDestinationsByPorts.erase (it); - return ret; - } - } - return nullptr; - } - - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () { if (m_DatagramDestination == nullptr) - m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); return m_DatagramDestination; } @@ -1423,110 +1090,50 @@ namespace client return ret; } - void ClientDestination::PersistTemporaryKeys (std::shared_ptr keys) + void ClientDestination::PersistTemporaryKeys () { - 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", (ident + ".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 *)m_EncryptionPublicKey, 256); + f.read ((char *)m_EncryptionPrivateKey, 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 ()); - keys->GenerateKeys (); - + LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p"); + memset (m_EncryptionPrivateKey, 0, 256); + memset (m_EncryptionPublicKey, 0, 256); + i2p::data::PrivateKeys::GenerateCryptoKeyPair (GetIdentity ()->GetCryptoKeyType (), m_EncryptionPrivateKey, m_EncryptionPublicKey); + 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 *)m_EncryptionPublicKey, 256); + f1.write ((char *)m_EncryptionPrivateKey, 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) + void ClientDestination::CreateNewLeaseSet (std::vector > tunnels) { 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 ()) - { - leaseSet = std::make_shared (GetIdentity (), it->second->pub.data (), tunnels); - // sign - Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); - } - else - LogPrint (eLogError, "Destinations: Wrong encryption key type for LeaseSet type 1"); + leaseSet = std::make_shared (GetIdentity (), m_EncryptionPublicKey, tunnels); + // sign + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); } 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 - } - bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; + auto keyLen = m_Decryptor ? m_Decryptor->GetPublicKeyLen () : 256; auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, - m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted); - if (isPublishedEncrypted) // encrypt if type 5 - ls2 = std::make_shared (ls2, m_Keys, GetAuthType (), m_AuthKeys); + m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels); + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) // encrypt if type 5 + ls2 = std::make_shared (ls2, m_Keys); leaseSet = ls2; - m_LastPublishedTimestamp = publishedTimestamp; } SetLeaseSet (leaseSet); } @@ -1536,111 +1143,13 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->CleanUp (); } - bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const + bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) 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 (m_Decryptor) + return m_Decryptor->Decrypt (encrypted, data, ctx, true); else - LogPrint (eLogError, "Destinations: Decryptor is not set"); + LogPrint (eLogError, "Destinations: decryptor is not set"); return false; } - - 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 - } - - 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; - } - - void ClientDestination::ReadAuthKey (const std::string& group, const std::map * params) - { - for (auto it: *params) - if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group)) - { - auto pos = it.second.find (':'); - if (pos != std::string::npos) - { - i2p::data::AuthPublicKey pubKey; - 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)); - } - } - } - - bool ClientDestination::DeleteStream (uint32_t recvStreamID) - { - if (m_StreamingDestination->DeleteStream (recvStreamID)) - return true; - for (auto it: m_StreamingDestinationsByPorts) - if (it.second->DeleteStream (recvStreamID)) - return true; - return false; - } - - RunnableClientDestination::RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - RunnableService ("Destination"), - ClientDestination (GetIOService (), keys, isPublic, params) - { - if (!GetNickname ().empty ()) - RunnableService::SetName (GetNickname ()); - } - - RunnableClientDestination::~RunnableClientDestination () - { - if (IsRunning ()) - Stop (); - } - - void RunnableClientDestination::Start () - { - if (!IsRunning ()) - { - ClientDestination::Start (); - StartIOService (); - } - } - - void RunnableClientDestination::Stop () - { - if (IsRunning ()) - { - ClientDestination::Stop (); - StopIOService (); - } - } - } } diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 35557859..b55a7490 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,34 +1,25 @@ -/* -* 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 DESTINATION_H__ #define DESTINATION_H__ -#include #include #include #include #include -#include -#include +#include #include #include +#ifdef I2LUA +#include +#endif #include #include "Identity.h" #include "TunnelPool.h" #include "Crypto.h" -#include "CryptoKey.h" #include "LeaseSet.h" #include "Garlic.h" #include "NetDb.hpp" #include "Streaming.h" #include "Datagram.h" -#include "util.h" namespace i2p { @@ -37,15 +28,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 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 @@ -57,26 +46,15 @@ namespace client const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5; const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity"; const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5; - const char I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE[] = "inbound.lengthVariance"; - const int DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE = 0; - const char I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE[] = "outbound.lengthVariance"; - const int DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE = 0; const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers"; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; const int DEFAULT_TAGS_TO_SEND = 40; - const char I2CP_PARAM_RATCHET_INBOUND_TAGS[] = "crypto.ratchet.inboundTags"; - const char I2CP_PARAM_RATCHET_OUTBOUND_TAGS[] = "crypto.ratchet.outboundTags"; // not used yet const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; - const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; - const int DEFAULT_LEASESET_TYPE = 3; + const int DEFAULT_LEASESET_TYPE = 1; const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; - const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 - const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; - const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn - const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn // latency const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min"; @@ -87,19 +65,7 @@ namespace client // streaming const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds - const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec - const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s - const char I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED[] = "i2p.streaming.maxInboundSpeed"; // bytes/sec - const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s - const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; - const int DEFAULT_ANSWER_PINGS = true; - const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile"; - const int STREAMING_PROFILE_BULK = 1; // high bandwidth - const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth - const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK; - const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams"; - const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048; - + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, @@ -109,8 +75,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; @@ -125,120 +91,114 @@ namespace client } }; + public: - LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map * params = nullptr); + LeaseSetDestination (bool isPublic, const std::map * params = nullptr); ~LeaseSetDestination (); const std::string& GetNickname () const { return m_Nickname; }; - auto& GetService () { return m_Service; }; - virtual void Start (); - virtual void Stop (); + virtual bool Start (); + virtual bool Stop (); /** i2cp reconfigure */ virtual bool Reconfigure(std::map i2cpOpts); - + + bool IsRunning () const { return m_IsRunning; }; + boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); - bool RequestDestinationWithEncryptedLeaseSet (std::shared_ptr dest, RequestComplete requestComplete = nullptr); + bool RequestDestinationWithEncryptedLeaseSet (std::shared_ptr dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify = true); 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; } + void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); // 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 IsPublic () const { return m_IsPublic; }; - void SetPublic (bool pub) { m_IsPublic = pub; }; + bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); + void ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatusMessage (std::shared_ptr msg); + void SetLeaseSetUpdated (); 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 SetLeaseSet (std::shared_ptr newLeaseSet); int GetLeaseSetType () const { return m_LeaseSetType; }; void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; }; - int GetAuthType () const { return m_AuthType; }; virtual void CleanupDestination () {}; // additional clean up in derived classes - virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0; // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; - virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; - + virtual void CreateNewLeaseSet (std::vector > tunnels) = 0; + private: + void Run (); void UpdateLeaseSet (); std::shared_ptr GetLeaseSetMt (); void Publish (); 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 HandleDeliveryStatusMessage (std::shared_ptr msg); 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); + bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, 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 (); private: - boost::asio::io_context& m_Service; + volatile bool m_IsRunning; + std::thread * m_Thread; + 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; std::string m_Nickname; - int m_LeaseSetType, m_AuthType; - std::unique_ptr > m_LeaseSetPrivKey; // non-null if presented + int m_LeaseSetType; public: // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; const decltype(m_RemoteLeaseSets)& GetLeaseSets () const { return m_RemoteLeaseSets; }; - bool IsEncryptedLeaseSet () const { return m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; }; - bool IsPerClientAuth () const { return m_AuthType > 0; }; }; class ClientDestination: public LeaseSetDestination { public: +#ifdef I2LUA + // type for informing that a client destination is ready + typedef std::promise > ReadyPromise; + // informs promise with shared_from_this() when this destination is ready to use + // if cancelled before ready, informs promise with nullptr + void Ready(ReadyPromise & p); +#endif - ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, - bool isPublic, const std::map * params = nullptr); + ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); - void Start () override; - void Stop () override; + virtual bool Start (); + virtual bool 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); }; @@ -249,96 +209,62 @@ 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; // 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 SendPing (const i2p::data::IdentHash& to); - void SendPing (std::shared_ptr to); + 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 AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); int GetStreamingAckDelay () const { return m_StreamingAckDelay; } - int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; } - int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; } - int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; } - bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (); // 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, BN_CTX * ctx) const; + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; 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) override; - void CreateNewLeaseSet (const std::vector >& tunnels) override; - + void HandleDataMessage (const uint8_t * buf, size_t len); + void CreateNewLeaseSet (std::vector > tunnels); + private: - std::shared_ptr GetSharedFromThis () { - return std::static_pointer_cast(shared_from_this ()); - } - void PersistTemporaryKeys (std::shared_ptr keys); - void ReadAuthKey (const std::string& group, const std::map * params); - - template - std::shared_ptr CreateStreamSync (const Dest& dest, uint16_t port); - + std::shared_ptr GetSharedFromThis () + { return std::static_pointer_cast(shared_from_this ()); } + void PersistTemporaryKeys (); +#ifdef I2LUA + void ScheduleCheckForReady(ReadyPromise * p); + void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p); +#endif 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; - bool m_IsStreamingAnswerPings; + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + i2p::data::CryptoKeyType m_EncryptionKeyType; + std::shared_ptr m_Decryptor; + + int m_StreamingAckDelay; 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; - std::shared_ptr > m_AuthKeys; // we don't need them for I2CP - public: // for HTTP only std::vector > GetAllStreams () const; - bool DeleteStream (uint32_t recvStreamID); }; - - class RunnableClientDestination: private i2p::util::RunnableService, public ClientDestination - { - public: - - RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); - ~RunnableClientDestination (); - - void Start (); - void Stop (); - }; - } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp deleted file mode 100644 index 08af4be3..00000000 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ /dev/null @@ -1,1427 +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 -#include -#include "Log.h" -#include "util.h" -#include "Crypto.h" -#include "PostQuantum.h" -#include "Elligator.h" -#include "Tag.h" -#include "I2PEndian.h" -#include "Timestamp.h" -#include "Tunnel.h" -#include "TunnelPool.h" -#include "Transports.h" -#include "ECIESX25519AEADRatchetSession.h" - -namespace i2p -{ -namespace garlic -{ - - void RatchetTagSet::DHInitialize (const uint8_t * rootKey, const uint8_t * k) - { - // DH_INITIALIZE(rootKey, k) - uint8_t keydata[64]; - i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) - memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_SessionTagKeyData); - // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) - memcpy (m_SymmKeyCK, (const uint8_t *)m_SessionTagKeyData + 32, 32); - m_NextSymmKeyIndex = 0; - } - - void RatchetTagSet::NextSessionTagRatchet () - { - i2p::crypto::HKDF (m_SessionTagKeyData, nullptr, 0, "STInitialization", m_SessionTagKeyData); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) - memcpy (m_SessTagConstant, (const uint8_t *)m_SessionTagKeyData + 32, 32); // SESSTAG_CONSTANT = keydata[32:63] - m_NextIndex = 0; - } - - uint64_t RatchetTagSet::GetNextSessionTag () - { - m_NextIndex++; - if (m_NextIndex >= 65535) - { - LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); - return 0; - } - i2p::crypto::HKDF (m_SessionTagKeyData, m_SessTagConstant, 32, "SessionTagKeyGen", m_SessionTagKeyData); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) - return m_SessionTagKeyData.GetLL ()[4]; // tag = keydata[32:39] - } - - void RatchetTagSet::GetSymmKey (int index, uint8_t * key) - { - if (index >= m_NextSymmKeyIndex) - { - auto num = index + 1 - m_NextSymmKeyIndex; - if (!m_NextSymmKeyIndex) - { - i2p::crypto::HKDF (m_SymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_0 = HKDF(symmKey_ck, SYMMKEY_CONSTANT, "SymmetricRatchet", 64) - m_NextSymmKeyIndex = 1; - num--; - } - for (int i = 0; i < num; i++) - { - i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); - if (i < num - 1) - m_ItermediateSymmKeys.emplace (m_NextSymmKeyIndex + i, m_CurrentSymmKeyCK + 32); - } - m_NextSymmKeyIndex += num; - memcpy (key, m_CurrentSymmKeyCK + 32, 32); - } - else - { - auto it = m_ItermediateSymmKeys.find (index); - if (it != m_ItermediateSymmKeys.end ()) - { - memcpy (key, it->second, 32); - m_ItermediateSymmKeys.erase (it); - } - else - LogPrint (eLogError, "Garlic: Missing symmetric key for index ", index); - } - } - - void RatchetTagSet::DeleteSymmKey (int index) - { - 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) - m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; - } - - bool ReceiveRatchetTagSet::IsExpired (uint64_t ts) const - { - return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; - } - - bool ReceiveRatchetTagSet::IsIndexExpired (int index) const - { - return index < m_TrimBehindIndex; - } - - bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) - { - auto session = GetSession (); - if (!session) return false; - 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) - { - memcpy (m_Key, key, 32); - Expire (); - } - - bool SymmetricKeyTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) - { - if (len < 24) return false; - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - size_t offset = 8; // first 8 bytes is reply tag used as AD - len -= 16; // poly1305 - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len - offset, buf, 8, m_Key, nonce, buf + offset, len - offset, false)) // decrypt - { - LogPrint (eLogWarning, "Garlic: Symmetric key tagset AEAD decryption failed"); - return false; - } - // we assume 1 I2NP block with delivery type local - if (offset + 3 > len) - { - LogPrint (eLogWarning, "Garlic: Symmetric key tagset is too short ", len); - return false; - } - if (buf[offset] != eECIESx25519BlkGalicClove) - { - LogPrint (eLogWarning, "Garlic: Symmetric key tagset unexpected block ", (int)buf[offset]); - return false; - } - offset++; - auto size = bufbe16toh (buf + offset); - offset += 2; - if (offset + size > len) - { - LogPrint (eLogWarning, "Garlic: Symmetric key tagset block is too long ", size); - return false; - } - if (m_Destination) - m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr); - return true; - } - - ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): - GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0) - { - if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); - RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; - } - - ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () - { - } - - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); - } - - bool ECIESX25519AEADRatchetSession::GenerateEphemeralKeysAndEncode (uint8_t * buf) - { - bool ineligible = false; - while (!ineligible) - { - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - ineligible = m_EphemeralKeys->IsElligatorIneligible (); - if (!ineligible) // we haven't tried it yet - { - if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys->GetPublicKey (), buf)) - return true; // success - // otherwise return back - m_EphemeralKeys->SetElligatorIneligible (); - i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); - } - else - i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); - } - // we still didn't find elligator eligible pair - for (int i = 0; i < 25; i++) - { - // create new - m_EphemeralKeys = std::make_shared(); - m_EphemeralKeys->GenerateKeys (); - if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys->GetPublicKey (), buf)) - return true; // success - else - { - // let NTCP2 use it - m_EphemeralKeys->SetElligatorIneligible (); - i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); - } - } - LogPrint (eLogError, "Garlic: Can't generate elligator eligible x25519 keys"); - return false; - } - - void ECIESX25519AEADRatchetSession::InitNewSessionTagset (std::shared_ptr tagsetNsr) const - { - uint8_t tagsetKey[32]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) - // Session Tag Ratchet - tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) - 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 - - if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) - { - LogPrint (eLogError, "Garlic: Can't decode elligator"); - return false; - } - buf += 32; len -= 32; - - 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 - { - i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), cryptoType, GetOwner ()->GetEncryptionPublicKey (cryptoType)); // bpk - MixHash (m_Aepk, 32); // h = SHA256(h || aepk) - - if (GetOwner ()->Decrypt (m_Aepk, sharedSecret, cryptoType)) // x25519(bsk, aepk) - { - MixKey (sharedSecret); - - auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType); - std::vector encapsKey(keyLen); - if (Decrypt (buf, encapsKey.data (), keyLen)) - { - decrypted = true; // encaps section has right hash - MixHash (buf, keyLen + 16); - buf += keyLen + 16; - len -= keyLen + 16; - - m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType); - m_PQKeys->SetPublicKey (encapsKey.data ()); - } - } - } -#endif - if (!decrypted) - { - if (cryptoType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || - GetOwner ()->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - { - cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; - i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk - MixHash (m_Aepk, 32); // h = SHA256(h || aepk) - - if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); - return false; - } - MixKey (sharedSecret); - } - else - { - LogPrint (eLogWarning, "Garlic: No supported encryption type"); - return false; - } - } - - // decrypt flags/static - uint8_t fs[32]; - if (!Decrypt (buf, fs, 32)) - { - LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); - return false; - } - MixHash (buf, 48); // h = SHA256(h || ciphertext) - buf += 48; len -= 48; // 32 data + 16 poly - - // KDF2 for payload - 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) - { - LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); - return false; - } - MixKey (sharedSecret); - } - - // decrypt payload - std::vector payload (len - 16); // we must save original ciphertext - if (!Decrypt (buf, payload.data (), len - 16)) - { - LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); - return false; - } - - m_State = eSessionStateNewSessionReceived; - if (isStatic) - { - MixHash (buf, len); // h = SHA256(h || ciphertext) - GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); - } - HandlePayload (payload.data (), len - 16, nullptr, 0); - - return true; - } - - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) - { - size_t offset = 0; - while (offset < len) - { - uint8_t blk = buf[offset]; - offset++; - auto size = bufbe16toh (buf + offset); - offset += 2; - LogPrint (eLogDebug, "Garlic: Block type ", (int)blk, " of size ", size); - if (size > len) - { - LogPrint (eLogError, "Garlic: Unexpected block length ", size); - break; - } - switch (blk) - { - case eECIESx25519BlkGalicClove: - if (GetOwner ()) - GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this); - break; - case eECIESx25519BlkNextKey: - LogPrint (eLogDebug, "Garlic: Next key"); - if (receiveTagset) - HandleNextKey (buf + offset, size, receiveTagset); - else - LogPrint (eLogError, "Garlic: Unexpected next key block"); - break; - case eECIESx25519BlkAck: - { - LogPrint (eLogDebug, "Garlic: Ack"); - int numAcks = size >> 2; // /4 - 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 - } - break; - } - case eECIESx25519BlkAckRequest: - { - LogPrint (eLogDebug, "Garlic: Ack request"); - if (receiveTagset) - m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); - break; - } - case eECIESx25519BlkTermination: - LogPrint (eLogDebug, "Garlic: Termination"); - if (GetOwner ()) - GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); - if (receiveTagset) receiveTagset->Expire (); - break; - case eECIESx25519BlkDateTime: - LogPrint (eLogDebug, "Garlic: Datetime"); - break; - case eECIESx25519BlkOptions: - LogPrint (eLogDebug, "Garlic: Options"); - break; - case eECIESx25519BlkPadding: - LogPrint (eLogDebug, "Garlic: Padding"); - break; - default: - LogPrint (eLogWarning, "Garlic: Unknown block type ", (int)blk); - } - offset += size; - } - } - - void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) - { - uint8_t flag = buf[0]; buf++; // flag - if (flag & ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG) - { - if (!m_SendForwardKey || !m_NextSendRatchet) return; - uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID - if (((!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) && keyID == m_NextSendRatchet->keyID) || - (m_NextSendRatchet->newKey && keyID == m_NextSendRatchet->keyID -1)) - { - if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) - memcpy (m_NextSendRatchet->remote, buf, 32); - uint8_t sharedSecret[32], tagsetKey[32]; - m_NextSendRatchet->key->Agree (m_NextSendRatchet->remote, sharedSecret); - i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared (); - newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); - newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); - newTagset->NextSessionTagRatchet (); - m_SendTagset = newTagset; - m_SendForwardKey = false; - LogPrint (eLogDebug, "Garlic: Next send tagset ", newTagset->GetTagSetID (), " created"); - } - else - LogPrint (eLogDebug, "Garlic: Unexpected next key ", keyID); - } - else - { - uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID - bool newKey = flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextReceiveRatchet) - m_NextReceiveRatchet.reset (new DHRatchet ()); - else - { - if (keyID == m_NextReceiveRatchet->keyID && newKey == m_NextReceiveRatchet->newKey) - { - LogPrint (eLogDebug, "Garlic: Duplicate ", newKey ? "new" : "old", " key ", keyID, " received"); - return; - } - m_NextReceiveRatchet->keyID = keyID; - } - if (newKey) - { - m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); - m_NextReceiveRatchet->newKey = true; - } - else - m_NextReceiveRatchet->newKey = false; - auto tagsetID = m_NextReceiveRatchet->GetReceiveTagSetID (); - if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) - memcpy (m_NextReceiveRatchet->remote, buf, 32); - - uint8_t sharedSecret[32], tagsetKey[32]; - m_NextReceiveRatchet->key->Agree (m_NextReceiveRatchet->remote, sharedSecret); - i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared(shared_from_this ()); - newTagset->SetTagSetID (tagsetID); - newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); - newTagset->NextSessionTagRatchet (); - 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; - } - } - - void ECIESX25519AEADRatchetSession::NewNextSendRatchet () - { - if (m_NextSendRatchet) - { - if (!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) - { - m_NextSendRatchet->keyID++; - m_NextSendRatchet->newKey = true; - } - else - m_NextSendRatchet->newKey = false; - } - else - m_NextSendRatchet.reset (new DHRatchet ()); - if (m_NextSendRatchet->newKey) - m_NextSendRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); - - m_SendForwardKey = true; - LogPrint (eLogDebug, "Garlic: New send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); - } - - bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic) - { - // we are Alice, bpk is m_RemoteStaticKey - size_t offset = 0; - if (!GenerateEphemeralKeysAndEncode (out + offset)) - { - LogPrint (eLogError, "Garlic: Can't encode elligator"); - return false; - } - 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 - MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - 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 - const uint8_t * fs; - if (isStatic) - fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType); - else - { - memset (out + offset, 0, 32); // all zeros flags section - fs = out + offset; - } - if (!Encrypt (fs, out + offset, 32)) - { - LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); - return false; - } - - MixHash (out + offset, 48); // h = SHA256(h || ciphertext) - offset += 48; - // KDF2 - if (isStatic) - { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk) - MixKey (sharedSecret); - } - // encrypt payload - if (!Encrypt (payload, out + offset, len)) - { - LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); - return false; - } - - m_State = eSessionStateNewSessionSent; - if (isStatic) - { - MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) - if (GetOwner ()) - { - auto tagsetNsr = std::make_shared(shared_from_this (), true); - InitNewSessionTagset (tagsetNsr); - tagsetNsr->Expire (); // let non-replied session expire - GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS); - } - } - return true; - } - - bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Bob - m_NSRSendTagset = std::make_shared(); - InitNewSessionTagset (m_NSRSendTagset); - uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); - - size_t offset = 0; - memcpy (out + offset, &tag, 8); - offset += 8; - if (!GenerateEphemeralKeysAndEncode (out + offset)) // bepk - { - LogPrint (eLogError, "Garlic: Can't encode elligator"); - return false; - } - memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR - memcpy (m_NSRH, m_H, 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) - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (m_Aepk, sharedSecret)) // sharedSecret = x25519(besk, aepk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); - 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); - // calculate hash for zero length - if (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) - { - LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); - return false; - } - MixHash (out + offset, 16); // h = SHA256(h || ciphertext) - offset += 16; - // KDF for payload - uint8_t keydata[64]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) - // k_ab = keydata[0:31], k_ba = keydata[32:63] - auto receiveTagset = std::make_shared(shared_from_this()); - receiveTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) - receiveTagset->NextSessionTagRatchet (); - m_SendTagset = std::make_shared(); - m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) - m_SendTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? - 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"); - return false; - } - m_State = eSessionStateNewSessionReplySent; - m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); - - return true; - } - - bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Bob and sent NSR already - uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); // next tag - memcpy (out, &tag, 8); - memcpy (out + 8, m_NSREncodedKey, 32); - // recalculate h with new tag - 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) - { - LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); - return false; - } - MixHash (out + offset, 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 - { - LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); - return false; - } - return true; - } - - bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (uint8_t * buf, size_t len) - { - // we are Alice - LogPrint (eLogDebug, "Garlic: Reply received"); - const uint8_t * tag = buf; - buf += 8; len -= 8; // tag - uint8_t bepk[32]; // Bob's ephemeral key - if (!i2p::crypto::GetElligator ()->Decode (buf, bepk)) - { - LogPrint (eLogError, "Garlic: Can't decode elligator"); - return false; - } - buf += 32; len -= 32; - // KDF for Reply Key Section - i2p::util::SaveStateHelper s(GetNoiseState ()); // restore noise state on exit - MixHash (tag, 8); // h = SHA256(h || tag) - MixHash (bepk, 32); // h = SHA256(h || bepk) - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (bepk, sharedSecret)) // sharedSecret = x25519(aesk, bepk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob ephemeral key"); - 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) - MixKey (sharedSecret); - - // calculate hash for zero length - if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only - { - LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); - return false; - } - MixHash (buf, 16); // h = SHA256(h || ciphertext) - buf += 16; len -= 16; - // KDF for payload - uint8_t keydata[64]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) - if (m_State == eSessionStateNewSessionSent) - { - // only first time, then we keep using existing tagsets - // k_ab = keydata[0:31], k_ba = keydata[32:63] - m_SendTagset = std::make_shared(); - m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) - m_SendTagset->NextSessionTagRatchet (); - auto receiveTagset = std::make_shared(shared_from_this ()); - receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) - receiveTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? - GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); - } - 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"); - return false; - } - - if (m_State == eSessionStateNewSessionSent) - { - m_State = eSessionStateEstablished; - // don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come - // done in CleanupReceiveNSRKeys called from NSR tagset destructor - m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); - GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); - } - HandlePayload (buf, len - 16, nullptr, 0); - - // we have received reply to NS with LeaseSet in it - SetLeaseSetUpdateStatus (eLeaseSetUpToDate); - SetLeaseSetUpdateMsgID (0); - - return true; - } - - 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 - uint64_t tag = m_SendTagset->GetNextSessionTag (); - if (!tag) - { - LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); - owner->RemoveECIESx25519Session (m_RemoteStaticKey); - return false; - } - memcpy (out, &tag, 8); - // ad = The session tag, 8 bytes - // 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)) - { - LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); - return false; - } - if (index >= ECIESX25519_TAGSET_MAX_NUM_TAGS && !m_SendForwardKey) - NewNextSendRatchet (); - return true; - } - - bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) - { - uint8_t nonce[12]; - CreateNonce (index, nonce); // tag's index - len -= 8; // tag - 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)) - { - 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 (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2) - moreTags = owner->GetNumRatchetInboundTags (); - index -= owner->GetNumRatchetInboundTags (); // trim behind - } - else - { - moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset - (ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2 - if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; - moreTags -= (receiveTagset->GetNextIndex () - index); - index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind - } - if (moreTags > 0) - GenerateMoreReceiveTags (receiveTagset, moreTags); - if (index > 0) - receiveTagset->SetTrimBehind (index); - return true; - } - - bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) - { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - switch (m_State) - { - case eSessionStateNewSessionReplySent: - m_State = eSessionStateEstablished; - m_NSRSendTagset = nullptr; - m_EphemeralKeys = nullptr; -#if OPENSSL_PQ - m_PQKeys = nullptr; - m_NSREncodedPQKey = nullptr; -#endif - [[fallthrough]]; - 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 - LogPrint (eLogDebug, "Garlic: Check for out of order NSR with index ", index); - if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); - return HandleNewOutgoingSessionReply (buf, len); - } - else - return HandleExistingSessionMessage (buf, len, receiveTagset, index); - case eSessionStateNew: - return HandleNewIncomingSession (buf, len); - case eSessionStateNewSessionSent: - return HandleNewOutgoingSessionReply (buf, len); - default: - return false; - } - return true; - } - - std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) - { - uint8_t * payload = GetOwner ()->GetPayloadBuffer (); - 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 - - switch (m_State) - { - case eSessionStateEstablished: - if (!NewExistingSessionMessage (payload, len, buf, m->maxLen)) - return nullptr; - len += 24; - break; - case eSessionStateNew: - 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)) - return nullptr; - len += 96; - break; - default: - return nullptr; - } - - htobe32buf (m->GetPayload (), len); - m->len += len + 4; - m->FillI2NPMessageHeader (eI2NPGarlic); - return m; - } - - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) - { - m_State = eSessionStateOneTime; - return WrapSingleMessage (msg); - } - - size_t ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload) - { - 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) - { - // resubmit non-confirmed LeaseSet - SetLeaseSetUpdateStatus (eLeaseSetUpdated); - SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed - } - auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? GetOwner ()->GetLeaseSet () : nullptr; - if (leaseSet) - { - payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; - if (!first) - { - // ack request for LeaseSet - m_AckRequestMsgID = m_SendTagset->GetMsgID (); - sendAckRequest = true; - // update LeaseSet status - SetLeaseSetUpdateStatus (eLeaseSetSubmitted); - SetLeaseSetUpdateMsgID (m_AckRequestMsgID); - SetLeaseSetSubmissionTime (ts); - } - } - 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) - { - payloadLen += 6; - if (m_NextReceiveRatchet->newKey) payloadLen += 32; - } - if (m_SendForwardKey) - { - payloadLen += 6; - if (m_NextSendRatchet->newKey) payloadLen += 32; - } - uint8_t paddingSize = 0; - if (payloadLen || ts > m_LastSentTimestamp + ECIESX25519_SEND_INACTIVITY_TIMEOUT) - { - int delta = (int)ECIESX25519_OPTIMAL_PAYLOAD_SIZE - (int)payloadLen; - if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size - { - paddingSize = m_PaddingSizes[m_NextPaddingSize++] & 0x0F; // 0 - 15 - if (m_NextPaddingSize >= 32) - { - RAND_bytes (m_PaddingSizes, 32); - m_NextPaddingSize = 0; - } - if (delta > 3) - { - delta -= 3; - if (paddingSize >= delta) paddingSize %= delta; - } - paddingSize++; - payloadLen += paddingSize + 3; - } - } - if (payloadLen) - { - if (payloadLen > I2NP_MAX_MESSAGE_SIZE) - { - LogPrint (eLogError, "Garlic: Payload length ", payloadLen, " is too long"); - return 0; - } - m_LastSentTimestamp = ts; - size_t offset = 0; - // DateTime - if (first) - { - payload[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (payload + offset, 4); offset += 2; - htobe32buf (payload + offset, ts/1000); offset += 4; // in seconds - } - // 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; - } - // msg - if (msg) - offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); - // ack - if (m_AckRequests.size () > 0) - { - payload[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (payload + offset, m_AckRequests.size () * 4); offset += 2; - for (auto& it: m_AckRequests) - { - htobe16buf (payload + offset, it.first); offset += 2; - htobe16buf (payload + offset, it.second); offset += 2; - } - m_AckRequests.clear (); - } - // next keys - if (m_SendReverseKey) - { - payload[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (payload + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - payload[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; - int keyID = m_NextReceiveRatchet->keyID - 1; - if (m_NextReceiveRatchet->newKey) - { - payload[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; - keyID++; - } - offset++; // flag - htobe16buf (payload + offset, keyID); offset += 2; // keyid - if (m_NextReceiveRatchet->newKey) - { - memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); - offset += 32; // public key - } - } - if (m_SendForwardKey) - { - payload[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (payload + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - payload[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextSendRatchet->keyID) payload[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only - offset++; // flag - htobe16buf (payload + offset, m_NextSendRatchet->keyID); offset += 2; // keyid - if (m_NextSendRatchet->newKey) - { - memcpy (payload + offset, m_NextSendRatchet->key->GetPublicKey (), 32); - offset += 32; // public key - } - } - // padding - if (paddingSize) - { - payload[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (payload + offset, paddingSize); offset += 2; - memset (payload + offset, 0, paddingSize); offset += paddingSize; - } - } - return payloadLen; - } - - size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len) - { - if (!msg) return 0; - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - if (m_Destination) cloveSize += 32; - if ((int)len < cloveSize + 3) return 0; - buf[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (buf + 1, cloveSize); // size - buf += 3; - if (m_Destination) - { - *buf = (eGarlicDeliveryTypeDestination << 5); - memcpy (buf + 1, *m_Destination, 32); buf += 32; - } - else - *buf = 0; - buf++; // flag and delivery instructions - *buf = msg->GetTypeID (); // I2NP msg type - htobe32buf (buf + 1, msg->GetMsgID ()); // msgID - htobe32buf (buf + 5, msg->GetExpiration () / 1000); // expiration in seconds - memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); - return cloveSize + 3; - } - - size_t ECIESX25519AEADRatchetSession::CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len) - { - if (!ls || ls->GetStoreType () != i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) - { - LogPrint (eLogError, "Garlic: Incorrect LeasetSet type to send"); - return 0; - } - uint16_t cloveSize = 1 + 9 + DATABASE_STORE_HEADER_SIZE + ls->GetBufferLen (); // to local - if ((int)len < cloveSize + 3) return 0; - buf[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (buf + 1, cloveSize); // size - buf += 3; - *buf = 0; buf++; // flag and delivery instructions - *buf = eI2NPDatabaseStore; buf++; // I2NP msg type - RAND_bytes (buf, 4); buf += 4; // msgID - htobe32buf (buf, (ts + I2NP_MESSAGE_EXPIRATION_TIMEOUT)/1000); buf += 4; // expiration - // payload - memcpy (buf + DATABASE_STORE_KEY_OFFSET, ls->GetStoreHash (), 32); - buf[DATABASE_STORE_TYPE_OFFSET] = i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2; - memset (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0, 4); // replyToken = 0 - buf += DATABASE_STORE_HEADER_SIZE; - memcpy (buf, ls->GetBuffer (), ls->GetBufferLen ()); - - return cloveSize + 3; - } - - void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) - { - if (GetOwner ()) - { - for (int i = 0; i < numTags; i++) - { - auto tag = GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); - if (!tag) - { - LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for receive tagset"); - break; - } - } - } - } - - 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 - } - - RouterIncomingRatchetSession::RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState): - ECIESX25519AEADRatchetSession (&i2p::context, false) - { - SetLeaseSetUpdateStatus (eLeaseSetDoNotSend); - SetNoiseState (initState); - } - - bool RouterIncomingRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len) - { - if (!GetOwner ()) return false; - m_CurrentNoiseState = GetNoiseState (); - // we are Bob - m_CurrentNoiseState.MixHash (buf, 32); - uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (buf, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) - { - LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); - return false; - } - m_CurrentNoiseState.MixKey (sharedSecret); - buf += 32; len -= 32; - uint8_t nonce[12]; - CreateNonce (0, nonce); - std::vector payload (len - 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_CurrentNoiseState.m_H, 32, - m_CurrentNoiseState.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); - return false; - } - HandlePayload (payload.data (), len - 16, nullptr, 0); - return true; - } - - static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, - bool datetime, size_t optimalSize) - { - size_t len = 0; - if (datetime) - { - // DateTime - payload[0] = eECIESx25519BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); - len = 7; - } - // I2NP - payload += len; - uint16_t cloveSize = msg->GetPayloadLength () + 10; - payload[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (payload + 1, cloveSize); // size - payload += 3; - payload[0] = 0; // flag and delivery instructions - payload[1] = msg->GetTypeID (); // I2NP msg type - htobe32buf (payload + 2, msg->GetMsgID ()); // msgID - htobe32buf (payload + 6, msg->GetExpiration () / 1000); // expiration in seconds - memcpy (payload + 10, msg->GetPayload (), msg->GetPayloadLength ()); - len += cloveSize + 3; - payload += cloveSize; - // padding - int delta = (int)optimalSize - (int)len; - if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size - { - uint8_t paddingSize = rand () & 0x0F; // 0 - 15 - if (delta > 3) - { - delta -= 3; - if (paddingSize > delta) paddingSize %= delta; - } - payload[0] = eECIESx25519BlkPadding; - htobe16buf (payload + 1, paddingSize); - if (paddingSize) memset (payload + 3, 0, paddingSize); - len += paddingSize + 3; - } - return len; - } - - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) - { - auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - size_t offset = 0; - memcpy (buf + offset, &tag, 8); offset += 8; - auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload, false, 956); // 1003 - 8 tag - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 3 local tunnel delivery - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); - return nullptr; - } - offset += len + 16; - 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) - { - // 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); - 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; - auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); - noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) - offset += 32; - uint8_t sharedSecret[32]; - if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - return nullptr; - } - noiseState.MixKey (sharedSecret); - auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload, true, 900); // 1003 - 32 eph key - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 35 router tunnel delivery - uint8_t nonce[12]; - memset (nonce, 0, 12); - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); - return nullptr; - } - offset += len + 16; - 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 deleted file mode 100644 index fd9cc45d..00000000 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ /dev/null @@ -1,282 +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 ECIES_X25519_AEAD_RATCHET_SESSION_H__ -#define ECIES_X25519_AEAD_RATCHET_SESSION_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Identity.h" -#include "Crypto.h" -#include "PostQuantum.h" -#include "Garlic.h" -#include "Tag.h" - -namespace i2p -{ -namespace garlic -{ - const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second since session creation we can restart session after - const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of seconds we receive nothing and should restart if we can - 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_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_NSR_NUM_GENERATED_TAGS = 12; - - const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ - // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ - - class RatchetTagSet - { - public: - - RatchetTagSet () {}; - virtual ~RatchetTagSet () {}; - - void DHInitialize (const uint8_t * rootKey, const uint8_t * k); - void NextSessionTagRatchet (); - uint64_t GetNextSessionTag (); - const uint8_t * GetNextRootKey () const { return m_NextRootKey; }; - int GetNextIndex () const { return m_NextIndex; }; - void GetSymmKey (int index, uint8_t * key); - void DeleteSymmKey (int index); - - 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; - uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; - int m_NextIndex, m_NextSymmKeyIndex; - std::unordered_map > m_ItermediateSymmKeys; - - int m_TagSetID = 0; - }; - - class ECIESX25519AEADRatchetSession; - class ReceiveRatchetTagSet: public RatchetTagSet, - public std::enable_shared_from_this - { - public: - - ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false); - ~ReceiveRatchetTagSet () override; - - bool IsNS () const { return m_IsNS; }; - std::shared_ptr GetSession () { return m_Session; }; - void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; - int GetTrimBehind () const { return m_TrimBehindIndex; }; - - void Expire (); - bool IsExpired (uint64_t ts) const; - - 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; - std::shared_ptr m_Session; - bool m_IsNS; - uint64_t m_ExpirationTimestamp = 0; - }; - - class SymmetricKeyTagSet: public ReceiveRatchetTagSet - { - public: - - 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; } - - private: - - GarlicDestination * m_Destination; - uint8_t m_Key[32]; - }; - - enum ECIESx25519BlockType - { - eECIESx25519BlkDateTime = 0, - eECIESx25519BlkSessionID = 1, - eECIESx25519BlkTermination = 4, - eECIESx25519BlkOptions = 5, - eECIESx25519BlkNextKey = 7, - eECIESx25519BlkAck = 8, - eECIESx25519BlkAckRequest = 9, - eECIESx25519BlkGalicClove = 11, - eECIESx25519BlkPadding = 254 - }; - - const uint8_t ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG = 0x01; - const uint8_t ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG = 0x02; - const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; - - class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, - private i2p::crypto::NoiseSymmetricState, - public std::enable_shared_from_this - { - enum SessionState - { - eSessionStateNew = 0, - eSessionStateNewSessionReceived, - eSessionStateNewSessionSent, - eSessionStateNewSessionReplySent, - eSessionStateEstablished, - eSessionStateOneTime - }; - - struct DHRatchet - { - int keyID = 0; - 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: - - ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS); - ~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 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 Terminate () { m_IsTerminated = true; } - void SetDestination (const i2p::data::IdentHash& dest) - { - if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); - } - bool CheckExpired (uint64_t ts); // true is expired - bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } - bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } - void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side - - bool IsRatchets () const override { return true; }; - bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; - bool IsTerminated () const override { return m_IsTerminated; } - uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; }; - void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; }; - bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP - - protected: - - i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; }; - void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; }; - void CreateNonce (uint64_t seqn, uint8_t * nonce); - void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); - bool MessageConfirmed (uint32_t msgID) override; - - private: - - bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes - void InitNewSessionTagset (std::shared_ptr tagsetNsr) const; - - bool HandleNewIncomingSession (const uint8_t * buf, size_t len); - bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); - bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); - void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); - - bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic = true); - bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - - size_t CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload); - size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); - size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); - - void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); - void NewNextSendRatchet (); - - 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) - 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 - int GetState () const { return (int)m_State; } - i2p::data::IdentHash GetDestination () const - { - return m_Destination ? *m_Destination : i2p::data::IdentHash (); - } - }; - - // single session for all incoming messages - class RouterIncomingRatchetSession: public ECIESX25519AEADRatchetSession - { - public: - - RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState); - bool HandleNextMessage (const uint8_t * buf, size_t len); - i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; - - private: - - 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); -} -} - -#endif - diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 47edb755..1096b3b7 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "Log.h" #include "Crypto.h" @@ -33,13 +25,13 @@ namespace crypto BN_add (l, l, tmp); BN_sub_word (two_252_2, 2); // 2^252 - 2 - // -121665*inv(121666) + // -121665*inv(121666) d = BN_new (); BN_set_word (tmp, 121666); BN_mod_inverse (tmp, tmp, q, ctx); BN_set_word (d, 121665); BN_set_negative (d, 1); - BN_mod_mul (d, d, tmp, q, ctx); + BN_mul (d, d, tmp, ctx); // 2^((q-1)/4) I = BN_new (); @@ -61,7 +53,7 @@ namespace crypto BN_mod (By, By, q, ctx); // % q // precalculate Bi256 table - Bi256Carry = { Bx, By }; // B + Bi256Carry = { Bx, By }; // B for (int i = 0; i < 32; i++) { Bi256[i][0] = Bi256Carry; // first point @@ -129,7 +121,7 @@ namespace crypto return passed; } - void Ed25519::Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, + void Ed25519::Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const { BN_CTX * bnCtx = BN_CTX_new (); @@ -161,7 +153,7 @@ namespace crypto BN_CTX_free (bnCtx); } - void Ed25519::SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, + void Ed25519::SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const { BN_CTX * bnCtx = BN_CTX_new (); @@ -172,16 +164,16 @@ namespace crypto SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, T, 80); - SHA512_Update (&ctx, publicKeyEncoded, 32); + SHA512_Update (&ctx, publicKeyEncoded, 32); SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; SHA512_Final (digest, &ctx); - BIGNUM * r = DecodeBN<64> (digest); - BN_mod (r, r, l, bnCtx); // % l + BIGNUM * r = DecodeBN<64> (digest); + BN_mod (r, r, l, bnCtx); // % l EncodeBN (r, digest, 32); // 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 (Normalize (MulB (digest, bnCtx), bnCtx), R); // calculate S SHA512_Init (&ctx); SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R @@ -190,7 +182,7 @@ namespace crypto SHA512_Final (digest, &ctx); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l - BIGNUM * a = DecodeBN (privateKey); + BIGNUM * a = DecodeBN (privateKey); BN_mod_mul (h, h, a, l, bnCtx); // %l BN_mod_add (h, h, r, l, bnCtx); // %l memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); @@ -198,7 +190,7 @@ namespace crypto BN_free (r); BN_free (h); BN_free (a); BN_CTX_free (bnCtx); } - + EDDSAPoint Ed25519::Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const { // x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2) @@ -215,7 +207,7 @@ namespace crypto if (!t1) { t1 = BN_CTX_get (ctx); BN_mul (t1, p1.x, p1.y, ctx); } if (!t2) { t2 = BN_CTX_get (ctx); BN_mul (t2, p2.x, p2.y, ctx); } BN_mul (t3, t1, t2, ctx); - BN_mul (t3, t3, d, ctx); // C = d*t1*t2 + BN_mul (t3, t3, d, ctx); // C = d*t1*t2 if (p1.z) { @@ -264,9 +256,9 @@ namespace crypto else { BN_mul (t2, p.x, p.y, ctx); // t = x*y - BN_sqr (t2, t2, ctx); // t2 = t^2 + BN_sqr (t2, t2, ctx); // t2 = t^2 } - BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 + BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 if (p.z) BN_sqr (z2, p.z, ctx); // z2 = D = z^2 else @@ -349,7 +341,7 @@ namespace crypto BN_mod_inverse (y, p.z, q, ctx); BN_mod_mul (x, p.x, y, q, ctx); // x = x/z BN_mod_mul (y, p.y, y, q, ctx); // y = y/z - return EDDSAPoint{x, y}; + return EDDSAPoint{x, y}; } else return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; @@ -413,7 +405,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 @@ -457,13 +449,93 @@ 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 (); // calculate alpha = seed mod l - BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian + BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian BN_mod (alpha, alpha, l, ctx); // % l - uint8_t priv[32]; + uint8_t priv[32]; EncodeBN (alpha, priv, 32); // back to Little Endian BN_free (alpha); // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) @@ -476,16 +548,16 @@ namespace crypto { BN_CTX * ctx = BN_CTX_new (); // calculate alpha = seed mod l - BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian + BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian BN_mod (alpha, alpha, l, ctx); // % l - BIGNUM * p = DecodeBN<32> (priv); // priv is in Little Endian + BIGNUM * p = DecodeBN<32> (priv); // priv is in Little Endian BN_add (alpha, alpha, p); // alpha = alpha + priv - // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L + // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L BN_mod (alpha, alpha, l, ctx); // % l EncodeBN (alpha, blindedPriv, 32); - // A' = DERIVE_PUBLIC(a') + // A' = DERIVE_PUBLIC(a') auto A1 = MulB (blindedPriv, ctx); - EncodePublicKey (A1, blindedPub, ctx); + EncodePublicKey (A1, blindedPub, ctx); BN_free (alpha); BN_free (p); BN_CTX_free (ctx); } @@ -502,14 +574,14 @@ namespace crypto { uint8_t seed[32]; RAND_bytes (seed, 32); - BIGNUM * p = DecodeBN<32> (seed); + BIGNUM * p = DecodeBN<32> (seed); BN_CTX * ctx = BN_CTX_new (); BN_mod (p, p, l, ctx); // % l - EncodeBN (p, priv, 32); + EncodeBN (p, priv, 32); BN_CTX_free (ctx); BN_free (p); - } - + } + static std::unique_ptr g_Ed25519; std::unique_ptr& GetEd25519 () { @@ -525,3 +597,4 @@ namespace crypto } } } + diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 9c0ad801..84501b03 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef ED25519_H__ #define ED25519_H__ @@ -84,17 +76,20 @@ 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 bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const; void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const; void SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const; - + static void ExpandPrivateKey (const uint8_t * key, uint8_t * expandedKey); // key - 32 bytes, expandedKey - 64 bytes void CreateRedDSAPrivateKey (uint8_t * priv); // priv is 32 bytes - + private: EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const; @@ -102,8 +97,8 @@ namespace crypto EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const; EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const; // B*e, e is 32 bytes Little Endian EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const; - - bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const; + + bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const; BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const; EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const; void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const; @@ -112,6 +107,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; @@ -121,11 +121,13 @@ namespace crypto // if j > 128 we use 256 - j and carry 1 to next byte // Bi256[0][0] = B, base point EDDSAPoint Bi256Carry; // Bi256[32][0] - }; + }; std::unique_ptr& GetEd25519 (); } } + #endif + diff --git a/libi2pd/Elligator.cpp b/libi2pd/Elligator.cpp deleted file mode 100644 index 25e09893..00000000 --- a/libi2pd/Elligator.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "Crypto.h" -#include "Elligator.h" - -namespace i2p -{ -namespace crypto -{ - - Elligator2::Elligator2 () - { - // TODO: share with Ed22519 - p = BN_new (); - // 2^255-19 - BN_set_bit (p, 255); // 2^255 - BN_sub_word (p, 19); - p38 = BN_dup (p); BN_add_word (p38, 3); BN_div_word (p38, 8); // (p+3)/8 - p12 = BN_dup (p); BN_sub_word (p12, 1); BN_div_word (p12, 2); // (p-1)/2 - p14 = BN_dup (p); BN_sub_word (p14, 1); BN_div_word (p14, 4); // (p-1)/4 - - A = BN_new (); BN_set_word (A, 486662); - nA = BN_new (); BN_sub (nA, p, A); - - BN_CTX * ctx = BN_CTX_new (); - // calculate sqrt(-1) - sqrtn1 = BN_new (); - BN_set_word (sqrtn1, 2); - BN_mod_exp (sqrtn1, sqrtn1, p14, p, ctx); // 2^((p-1)/4 - - u = BN_new (); BN_set_word (u, 2); - iu = BN_new (); BN_mod_inverse (iu, u, p, ctx); - - BN_CTX_free (ctx); - } - - Elligator2::~Elligator2 () - { - BN_free (p); BN_free (p38); BN_free (p12); BN_free (p14); - BN_free (sqrtn1); BN_free (A); BN_free (nA); - BN_free (u); BN_free (iu); - } - - bool Elligator2::Encode (const uint8_t * key, uint8_t * encoded, bool highY, bool random) const - { - bool ret = true; - BN_CTX * ctx = BN_CTX_new (); - BN_CTX_start (ctx); - - uint8_t key1[32]; - for (size_t i = 0; i < 16; i++) // from Little Endian - { - key1[i] = key[31 - i]; - key1[31 - i] = key[i]; - } - - BIGNUM * x = BN_CTX_get (ctx); BN_bin2bn (key1, 32, x); - BIGNUM * xA = BN_CTX_get (ctx); BN_add (xA, x, A); // x + A - BN_sub (xA, p, xA); // p - (x + A) - - BIGNUM * uxxA = BN_CTX_get (ctx); // u*x*xA - BN_mod_mul (uxxA, u, x, p, ctx); - BN_mod_mul (uxxA, uxxA, xA, p, ctx); - - if (Legendre (uxxA, ctx) != -1) - { - uint8_t randByte = 0; // random highest bits and high y - if (random) - { - RAND_bytes (&randByte, 1); - highY = randByte & 0x01; - } - - BIGNUM * r = BN_CTX_get (ctx); - if (highY) - { - BN_mod_inverse (r, x, p, ctx); - BN_mod_mul (r, r, xA, p, ctx); - } - else - { - BN_mod_inverse (r, xA, p, ctx); - BN_mod_mul (r, r, x, p, ctx); - } - BN_mod_mul (r, r, iu, p, ctx); - - SquareRoot (r, r, ctx); - bn2buf (r, encoded, 32); - - if (random) - encoded[0] |= (randByte & 0xC0); // copy two highest bits from randByte - for (size_t i = 0; i < 16; i++) // To Little Endian - { - uint8_t tmp = encoded[i]; - encoded[i] = encoded[31 - i]; - encoded[31 - i] = tmp; - } - } - else - ret = false; - - BN_CTX_end (ctx); - BN_CTX_free (ctx); - return ret; - } - - bool Elligator2::Decode (const uint8_t * encoded, uint8_t * key) const - { - bool ret = true; - BN_CTX * ctx = BN_CTX_new (); - BN_CTX_start (ctx); - - uint8_t encoded1[32]; - for (size_t i = 0; i < 16; i++) // from Little Endian - { - encoded1[i] = encoded[31 - i]; - encoded1[31 - i] = encoded[i]; - } - encoded1[0] &= 0x3F; // drop two highest bits - - BIGNUM * r = BN_CTX_get (ctx); BN_bin2bn (encoded1, 32, r); - - if (BN_cmp (r, p12) <= 0) // r < (p-1)/2 - { - // v = -A/(1+u*r^2) - BIGNUM * v = BN_CTX_get (ctx); BN_mod_sqr (v, r, p, ctx); - BN_mod_mul (v, v, u, p, ctx); - BN_add_word (v, 1); - BN_mod_inverse (v, v, p, ctx); - BN_mod_mul (v, v, nA, p, ctx); - - BIGNUM * vpA = BN_CTX_get (ctx); - BN_add (vpA, v, A); // v + A - // t = v^3+A*v^2+v = v^2*(v+A)+v - BIGNUM * t = BN_CTX_get (ctx); BN_mod_sqr (t, v, p, ctx); - BN_mod_mul (t, t, vpA, p, ctx); - BN_mod_add (t, t, v, p, ctx); - - int legendre = Legendre (t, ctx); - BIGNUM * x = BN_CTX_get (ctx); - if (legendre == 1) - BN_copy (x, v); - else - { - BN_sub (x, p, v); - BN_mod_sub (x, x, A, p, ctx); - } - - bn2buf (x, key, 32); - for (size_t i = 0; i < 16; i++) // To Little Endian - { - uint8_t tmp = key[i]; - key[i] = key[31 - i]; - key[31 - i] = tmp; - } - } - else - ret = false; - - BN_CTX_end (ctx); - BN_CTX_free (ctx); - - return ret; - } - - void Elligator2::SquareRoot (const BIGNUM * x, BIGNUM * r, BN_CTX * ctx) const - { - BIGNUM * t = BN_CTX_get (ctx); - BN_mod_exp (t, x, p14, p, ctx); // t = x^((p-1)/4) - BN_mod_exp (r, x, p38, p, ctx); // r = x^((p+3)/8) - BN_add_word (t, 1); - - if (!BN_cmp (t, p)) - BN_mod_mul (r, r, sqrtn1, p, ctx); - - if (BN_cmp (r, p12) > 0) // r > (p-1)/2 - BN_sub (r, p, r); - } - - int Elligator2::Legendre (const BIGNUM * a, BN_CTX * ctx) const - { - // assume a < p, so don't check for a % p = 0, but a = 0 only - if (BN_is_zero(a)) return 0; - BIGNUM * r = BN_CTX_get (ctx); - BN_mod_exp (r, a, p12, p, ctx); // r = a^((p-1)/2) mod p - if (BN_is_word(r, 1)) - return 1; - else if (BN_is_zero(r)) - return 0; - return -1; - } - - static std::unique_ptr g_Elligator; - std::unique_ptr& GetElligator () - { - if (!g_Elligator) - { - auto el = new Elligator2(); - if (!g_Elligator) // make sure it was not created already - g_Elligator.reset (el); - else - delete el; - } - return g_Elligator; - } -} -} diff --git a/libi2pd/Elligator.h b/libi2pd/Elligator.h deleted file mode 100644 index eacb03cd..00000000 --- a/libi2pd/Elligator.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef ELLIGATOR_H__ -#define ELLIGATOR_H__ - -#include -#include -#include - -namespace i2p -{ -namespace crypto -{ - - class Elligator2 - { - public: - - Elligator2 (); - ~Elligator2 (); - - bool Encode (const uint8_t * key, uint8_t * encoded, bool highY = false, bool random = true) const; - bool Decode (const uint8_t * encoded, uint8_t * key) const; - - private: - - void SquareRoot (const BIGNUM * x, BIGNUM * r, BN_CTX * ctx) const; - int Legendre (const BIGNUM * a, BN_CTX * ctx) const; // a/p - - private: - - BIGNUM * p, * p38, * p12, * p14, * sqrtn1, * A, * nA, * u, * iu; - }; - - std::unique_ptr& GetElligator (); -} -} - -#endif diff --git a/libi2pd/Event.cpp b/libi2pd/Event.cpp new file mode 100644 index 00000000..9c75f95b --- /dev/null +++ b/libi2pd/Event.cpp @@ -0,0 +1,61 @@ +#include "Event.h" +#include "Log.h" + +namespace i2p +{ + namespace event + { +#ifdef WITH_EVENTS + EventCore core; +#endif + + void EventCore::SetListener(EventListener * l) + { + m_listener = l; + LogPrint(eLogInfo, "Event: listener set"); + } + + void EventCore::QueueEvent(const EventType & ev) + { + if(m_listener) m_listener->HandleEvent(ev); + } + + void EventCore::CollectEvent(const std::string & type, const std::string & ident, uint64_t val) + { + std::unique_lock lock(m_collect_mutex); + std::string key = type + "." + ident; + if (m_collected.find(key) == m_collected.end()) + { + m_collected[key] = {type, key, 0}; + } + m_collected[key].Val += val; + } + + void EventCore::PumpCollected(EventListener * listener) + { + std::unique_lock lock(m_collect_mutex); + if(listener) + { + for(const auto & ev : m_collected) { + listener->HandlePumpEvent({{"type", ev.second.Key}, {"ident", ev.second.Ident}}, ev.second.Val); + } + } + m_collected.clear(); + } + } +} + +void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val) +{ +#ifdef WITH_EVENTS + i2p::event::core.CollectEvent(type, ident, val); +#endif +} + +void EmitEvent(const EventType & e) +{ +#if WITH_EVENTS + i2p::event::core.QueueEvent(e); +#endif +} + diff --git a/libi2pd/Event.h b/libi2pd/Event.h new file mode 100644 index 00000000..a8b46a4b --- /dev/null +++ b/libi2pd/Event.h @@ -0,0 +1,53 @@ +#ifndef EVENT_H__ +#define EVENT_H__ +#include +#include +#include +#include +#include + +#include + +typedef std::map EventType; + +namespace i2p +{ + namespace event + { + class EventListener { + public: + virtual ~EventListener() {}; + virtual void HandleEvent(const EventType & ev) = 0; + /** @brief handle collected event when pumped */ + virtual void HandlePumpEvent(const EventType & ev, const uint64_t & val) = 0; + }; + + class EventCore + { + public: + void QueueEvent(const EventType & ev); + void CollectEvent(const std::string & type, const std::string & ident, uint64_t val); + void SetListener(EventListener * l); + void PumpCollected(EventListener * l); + + private: + std::mutex m_collect_mutex; + struct CollectedEvent + { + std::string Key; + std::string Ident; + uint64_t Val; + }; + std::map m_collected; + EventListener * m_listener = nullptr; + }; +#ifdef WITH_EVENTS + extern EventCore core; +#endif + } +} + +void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val); +void EmitEvent(const EventType & ev); + +#endif diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 3f5fc6b9..f8b3ed7e 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,22 +7,11 @@ */ #include - -#if defined(MAC_OSX) -#if !STD_FILESYSTEM -#include -#endif -#include -#endif - -#if defined(__HAIKU__) -#include -#endif +#include #ifdef _WIN32 #include #include -#include #endif #include "Base.h" @@ -30,19 +19,10 @@ #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"; std::string dataDir = ""; - std::string certsDir = ""; #ifdef _WIN32 std::string dirSep = "\\"; #else @@ -61,72 +41,18 @@ namespace fs { return dataDir; } - const std::string & GetCertsDir () { - return certsDir; - } - - 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; -#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]; +#if defined(WIN32) || defined(_WIN32) + char localAppData[MAX_PATH]; // check executable directory first - if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) + if(!GetModuleFileName(NULL, localAppData, MAX_PATH)) { -#ifdef WIN32_APP +#if defined(WIN32_APP) MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get application path!"); @@ -135,21 +61,16 @@ 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::path(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% + else // otherwise %appdata% { - if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) + if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) { -#ifdef WIN32_APP +#if defined(WIN32_APP) MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get AppData path!"); @@ -157,13 +78,7 @@ namespace fs { exit(1); } else - { -#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) - dataDir = fs_lib::path(localAppData).string() + "\\" + appName; -#else - dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName; -#endif - } + dataDir = std::string(localAppData) + "\\" + appName; } } return; @@ -172,26 +87,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; @@ -200,32 +110,17 @@ namespace fs { #endif } - void SetCertsDir(const std::string & cmdline_certsdir) { - if (cmdline_certsdir != "") - { - if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') - certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash - else - certsDir = cmdline_certsdir; - } - else - { - certsDir = i2p::fs::DataDirPath("certificates"); - } - return; - } - 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 +128,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 +143,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 +173,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 +203,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 +216,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..6f112218 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,167 +15,135 @@ #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; + extern std::string dirSep; - /** - * @brief Class to work with NetDb & Router profiles - * - * Usage: - * - * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; - * auto h = HashedStorage("name", "y", "z-", ".txt"); - * h.SetPlace("/tmp/hs-test"); - * h.GetName() -> gives "name" - * h.GetRoot() -> gives "/tmp/hs-test/name" - * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet - * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt - * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists - * std::vector files; - * h.Traverse(files); <- finds all files in storage and saves in given vector - */ - class HashedStorage - { - protected: + /** + * @brief Class to work with NetDb & Router profiles + * + * Usage: + * + * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + * auto h = HashedStorage("name", "y", "z-", ".txt"); + * h.SetPlace("/tmp/hs-test"); + * h.GetName() -> gives "name" + * h.GetRoot() -> gives "/tmp/hs-test/name" + * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet + * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt + * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists + * std::vector files; + * h.Traverse(files); <- finds all files in storage and saves in given vector + */ + class HashedStorage { + protected: + std::string root; /**< path to storage with it's name included */ + std::string name; /**< name of the storage */ + std::string prefix1; /**< hashed directory prefix */ + std::string prefix2; /**< prefix of file in storage */ + std::string suffix; /**< suffix of file in storage (extension) */ - std::string root; /**< path to storage with it's name included */ - std::string name; /**< name of the storage */ - std::string prefix1; /**< hashed directory prefix */ - std::string prefix2; /**< prefix of file in storage */ - std::string suffix; /**< suffix of file in storage (extension) */ + public: + typedef std::function FilenameVisitor; + HashedStorage(const char *n, const char *p1, const char *p2, const char *s): + name(n), prefix1(p1), prefix2(p2), suffix(s) {}; - public: + /** create subdirs in storage */ + bool Init(const char* chars, size_t cnt); + const std::string & GetRoot() const { return root; } + const std::string & GetName() const { return name; } + /** set directory where to place storage directory */ + void SetPlace(const std::string & path); + /** path to file with given ident */ + std::string Path(const std::string & ident) const; + /** remove file by ident */ + void Remove(const std::string & ident); + /** find all files in storage and store list in provided vector */ + void Traverse(std::vector & files); + /** visit every file in this storage with a visitor */ + void Iterate(FilenameVisitor v); + }; - typedef std::function FilenameVisitor; - HashedStorage(const char *n, const char *p1, const char *p2, const char *s): - name(n), prefix1(p1), prefix2(p2), suffix(s) {}; - - /** create subdirs in storage */ - bool Init(const char* chars, size_t cnt); - const std::string & GetRoot() const { return root; } - const std::string & GetName() const { return name; } - /** set directory where to place storage directory */ - void SetPlace(const std::string & path); - /** path to file with given ident */ - std::string Path(const std::string & ident) const; - /** remove file by ident */ - void Remove(const std::string & ident); - /** find all files in storage and store list in provided vector */ - void Traverse(std::vector & files); - /** visit every file in this storage with a visitor */ - void Iterate(FilenameVisitor v); - }; - - /** @brief Returns current application name, default 'i2pd' */ + /** @brief Returns current application name, default 'i2pd' */ const std::string & GetAppName (); - /** @brief Set application name, affects autodetection of datadir */ + /** @brief Set application name, affects autodetection of datadir */ void SetAppName (const std::string& name); - /** @brief Returns datadir path */ - const std::string & GetDataDir(); + /** @brief Returns datadir path */ + const std::string & GetDataDir(); - /** @brief Returns certsdir path */ - const std::string & GetCertsDir(); + /** + * @brief Set datadir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --datadir= + * @param isService Value of cmdline parameter --service + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ + * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ + */ + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); - /** @brief Returns datadir path in UTF-8 encoding */ - const std::string GetUTF8DataDir(); + /** + * @brief Create subdirectories inside datadir + */ + bool Init(); - /** - * @brief Set datadir either from cmdline option or using autodetection - * @param cmdline_param Value of cmdline parameter --datadir= - * @param isService Value of cmdline parameter --service - * - * Examples of autodetected paths: - * - * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ - * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ - * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ - * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ - */ - void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + /** + * @brief Get list of files in directory + * @param path Path to directory + * @param files Vector to store found files + * @return true on success and false if directory not exists + */ + bool ReadDir(const std::string & path, std::vector & files); - /** - * @brief Set certsdir either from cmdline option or using autodetection - * @param cmdline_param Value of cmdline parameter --certsdir= - * - * Examples of autodetected paths: - * - * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\certificates - * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\certificates - * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/certificates - * Unix: /var/lib/i2pd/certificates (system=1) >> ~/.i2pd/ or /tmp/i2pd/certificates - */ - void SetCertsDir(const std::string & cmdline_certsdir); + /** + * @brief Remove file with given path + * @param path Absolute path to file + * @return true on success, false if file not exists, throws exception on error + */ + bool Remove(const std::string & path); - /** - * @brief Create subdirectories inside datadir - */ - bool Init(); + /** + * @brief Check existence of file + * @param path Absolute path to file + * @return true if file exists, false otherwise + */ + bool Exists(const std::string & path); - /** - * @brief Get list of files in directory - * @param path Path to directory - * @param files Vector to store found files - * @return true on success and false if directory not exists - */ - bool ReadDir(const std::string & path, std::vector & files); + uint32_t GetLastUpdateTime (const std::string & path); // seconds since epoch - /** - * @brief Remove file with given path - * @param path Absolute path to file - * @return true on success, false if file not exists, throws exception on error - */ - bool Remove(const std::string & path); + bool CreateDirectory (const std::string& path); - /** - * @brief Check existence of file - * @param path Absolute path to file - * @return true if file exists, false otherwise - */ - bool Exists(const std::string & path); + template + void _ExpandPath(std::stringstream & path, T c) { + path << i2p::fs::dirSep << c; + } - uint32_t GetLastUpdateTime (const std::string & path); // seconds since epoch + template + void _ExpandPath(std::stringstream & path, T c, Other ... other) { + _ExpandPath(path, c); + _ExpandPath(path, other ...); + } - bool CreateDirectory (const std::string& path); + /** + * @brief Get path relative to datadir + * + * Examples (with datadir = "/tmp/i2pd"): + * + * i2p::fs::Path("test") -> '/tmp/i2pd/test' + * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' + */ + template + std::string DataDirPath(Other ... components) { + std::stringstream s(""); + s << i2p::fs::GetDataDir(); + _ExpandPath(s, components ...); - template - void _ExpandPath(std::stringstream & path, T c) { - path << i2p::fs::dirSep << c; - } - - template - void _ExpandPath(std::stringstream & path, T c, Other ... other) { - _ExpandPath(path, c); - _ExpandPath(path, other ...); - } - - /** - * @brief Get path relative to datadir - * - * Examples (with datadir = "/tmp/i2pd"): - * - * i2p::fs::Path("test") -> '/tmp/i2pd/test' - * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' - */ - template - std::string DataDirPath(Other ... components) { - std::stringstream s(""); - s << i2p::fs::GetDataDir(); - _ExpandPath(s, components ...); - - return s.str(); - } + return s.str(); + } template std::string StorageRootPath (const Storage& storage, Filename... filenames) diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index 300a50ab..b7cbf7d4 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,18 +1,10 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include +#include #include #include "Crypto.h" #include "FS.h" #include "Log.h" #include "Family.h" -#include "Config.h" namespace i2p { @@ -24,8 +16,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,16 +38,48 @@ namespace data cn += 3; char * family = strstr (cn, ".family"); if (family) family[0] = 0; - auto pkey = X509_get_pubkey (cert); - if (pkey) - { - 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); - } - } } + 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[cn] = verifier; } SSL_free (ssl); } @@ -68,8 +90,7 @@ namespace data void Families::LoadCertificates () { - std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family"; - + std::string certDir = i2p::fs::DataDirPath("certificates", "family"); std::vector files; int numCertificates = 0; @@ -90,41 +111,25 @@ 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) { - uint8_t buf[100], signatureBuf[64]; - size_t len = family.length (); - if (len + 32 > 100) + uint8_t buf[50], signatureBuf[64]; + size_t len = family.length (), signatureLen = strlen (signature); + if (len + 32 > 50) { LogPrint (eLogError, "Family: ", family, " is too long"); return false; } - 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) - { - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, it->second.first); - auto ret = EVP_DigestVerify (ctx, signatureBuf, signatureBufLen, buf, len); - EVP_MD_CTX_destroy (ctx); - return ret; - } - } - // TODO: process key - return true; - } - FamilyID Families::GetFamilyID (const std::string& family) const - { + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + Base64ToByteStream (signature, signatureLen, signatureBuf, 64); auto it = m_SigningKeys.find (family); if (it != m_SigningKeys.end ()) - return it->second.second; - return 0; + return it->second->Verify (buf, len, signatureBuf); + // TODO: process key + return true; } std::string CreateFamilySignature (const std::string& family, const IdentHash& ident) @@ -154,7 +159,12 @@ namespace data memcpy (buf + len, (const uint8_t *)ident, 32); len += 32; signer.Sign (buf, len, signature); - sig = ByteStreamToBase64 (signature, 64); + 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"); @@ -169,3 +179,4 @@ namespace data } } } + diff --git a/libi2pd/Family.h b/libi2pd/Family.h index fcf61082..a1b5a789 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,26 +1,16 @@ -/* -* 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 FAMILY_H__ #define FAMILY_H__ #include #include -#include #include -#include +#include "Signature.h" #include "Identity.h" namespace i2p { namespace data { - typedef int FamilyID; class Families { public: @@ -29,8 +19,7 @@ namespace data ~Families (); void LoadCertificates (); bool VerifyFamily (const std::string& family, const IdentHash& ident, - std::string_view signature, const char * key = nullptr) const; - FamilyID GetFamilyID (const std::string& family) const; + const char * signature, const char * key = nullptr); private: @@ -38,7 +27,7 @@ namespace data private: - std::map > m_SigningKeys; // family -> (verification pkey, id) + std::map > m_SigningKeys; }; std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 8c8602e8..888e988c 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "I2PEndian.h" #include @@ -19,22 +11,30 @@ #include "Timestamp.h" #include "Log.h" #include "FS.h" -#include "ECIESX25519AEADRatchetSession.h" #include "Garlic.h" namespace i2p { namespace garlic { - GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, bool attachLeaseSet): - m_Owner (owner), m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), + GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, + std::shared_ptr destination, int numTags, bool attachLeaseSet): + m_Owner (owner), m_Destination (destination), m_NumTags (numTags), + m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) { + // create new session tags and session key + RAND_bytes (m_SessionKey, 32); + m_Encryption.SetKey (m_SessionKey); } - GarlicRoutingSession::GarlicRoutingSession (): - m_Owner (nullptr), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) + GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag): + m_Owner (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0) { + memcpy (m_SessionKey, sessionKey, 32); + m_Encryption.SetKey (m_SessionKey); + m_SessionTags.push_back (sessionTag); + m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch (); } GarlicRoutingSession::~GarlicRoutingSession () @@ -45,80 +45,109 @@ 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) + 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; } - bool GarlicRoutingSession::MessageConfirmed (uint32_t msgID) + GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags () { - if (msgID == GetLeaseSetUpdateMsgID ()) + auto tags = new UnconfirmedTags (m_NumTags); + tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); + for (int i = 0; i < m_NumTags; i++) { - SetLeaseSetUpdateStatus (eLeaseSetUpToDate); - SetLeaseSetUpdateMsgID (0); - LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); - return true; + RAND_bytes (tags->sessionTags[i], 32); + tags->sessionTags[i].creationTime = tags->tagsCreationTime; } - return false; + return tags; } - void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts) + void GarlicRoutingSession::MessageConfirmed (uint32_t msgID) { - if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASESET_CONFIRMATION_TIMEOUT) + TagsConfirmed (msgID); + if (msgID == m_LeaseSetUpdateMsgID) { - if (GetOwner ()) - GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); + m_LeaseSetUpdateStatus = eLeaseSetUpToDate; + m_LeaseSetUpdateMsgID = 0; + LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); + } + else + CleanupExpiredTags (); + } + + void GarlicRoutingSession::TagsConfirmed (uint32_t msgID) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + auto it = m_UnconfirmedTagsMsgs.find (msgID); + if (it != m_UnconfirmedTagsMsgs.end ()) + { + auto& tags = it->second; + if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + { + for (int i = 0; i < tags->numTags; i++) + m_SessionTags.push_back (tags->sessionTags[i]); + } + m_UnconfirmedTagsMsgs.erase (it); + } + } + + bool GarlicRoutingSession::CleanupExpiredTags () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();) + { + if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + it = m_SessionTags.erase (it); + else + ++it; + } + CleanupUnconfirmedTags (); + if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + { + if (m_Owner) + m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); m_LeaseSetUpdateMsgID = 0; } + return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); } - std::shared_ptr GarlicRoutingSession::CreateEncryptedDeliveryStatusMsg (uint32_t msgID) + bool GarlicRoutingSession::CleanupUnconfirmedTags () { - auto msg = CreateDeliveryStatusMsg (msgID); - if (GetOwner ()) + bool ret = false; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + // delete expired unconfirmed tags + for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { - //encrypt - uint8_t key[32], tag[32]; - RAND_bytes (key, 32); // random session key - RAND_bytes (tag, 32); // random session tag - GetOwner ()->SubmitSessionKey (key, tag); - ElGamalAESSession garlic (key, tag); - msg = garlic.WrapSingleMessage (msg); + if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) + { + if (m_Owner) + m_Owner->RemoveDeliveryStatusSession (it->first); + it = m_UnconfirmedTagsMsgs.erase (it); + ret = true; + } + else + ++it; } - return msg; + return ret; } - ElGamalAESSession::ElGamalAESSession (GarlicDestination * owner, - std::shared_ptr destination, int numTags, bool attachLeaseSet): - GarlicRoutingSession (owner, attachLeaseSet), - m_Destination (destination), m_NumTags (numTags) - { - // create new session tags and session key - RAND_bytes (m_SessionKey, 32); - m_Encryption.SetKey (m_SessionKey); - } - - ElGamalAESSession::ElGamalAESSession (const uint8_t * sessionKey, const SessionTag& sessionTag): - m_NumTags(1) - { - memcpy (m_SessionKey, sessionKey, 32); - m_Encryption.SetKey (m_SessionKey); - m_SessionTags.push_back (sessionTag); - m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch (); - } - - std::shared_ptr ElGamalAESSession::WrapSingleMessage (std::shared_ptr msg) + std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -159,8 +188,10 @@ namespace garlic RAND_bytes (elGamal.preIV, 32); // Pre-IV uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - m_Destination->Encrypt ((uint8_t *)&elGamal, buf); - m_IV = iv; + BN_CTX * ctx = BN_CTX_new (); + m_Destination->Encrypt ((uint8_t *)&elGamal, buf, ctx); + BN_CTX_free (ctx); + m_Encryption.SetIV (iv); buf += 514; len += 514; } @@ -170,7 +201,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; } @@ -182,10 +213,10 @@ namespace garlic return m; } - size_t ElGamalAESSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) + size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) { size_t blockSize = 0; - bool createNewTags = GetOwner () && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); + bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr; htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count blockSize += 2; @@ -210,11 +241,11 @@ 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; } - size_t ElGamalAESSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) + size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t msgID; @@ -224,17 +255,17 @@ namespace garlic *numCloves = 0; size++; - if (GetOwner ()) + if (m_Owner) { // resubmit non-confirmed LeaseSet - if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT) + if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && ts > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) { - SetLeaseSetUpdateStatus (eLeaseSetUpdated); + m_LeaseSetUpdateStatus = eLeaseSetUpdated; SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed } // attach DeviveryStatus if necessary - if (newTags || GetLeaseSetUpdateStatus () == eLeaseSetUpdated) // new tags created or leaseset updated + if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated { // clove is DeliveryStatus auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID); @@ -248,25 +279,25 @@ namespace garlic m_UnconfirmedTagsMsgs.insert (std::make_pair(msgID, std::unique_ptr(newTags))); newTags = nullptr; // got acquired } - GetOwner ()->DeliveryStatusSent (shared_from_this (), msgID); + m_Owner->DeliveryStatusSent (shared_from_this (), msgID); } else LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created"); } // attach LeaseSet - if (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) + if (m_LeaseSetUpdateStatus == eLeaseSetUpdated) { - if (GetLeaseSetUpdateMsgID ()) GetOwner ()->RemoveDeliveryStatusSession (GetLeaseSetUpdateMsgID ()); // remove previous - SetLeaseSetUpdateStatus (eLeaseSetSubmitted); - SetLeaseSetUpdateMsgID (msgID); - SetLeaseSetSubmissionTime (ts); + if (m_LeaseSetUpdateMsgID) m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); // remove previous + m_LeaseSetUpdateStatus = eLeaseSetSubmitted; + m_LeaseSetUpdateMsgID = msgID; + m_LeaseSetSubmissionTime = ts; // clove if our leaseSet must be attached - auto leaseSet = CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()); + auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ()); size += CreateGarlicClove (payload + size, leaseSet, false); (*numCloves)++; } } - if (msg) // clove message itself if presented + if (msg) // clove message ifself if presented { size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false); (*numCloves)++; @@ -282,20 +313,20 @@ namespace garlic return size; } - size_t ElGamalAESSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) + size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec size_t size = 0; if (isDestination) { - buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination + buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination size++; memcpy (buf + size, m_Destination->GetIdentHash (), 32); size += 32; } else { - buf[size] = 0;// delivery instructions flag local + buf[size] = 0;// delivery instructions flag local size++; } @@ -312,12 +343,12 @@ namespace garlic return size; } - size_t ElGamalAESSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID) + size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID) { size_t size = 0; - if (GetOwner ()) + if (m_Owner) { - auto inboundTunnel = GetOwner ()->GetTunnelPool ()->GetNextInboundTunnel (); + auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel (); if (inboundTunnel) { buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel @@ -328,12 +359,19 @@ namespace garlic htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID size += 4; // create msg - auto msg = CreateEncryptedDeliveryStatusMsg (msgID); - if (msg) + auto msg = CreateDeliveryStatusMsg (msgID); + if (m_Owner) { - memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); - size += msg->GetLength (); + //encrypt + uint8_t key[32], tag[32]; + RAND_bytes (key, 32); // random session key + RAND_bytes (tag, 32); // random session tag + m_Owner->SubmitSessionKey (key, tag); + GarlicRoutingSession garlic (key, tag); + msg = garlic.WrapSingleMessage (msg); } + memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); + size += msg->GetLength (); // fill clove uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec uint32_t cloveID; @@ -354,103 +392,21 @@ namespace garlic return size; } - ElGamalAESSession::UnconfirmedTags * ElGamalAESSession::GenerateSessionTags () - { - auto tags = new UnconfirmedTags (m_NumTags); - tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); - for (int i = 0; i < m_NumTags; i++) - { - RAND_bytes (tags->sessionTags[i], 32); - tags->sessionTags[i].creationTime = tags->tagsCreationTime; - } - return tags; - } - - bool ElGamalAESSession::MessageConfirmed (uint32_t msgID) - { - TagsConfirmed (msgID); - if (!GarlicRoutingSession::MessageConfirmed (msgID)) - CleanupExpiredTags (); - return true; - } - - void ElGamalAESSession::TagsConfirmed (uint32_t msgID) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - auto it = m_UnconfirmedTagsMsgs.find (msgID); - if (it != m_UnconfirmedTagsMsgs.end ()) - { - auto& tags = it->second; - if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) - { - for (int i = 0; i < tags->numTags; i++) - m_SessionTags.push_back (tags->sessionTags[i]); - } - m_UnconfirmedTagsMsgs.erase (it); - } - } - - bool ElGamalAESSession::CleanupExpiredTags () - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();) - { - if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) - it = m_SessionTags.erase (it); - else - ++it; - } - CleanupUnconfirmedTags (); - CleanupUnconfirmedLeaseSet (ts); - return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); - } - - bool ElGamalAESSession::CleanupUnconfirmedTags () - { - bool ret = false; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - // delete expired unconfirmed tags - for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) - { - if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) - { - if (GetOwner ()) - GetOwner ()->RemoveDeliveryStatusSession (it->first); - it = m_UnconfirmedTagsMsgs.erase (it); - ret = true; - } - else - ++it; - } - return ret; - } - - GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0), - m_NumRatchetInboundTags (0) // 0 means standard + GarlicDestination::GarlicDestination (): m_NumTags (32) // 32 tags by default { + m_Ctx = BN_CTX_new (); } GarlicDestination::~GarlicDestination () { - if (m_PayloadBuffer) - delete[] m_PayloadBuffer; + BN_CTX_free (m_Ctx); } void GarlicDestination::CleanUp () { - for (auto it: m_Sessions) - it.second->SetOwner (nullptr); m_Sessions.clear (); m_DeliveryStatusSessions.clear (); m_Tags.clear (); - for (auto it: m_ECIESx25519Sessions) - { - it.second->Terminate (); - it.second->SetOwner (nullptr); - } - m_ECIESx25519Sessions.clear (); - m_ECIESx25519Tags.clear (); } void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag) { @@ -461,115 +417,55 @@ namespace garlic } } - void GarlicDestination::AddECIESx25519Key (const uint8_t * key, const uint8_t * tag) - { - uint64_t t; - memcpy (&t, tag, 8); - AddECIESx25519Key (key, t); - } - - void GarlicDestination::AddECIESx25519Key (const uint8_t * key, uint64_t tag) - { - auto tagset = std::make_shared(this, key); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{0, tagset}); - } - bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) { AddSessionKey (key, tag); return true; } - void GarlicDestination::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) - { - AddECIESx25519Key (key, tag); - } - void GarlicDestination::HandleGarlicMessage (std::shared_ptr msg) { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { - LogPrint (eLogWarning, "Garlic: Message length ", length, " exceeds I2NP message length ", msg->GetLength ()); + LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ()); return; } - auto mod = length & 0x0f; // %16 buf += 4; // length - - bool found = false; - bool supportsRatchets = SupportsRatchets (); - if (supportsRatchets) - // try ECIESx25519 tag - found = HandleECIESx25519TagMessage (buf, length); - if (!found) + auto it = m_Tags.find (SessionTag(buf)); + if (it != m_Tags.end ()) { - auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 - // AES tag might be used even if encryption type is not ElGamal/AES - if (it != m_Tags.end ()) // try AES tag + // tag found. Use AES + auto decryption = it->second; + m_Tags.erase (it); // tag might be used only once + if (length >= 32) { - // tag found. Use AES - auto decryption = it->second; - m_Tags.erase (it); // tag might be used only once - if (length >= 32) - { - uint8_t iv[32]; // IV is first 16 bytes - SHA256(buf, 32, iv); - decryption->Decrypt (buf + 32, length - 32, iv, buf + 32); - HandleAESBlock (buf + 32, length - 32, decryption, msg->from); - found = true; - } - else - LogPrint (eLogWarning, "Garlic: Message length ", length, " is less than 32 bytes"); - } - if (!found) // assume new session - { - // AES tag not found. Handle depending on encryption type - // try ElGamal/AES first if leading block is 514 - ElGamalBlock elGamal; - if (mod == 2 && length >= 514 && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) && - Decrypt (buf, (uint8_t *)&elGamal, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) - { - 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); - HandleAESBlock (buf + 514, length - 514, decryption, msg->from); - } - else if (supportsRatchets) - { - // 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 - 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"); + uint8_t iv[32]; // IV is first 16 bytes + SHA256(buf, 32, iv); + decryption->SetIV (iv); + decryption->Decrypt (buf + 32, length - 32, buf + 32); + HandleAESBlock (buf + 32, length - 32, decryption, msg->from); } + else + LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); } - } - - bool GarlicDestination::HandleECIESx25519TagMessage (uint8_t * buf, size_t len) - { - uint64_t tag; - memcpy (&tag, buf, 8); - auto it = m_ECIESx25519Tags.find (tag); - if (it != m_ECIESx25519Tags.end ()) + else { - if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index)) - LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it); - return true; + // tag not found. Use ElGamal + ElGamalBlock elGamal; + if (length >= 514 && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx)) + { + auto decryption = std::make_shared(elGamal.sessionKey); + uint8_t iv[32]; // IV is first 16 bytes + SHA256(elGamal.preIV, 32, iv); + decryption->SetIV (iv); + decryption->Decrypt(buf + 514, length - 514, buf + 514); + HandleAESBlock (buf + 514, length - 514, decryption, msg->from); + } + else + LogPrint (eLogError, "Garlic: Failed to decrypt message"); } - return false; } void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, @@ -608,7 +504,7 @@ namespace garlic SHA256 (buf, payloadSize, digest); if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match { - LogPrint (eLogError, "Garlic: Wrong payload hash"); + LogPrint (eLogError, "Garlic: wrong payload hash"); return; } HandleGarlicPayload (buf, payloadSize, from); @@ -618,7 +514,7 @@ namespace garlic { if (len < 1) { - LogPrint (eLogError, "Garlic: Payload is too short"); + LogPrint (eLogError, "Garlic: payload is too short"); return; } int numCloves = buf[0]; @@ -633,7 +529,7 @@ namespace garlic if (flag & 0x80) // encrypted? { // TODO: implement - LogPrint (eLogWarning, "Garlic: Clove encrypted"); + LogPrint (eLogWarning, "Garlic: clove encrypted"); buf += 32; } ptrdiff_t offset = buf - buf1; @@ -641,35 +537,35 @@ namespace garlic switch (deliveryType) { case eGarlicDeliveryTypeLocal: - LogPrint (eLogDebug, "Garlic: Type local"); + LogPrint (eLogDebug, "Garlic: type local"); if (offset > (int)len) { - LogPrint (eLogError, "Garlic: Message is too short"); + LogPrint (eLogError, "Garlic: message is too short"); break; } - HandleI2NPMessage (buf, len - offset); + HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeDestination: - LogPrint (eLogDebug, "Garlic: Type destination"); + LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // destination. check it later or for multiple destinations offset = buf - buf1; if (offset > (int)len) { - LogPrint (eLogError, "Garlic: Message is too short"); + LogPrint (eLogError, "Garlic: message is too short"); break; } - HandleI2NPMessage (buf, len - offset); + HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeTunnel: { - LogPrint (eLogDebug, "Garlic: Type tunnel"); + LogPrint (eLogDebug, "Garlic: type tunnel"); // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; offset = buf - buf1; if (offset + 4 > (int)len) { - LogPrint (eLogError, "Garlic: Message is too short"); + LogPrint (eLogError, "Garlic: message is too short"); break; } uint32_t gwTunnel = bufbe32toh (buf); @@ -683,7 +579,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"); } @@ -700,103 +596,63 @@ namespace garlic { if (offset > (int)len) { - LogPrint (eLogError, "Garlic: Message is too short"); + LogPrint (eLogError, "Garlic: message is too short"); break; } i2p::transport::transports.SendMessage (ident, CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset))); } else - LogPrint (eLogWarning, "Garlic: Type router for inbound tunnels not supported"); + LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported"); break; } default: - LogPrint (eLogWarning, "Garlic: Unknown delivery type ", (int)deliveryType); + LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } if (offset > (int)len) { - LogPrint (eLogError, "Garlic: Message is too short"); + LogPrint (eLogError, "Garlic: message is too short"); break; } - buf += GetI2NPMessageLength (buf, len - offset); // I2NP + buf += GetI2NPMessageLength (buf, len - offset); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate offset = buf - buf1; if (offset > (int)len) { - LogPrint (eLogError, "Garlic: Clove is too long"); + LogPrint (eLogError, "Garlic: clove is too long"); break; } len -= offset; } } - std::shared_ptr GarlicDestination::WrapMessageForRouter (std::shared_ptr router, - std::shared_ptr msg) + std::shared_ptr GarlicDestination::WrapMessage (std::shared_ptr destination, + std::shared_ptr msg, bool attachLeaseSet) { - if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - return WrapECIESX25519MessageForRouter (msg, router->GetIdentity ()->GetEncryptionPublicKey ()); - else - { - auto session = GetRoutingSession (router, false); - return session->WrapSingleMessage (msg); - } + auto session = GetRoutingSession (destination, attachLeaseSet); + return session->WrapSingleMessage (msg); } 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) + GarlicRoutingSessionPtr session; { - 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 ()) - { - session = it->second; - if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) - { - LogPrint (eLogDebug, "Garlic: Session restarted"); - requestNewIfNotFound = true; // it's not a new session - session = nullptr; - } - } - if (!session && requestNewIfNotFound) - { - session = std::make_shared (this, true); - session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey); - } - if (session && destination->IsDestination ()) - session->SetDestination (destination->GetIdentHash ()); // NS or NSR - return session; - } - else - LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ()); + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (destination->GetIdentHash ()); + if (it != m_Sessions.end ()) + session = it->second; } - else + if (!session) { - ElGamalAESSessionPtr session; - { - std::unique_lock l(m_SessionsMutex); - auto it = m_Sessions.find (destination->GetIdentHash ()); - if (it != m_Sessions.end ()) - session = it->second; - } - if (!session) - { - session = std::make_shared (this, destination, - attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests - std::unique_lock l(m_SessionsMutex); - m_Sessions[destination->GetIdentHash ()] = session; - } - return session; + session = std::make_shared (this, destination, + attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests + std::unique_lock l(m_SessionsMutex); + m_Sessions[destination->GetIdentHash ()] = session; } - return nullptr; + return session; } void GarlicDestination::CleanupExpiredTags () @@ -825,7 +681,7 @@ namespace garlic it->second->GetSharedRoutingPath (); // delete shared path if necessary if (!it->second->CleanupExpiredTags ()) { - LogPrint (eLogInfo, "Garlic: Routing session to ", it->first.ToBase32 (), " deleted"); + LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted"); it->second->SetOwner (nullptr); it = m_Sessions.erase (it); } @@ -844,40 +700,6 @@ namespace garlic ++it; } } - // ECIESx25519 - for (auto it = m_ECIESx25519Sessions.begin (); it != m_ECIESx25519Sessions.end ();) - { - if (it->second->CheckExpired (ts)) - { - it->second->Terminate (); - it = m_ECIESx25519Sessions.erase (it); - } - else - ++it; - } - - numExpiredTags = 0; - for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) - { - if (it->second.tagset->IsExpired (ts) || it->second.tagset->IsIndexExpired (it->second.index)) - { - it->second.tagset->DeleteSymmKey (it->second.index); - it = m_ECIESx25519Tags.erase (it); - numExpiredTags++; - } - else - { - if (it->second.tagset->IsSessionTerminated ()) - { - it = m_ECIESx25519Tags.erase (it); - numExpiredTags++; - } - else - ++it; - } - } - if (numExpiredTags > 0) - LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -892,8 +714,9 @@ namespace garlic m_DeliveryStatusSessions[msgID] = session; } - void GarlicDestination::HandleDeliveryStatusMessage (uint32_t msgID) + void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { + uint32_t msgID = bufbe32toh (msg->GetPayload ()); GarlicRoutingSessionPtr session; { std::unique_lock l(m_DeliveryStatusSessionsMutex); @@ -907,18 +730,14 @@ namespace garlic if (session) { session->MessageConfirmed (msgID); - LogPrint (eLogDebug, "Garlic: Message ", msgID, " acknowledged"); + LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); } } - void GarlicDestination::SetLeaseSetUpdated (bool post) + void GarlicDestination::SetLeaseSetUpdated () { - { - std::unique_lock l(m_SessionsMutex); - for (auto& it: m_Sessions) - it.second->SetLeaseSetUpdated (); - } - for (auto& it: m_ECIESx25519Sessions) + std::unique_lock l(m_SessionsMutex); + for (auto& it: m_Sessions) it.second->SetLeaseSetUpdated (); } @@ -929,8 +748,7 @@ namespace garlic void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); - HandleDeliveryStatusMessage (msgID); + HandleDeliveryStatusMessage (msg); } void GarlicDestination::SaveTags () @@ -988,7 +806,7 @@ namespace garlic m_Tags.insert (std::make_pair (SessionTag (tag, ts), decryption)); } if (!m_Tags.empty ()) - LogPrint (eLogInfo, "Garlic: ", m_Tags.size (), " tags loaded for ", ident); + LogPrint (eLogInfo, m_Tags.size (), " loaded for ", ident); } } i2p::fs::Remove (path); @@ -999,127 +817,9 @@ namespace garlic std::vector files; i2p::fs::ReadDir (i2p::fs::DataDirPath("tags"), files); uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it: files) + for (auto it: files) if (ts >= i2p::fs::GetLastUpdateTime (it) + INCOMING_TAGS_EXPIRATION_TIMEOUT) - i2p::fs::Remove (it); + i2p::fs::Remove (it); } - - void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, - ECIESX25519AEADRatchetSession * from) - { - const uint8_t * buf1 = buf; - uint8_t flag = buf[0]; buf++; // flag - GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); - switch (deliveryType) - { - case eGarlicDeliveryTypeDestination: - LogPrint (eLogDebug, "Garlic: Type destination"); - buf += 32; // TODO: check destination - [[fallthrough]]; - // no break here - case eGarlicDeliveryTypeLocal: - { - LogPrint (eLogDebug, "Garlic: Type local"); - I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - int32_t msgID = bufbe32toh (buf); buf += 4; // msgID - buf += 4; // expiration - ptrdiff_t offset = buf - buf1; - if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from); - else - LogPrint (eLogError, "Garlic: Clove is too long"); - break; - } - case eGarlicDeliveryTypeTunnel: - { - LogPrint (eLogDebug, "Garlic: Type tunnel"); - // gwHash and gwTunnel sequence is reverted - const uint8_t * gwHash = buf; - buf += 32; - ptrdiff_t offset = buf - buf1; - if (offset + 13 > (int)len) - { - LogPrint (eLogError, "Garlic: Message is too short"); - break; - } - uint32_t gwTunnel = bufbe32toh (buf); buf += 4; - I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID - buf += 4; // expiration - offset += 13; - if (GetTunnelPool ()) - { - auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); - if (tunnel) - tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); - else - LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); - } - else - LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); - break; - } - default: - LogPrint (eLogWarning, "Garlic: Unexpected delivery type ", (int)deliveryType); - } - } - - uint64_t GarlicDestination::AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset) - { - auto index = tagset->GetNextIndex (); - uint64_t tag = tagset->GetNextSessionTag (); - if (tag) - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); - return tag; - } - - void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) - { - i2p::data::Tag<32> staticKeyTag (staticKey); - auto it = m_ECIESx25519Sessions.find (staticKeyTag); - if (it != m_ECIESx25519Sessions.end ()) - { - if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) - { - it->second->Terminate (); // detach - m_ECIESx25519Sessions.erase (it); - } - else - { - LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); - return; - } - } - m_ECIESx25519Sessions.emplace (staticKeyTag, session); - } - - void GarlicDestination::RemoveECIESx25519Session (const uint8_t * staticKey) - { - auto it = m_ECIESx25519Sessions.find (staticKey); - if (it != m_ECIESx25519Sessions.end ()) - { - it->second->Terminate (); - m_ECIESx25519Sessions.erase (it); - } - } - - uint8_t * GarlicDestination::GetPayloadBuffer () - { - if (!m_PayloadBuffer) - 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 25106c45..c5236c90 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,16 +1,8 @@ -/* -* 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 GARLIC_H__ #define GARLIC_H__ #include -#include +#include #include #include #include @@ -50,9 +42,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> { @@ -89,13 +81,12 @@ namespace garlic std::shared_ptr remoteLease; int rtt; // RTT uint32_t updateTime; // seconds since epoch + int numTimesUsed; }; class GarlicDestination; - class GarlicRoutingSession + class GarlicRoutingSession: public std::enable_shared_from_this { - protected: - enum LeaseSetUpdateStatus { eLeaseSetUpToDate = 0, @@ -104,20 +95,27 @@ namespace garlic eLeaseSetDoNotSend }; + struct UnconfirmedTags + { + UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; + ~UnconfirmedTags () { delete[] sessionTags; }; + uint32_t msgID; + int numTags; + SessionTag * sessionTags; + uint32_t tagsCreationTime; + }; + public: - GarlicRoutingSession (GarlicDestination * owner, bool attachLeaseSet); - 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 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 - + GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, + int numTags, bool attachLeaseSet); + GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption + ~GarlicRoutingSession (); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + void MessageConfirmed (uint32_t msgID); + bool CleanupExpiredTags (); // returns true if something left + bool CleanupUnconfirmedTags (); // returns true if something has been deleted + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -125,67 +123,13 @@ namespace garlic bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; }; bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; }; uint64_t GetLeaseSetSubmissionTime () const { return m_LeaseSetSubmissionTime; } - void CleanupUnconfirmedLeaseSet (uint64_t ts); std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); - GarlicDestination * GetOwner () const { return m_Owner; } + const GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } - protected: - - LeaseSetUpdateStatus GetLeaseSetUpdateStatus () const { return m_LeaseSetUpdateStatus; } - void SetLeaseSetUpdateStatus (LeaseSetUpdateStatus status) { m_LeaseSetUpdateStatus = status; } - uint32_t GetLeaseSetUpdateMsgID () const { return m_LeaseSetUpdateMsgID; } - void SetLeaseSetUpdateMsgID (uint32_t msgID) { m_LeaseSetUpdateMsgID = msgID; } - void SetLeaseSetSubmissionTime (uint64_t ts) { m_LeaseSetSubmissionTime = ts; } - - std::shared_ptr CreateEncryptedDeliveryStatusMsg (uint32_t msgID); - - private: - - GarlicDestination * m_Owner; - - LeaseSetUpdateStatus m_LeaseSetUpdateStatus; - uint32_t m_LeaseSetUpdateMsgID; - uint64_t m_LeaseSetSubmissionTime; // in milliseconds - - std::shared_ptr m_SharedRoutingPath; - - public: - - // for HTTP only - virtual size_t GetNumOutgoingTags () const { return 0; }; - }; - //using GarlicRoutingSessionPtr = std::shared_ptr; - typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 - - class ElGamalAESSession: public GarlicRoutingSession, public std::enable_shared_from_this - { - struct UnconfirmedTags - { - UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; - ~UnconfirmedTags () { delete[] sessionTags; }; - uint32_t msgID; - int numTags; - SessionTag * sessionTags; - uint32_t tagsCreationTime; - }; - - public: - - ElGamalAESSession (GarlicDestination * owner, std::shared_ptr destination, - int numTags, bool attachLeaseSet); - ElGamalAESSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption - ~ElGamalAESSession () {}; - - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - - bool MessageConfirmed (uint32_t msgID); - bool CleanupExpiredTags (); // returns true if something left - bool CleanupUnconfirmedTags (); // returns true if something has been deleted - private: size_t CreateAESBlock (uint8_t * buf, std::shared_ptr msg); @@ -198,6 +142,7 @@ namespace garlic private: + GarlicDestination * m_Owner; std::shared_ptr m_Destination; i2p::crypto::AESKey m_SessionKey; @@ -205,25 +150,20 @@ namespace garlic int m_NumTags; std::map > m_UnconfirmedTagsMsgs; // msgID->tags + LeaseSetUpdateStatus m_LeaseSetUpdateStatus; + uint32_t m_LeaseSetUpdateMsgID; + uint64_t m_LeaseSetSubmissionTime; // in milliseconds + i2p::crypto::CBCEncryption m_Encryption; - i2p::data::Tag<16> m_IV; + + std::shared_ptr m_SharedRoutingPath; public: - // for HTTP only size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; }; - typedef std::shared_ptr ElGamalAESSessionPtr; - - class ECIESX25519AEADRatchetSession; - typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; - class ReceiveRatchetTagSet; - typedef std::shared_ptr ReceiveRatchetTagSetPtr; - struct ECIESX25519AEADRatchetIndexTagset - { - int index; - ReceiveRatchetTagSetPtr tagset; // null if used - }; + //using GarlicRoutingSessionPtr = std::shared_ptr; + typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 class GarlicDestination: public i2p::data::LocalDestination { @@ -234,90 +174,56 @@ namespace garlic void CleanUp (); void SetNumTags (int numTags) { m_NumTags = numTags; }; - 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); + std::shared_ptr WrapMessage (std::shared_ptr destination, + std::shared_ptr msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag - void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread - virtual void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - 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); - 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; - } + virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 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; void HandleGarlicMessage (std::shared_ptr msg); - void HandleDeliveryStatusMessage (uint32_t msgID); + void HandleDeliveryStatusMessage (std::shared_ptr msg); 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); private: + BN_CTX * m_Ctx; // incoming // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; - 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 + std::map m_Sessions; // incoming - int m_NumRatchetInboundTags; - std::unordered_map, std::hash > > m_Tags; - std::unordered_map m_ECIESx25519Tags; // session tag -> session + std::map > m_Tags; // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; - std::unordered_map m_DeliveryStatusSessions; // msgID -> session - // encryption - i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; - i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; - + std::map m_DeliveryStatusSessions; // msgID -> session + public: // for HTTP only size_t GetNumIncomingTags () const { return m_Tags.size (); } - size_t GetNumIncomingECIESx25519Tags () const { return m_ECIESx25519Tags.size (); } const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; - const decltype(m_ECIESx25519Sessions)& GetECIESx25519Sessions () const { return m_ECIESx25519Sessions; } }; void CleanUpTagsFiles (); diff --git a/libi2pd/Gost.cpp b/libi2pd/Gost.cpp index 2dafc9ae..c401f8be 100644 --- a/libi2pd/Gost.cpp +++ b/libi2pd/Gost.cpp @@ -1,11 +1,3 @@ -/* -* 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 @@ -96,7 +88,7 @@ namespace crypto EC_POINT * C = EC_POINT_new (m_Group); EC_POINT_mul (m_Group, C, z1, pub, z2, ctx); // z1*P + z2*pub BIGNUM * x = BN_CTX_get (ctx); - GetXY (C, x, nullptr); // Cx + GetXY (C, x, nullptr); // Cx BN_mod (x, x, q, ctx); // Cx % q bool ret = !BN_cmp (x, r); // Cx = r ? EC_POINT_free (C); @@ -111,8 +103,8 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); EC_POINT * C = EC_POINT_new (m_Group); // C = k*P = (rx, ry) - EC_POINT * Q = nullptr; - if (EC_POINT_set_compressed_coordinates_GFp (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) + EC_POINT * Q = nullptr; + 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/Gost.h b/libi2pd/Gost.h index 0a79c30b..30386104 100644 --- a/libi2pd/Gost.h +++ b/libi2pd/Gost.h @@ -1,11 +1,3 @@ -/* -* 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 GOST_H__ #define GOST_H__ @@ -21,11 +13,11 @@ namespace crypto enum GOSTR3410ParamSet { - eGOSTR3410CryptoProA = 0, // 1.2.643.2.2.35.1 + eGOSTR3410CryptoProA = 0, // 1.2.643.2.2.35.1 // XchA = A, XchB = C - //eGOSTR3410CryptoProXchA, // 1.2.643.2.2.36.0 - //eGOSTR3410CryptoProXchB, // 1.2.643.2.2.36.1 - eGOSTR3410TC26A512, // 1.2.643.7.1.2.1.2.1 + //eGOSTR3410CryptoProXchA, // 1.2.643.2.2.36.0 + //eGOSTR3410CryptoProXchB, // 1.2.643.2.2.36.1 + eGOSTR3410TC26A512, // 1.2.643.7.1.2.1.2.1 eGOSTR3410NumParamSets }; diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 4be8684c..1c06e941 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2017, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,6 @@ #include /* memset */ #include #include "Log.h" -#include "I2PEndian.h" #include "Gzip.h" namespace i2p @@ -32,35 +31,18 @@ namespace data size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) { - if (inLen < 23) return 0; - if (in[10] == 0x01) // non compressed - { - size_t len = bufle16toh (in + 11); - if (len + 23 < inLen) - { - LogPrint (eLogError, "Gzip: Incorrect length"); - return 0; - } - if (len > outLen) len = outLen; - memcpy (out, in + 15, len); - return len; - } - else - { - if (m_IsDirty) inflateReset (&m_Inflator); - m_IsDirty = true; - m_Inflator.next_in = const_cast(in); - m_Inflator.avail_in = inLen; - m_Inflator.next_out = out; - m_Inflator.avail_out = outLen; - int err; - 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); - return 0; - } + if (m_IsDirty) inflateReset (&m_Inflator); + m_IsDirty = true; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + m_Inflator.next_out = out; + m_Inflator.avail_out = outLen; + int err; + if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) + return outLen - m_Inflator.avail_out; + // else + LogPrint (eLogError, "Gzip: Inflate error ", err); + return 0; } void GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& os) @@ -124,81 +106,10 @@ namespace data m_Deflator.avail_out = outLen; int err; if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) - { - out[9] = 0xff; // OS is always unknown return outLen - m_Deflator.avail_out; - } // else - if (err) - LogPrint (eLogError, "Gzip: Deflate error ", err); + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } - - size_t GzipDeflator::Deflate (const std::vector >& bufs, uint8_t * out, size_t outLen) - { - if (m_IsDirty) deflateReset (&m_Deflator); - m_IsDirty = true; - size_t offset = 0; - int err = 0; - for (const auto& it: bufs) - { - m_Deflator.next_in = const_cast(it.first); - m_Deflator.avail_in = it.second; - m_Deflator.next_out = out + offset; - m_Deflator.avail_out = outLen - offset; - auto flush = (it == bufs.back ()) ? Z_FINISH : Z_NO_FLUSH; - err = deflate (&m_Deflator, flush); - if (err) - { - if (flush && err == Z_STREAM_END) - { - out[9] = 0xff; // OS is always unknown - return outLen - m_Deflator.avail_out; - } - break; - } - offset = outLen - m_Deflator.avail_out; - } - // else - if (err) - LogPrint (eLogError, "Gzip: Deflate error ", err); - return 0; - } - - size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) - { - static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; - if (outLen < (size_t)inLen + 23) return 0; - memcpy (out, gzipHeader, 11); - htole16buf (out + 11, inLen); - htole16buf (out + 13, 0xffff - inLen); - memcpy (out + 15, in, inLen); - htole32buf (out + inLen + 15, crc32 (0, in, inLen)); - htole32buf (out + inLen + 19, inLen); - return inLen + 23; - } - - size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen) - { - static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; - memcpy (out, gzipHeader, 11); - uint32_t crc = 0; - size_t len = 0, len1; - for (const auto& it: bufs) - { - len1 = len; - len += it.second; - if (outLen < len + 23) return 0; - memcpy (out + 15 + len1, it.first, it.second); - crc = crc32 (crc, it.first, it.second); - } - if (len > 0xffff) return 0; - htole32buf (out + len + 15, crc); - htole32buf (out + len + 19, len); - htole16buf (out + 11, len); - htole16buf (out + 13, 0xffff - len); - return len + 23; - } - } // data } // i2p diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index d0bb28ed..35661abe 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -1,21 +1,10 @@ -/* -* 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 GZIP_H__ #define GZIP_H__ #include -#include -namespace i2p -{ -namespace data -{ +namespace i2p { +namespace data { class GzipInflator { public: @@ -43,16 +32,12 @@ namespace data void SetCompressionLevel (int level); size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); - size_t Deflate (const std::vector >& bufs, uint8_t * out, size_t outLen); private: z_stream m_Deflator; bool m_IsDirty; }; - - size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen); // for < 64K - size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen); // for total size < 64K } // data } // i2p diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 3cd5c193..c1a46e73 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2017, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,344 +9,288 @@ #include #include #include -#include -#include #include "util.h" -#include "Base.h" #include "HTTP.h" +#include -namespace i2p -{ -namespace http -{ - // list of valid HTTP methods - static constexpr std::array HTTP_METHODS = - { - "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods - "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 - }; +namespace i2p { +namespace http { + const std::vector HTTP_METHODS = { + "GET", "HEAD", "POST", "PUT", "PATCH", + "DELETE", "OPTIONS", "CONNECT" + }; + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" + }; + const std::vector weekdays = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + const std::vector months = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; - // list of valid HTTP versions - static constexpr std::array HTTP_VERSIONS = - { - "HTTP/1.0", "HTTP/1.1" - }; - - static constexpr std::array weekdays = - { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - - static constexpr std::array months = - { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); + } - static inline bool is_http_version(std::string_view str) - { - return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { + std::size_t count = 0; + std::stringstream ss(line); + std::string token; + while (1) { + count++; + if (limit > 0 && count >= limit) + delim = '\n'; /* reset delimiter */ + if (!std::getline(ss, token, delim)) + break; + tokens.push_back(token); + } + } + + 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::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::make_pair("", ""); // no following space, but something else } + return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + } - static inline bool is_http_method(std::string_view 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 = 0, pos; - while ((pos = line.find (delim)) != line.npos) - { - count++; - if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter - tokens.push_back (line.substr (0, pos)); - line = line.substr (pos + 1); - } - if (!line.empty ()) tokens.push_back (line); - } - - static std::pair parse_header_line(std::string_view line) - { - std::size_t pos = 0; - std::size_t len = 1; /*: */ - std::size_t max = line.length(); - if ((pos = line.find(':', pos)) == std::string::npos) - return std::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::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))}; - } + void gen_rfc7231_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::tm *tm = std::gmtime(&now); + snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", + weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], + tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec + ); + out = buf; + } - void gen_rfc7231_date(std::string & out) { - std::time_t now = std::time(nullptr); - char buf[128]; - std::tm *tm = std::gmtime(&now); - snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", - weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], - tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec - ); - out = buf; - } + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); + } - bool URL::parse(const char *str, std::size_t len) - { - return parse({str, len ? len : strlen(str)}); - } + bool URL::parse(const std::string& url) { + std::size_t pos_p = 0; /* < current parse position */ + std::size_t pos_c = 0; /* < work position */ + 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) { + user = url.substr(pos_p, delim - pos_p); + delim += 1; + pass = url.substr(delim, pos_c - delim); + } else if(delim) { + user = url.substr(pos_p, pos_c - pos_p); + } + pos_p = pos_c + 1; + } + /* hostname[:port][/path] */ + pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { + /* only hostname, without post and path */ + host = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == ':') { + host = url.substr(pos_p, pos_c - pos_p); + /* port[/path] */ + pos_p = pos_c + 1; + pos_c = url.find('/', pos_p); + std::string port_str = (pos_c == std::string::npos) + ? 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 */ + for (char c : port_str) { + if (c < '0' || c > '9') + return false; + port *= 10; + port += c - '0'; + } + if (pos_c == std::string::npos) + return true; /* no path part */ + pos_p = pos_c; + } else { + /* start of path part found */ + host = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c; + } + } - bool URL::parse(std::string_view url) - { - if (url.empty ()) return false; - std::size_t pos_p = 0; /* < current parse position */ - std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) - { - std::size_t pos_s = 0; + /* pos_p now at start of path part */ + pos_c = url.find_first_of("?#", pos_p); + if (pos_c == std::string::npos) { + /* only path, without fragment and query */ + path = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == '?') { + /* found query part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + pos_c = url.find('#', pos_p); + if (pos_c == std::string::npos) { + /* no fragment */ + query = url.substr(pos_p, std::string::npos); + return true; + } else { + query = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + } else { + /* found fragment part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } - /* schema */ - pos_c = url.find("://"); - if (pos_c != std::string::npos) { - schema = url.substr(0, pos_c); - pos_p = pos_c + 3; - } + /* pos_p now at start of fragment part */ + frag = url.substr(pos_p, std::string::npos); + return true; + } - /* 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 */ + bool URL::parse_query(std::map & params) { + std::vector tokens; + strsplit(query, tokens, '&'); - 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) { - user = url.substr(pos_p, delim - pos_p); - delim += 1; - pass = url.substr(delim, pos_c - delim); - } else if(delim) { - user = url.substr(pos_p, pos_c - pos_p); - } - pos_p = pos_c + 1; - } + params.clear(); + for (const auto& it : tokens) { + std::size_t eq = it.find ('='); + if (eq != std::string::npos) { + auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); + params.insert(e); + } else { + auto e = std::pair(it, ""); + params.insert(e); + } + } + return true; + } - /* hostname[:port][/path] */ - if (url.at(pos_p) == '[') // ipv6 - { - auto pos_b = url.find(']', pos_p); - if (pos_b == std::string::npos) return false; - ipv6 = true; - pos_c = url.find_first_of(":/", pos_b); - } - else - pos_c = url.find_first_of(":/", pos_p); - - if (pos_c == std::string::npos) { - /* only hostname, without post and path */ - host = ipv6 ? - url.substr(pos_p + 1, url.length() - 1) : - url.substr(pos_p, std::string::npos); - return true; - } else if (url.at(pos_c) == ':') { - host = ipv6 ? - url.substr(pos_p + 1, pos_c - pos_p - 2) : - url.substr(pos_p, pos_c - pos_p); - /* port[/path] */ - pos_p = pos_c + 1; - pos_c = url.find('/', pos_p); - std::string_view port_str = (pos_c == std::string::npos) - ? url.substr(pos_p, std::string::npos) - : url.substr(pos_p, pos_c - pos_p); - /* stoi throws exception on failure, we don't need it */ - port = 0; - for (char c : port_str) { - if (c < '0' || c > '9') - return false; - port *= 10; - port += c - '0'; - } - if (pos_c == std::string::npos) - return true; /* no path part */ - 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); - pos_p = pos_c; - } - } - - /* pos_p now at start of path part */ - pos_c = url.find_first_of("?#", pos_p); - if (pos_c == std::string::npos) { - /* only path, without fragment and query */ - path = url.substr(pos_p, std::string::npos); - 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); - if (pos_c == std::string::npos) { - /* no fragment */ - query = url.substr(pos_p, std::string::npos); - return true; - } else { - query = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - } else { - /* found fragment part */ - path = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - - /* pos_p now at start of fragment part */ - frag = url.substr(pos_p, std::string::npos); - return true; - } - - bool URL::parse_query(std::map & params) - { - std::vector tokens; - strsplit(query, tokens, '&'); - - params.clear(); - for (const auto& it : tokens) { - if (!it.length()) // empty - continue; - std::size_t eq = it.find ('='); - if (eq != std::string::npos) { - auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); - params.insert(e); - } else { - auto e = std::pair(it, ""); - params.insert(e); - } - } - return true; - } - - std::string URL::to_string() { - std::string out = ""; - if (schema != "") { - out = schema + "://"; - if (user != "" && pass != "") { - out += user + ":" + pass + "@"; - } else if (user != "") { - out += user + "@"; - } - if (ipv6) { - if (port) { - out += "[" + host + "]:" + std::to_string(port); - } else { - out += "[" + host + "]"; - } - } else { - if (port) { - out += host + ":" + std::to_string(port); - } else { - out += host; - } - } - } - out += path; - if (hasquery) // add query even if it was empty - out += "?"; - if (query != "") - out += query; - if (frag != "") - out += "#" + frag; - return out; - } + std::string URL::to_string() { + std::string out = ""; + if (schema != "") { + out = schema + "://"; + if (user != "" && pass != "") { + out += user + ":" + pass + "@"; + } else if (user != "") { + out += user + "@"; + } + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } + } + out += path; + if (query != "") + out += "?" + query; + if (frag != "") + out += "#" + frag; + return out; + } bool URL::is_i2p() const { return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) { - add_header(name, value.c_str(), replace); - } + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + add_header(name, value.c_str(), replace); + } - void HTTPMsg::add_header(const char *name, const char *value, bool replace) { - std::size_t count = headers.count(name); - if (count && !replace) - return; - if (count) { - headers[name] = value; - return; - } - headers.insert(std::pair(name, value)); - } + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { + std::size_t count = headers.count(name); + if (count && !replace) + return; + if (count) { + headers[name] = value; + return; + } + headers.insert(std::pair(name, value)); + } - void HTTPMsg::del_header(const char *name) { - headers.erase(name); - } + void HTTPMsg::del_header(const char *name) { + 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) - { - 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; - URL url; + 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; + URL url; - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ + 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; - strsplit(line, tokens, ' '); - - if (tokens.size() != 3) - return -1; - if (!is_http_method(tokens[0])) - return -1; - if (!is_http_version(tokens[2])) - return -1; - if (!url.parse(tokens[1])) - return -1; - /* all ok */ - method = tokens[0]; - uri = tokens[1]; - version = tokens[2]; - expect = HEADER_LINE; - } - else - { - std::string_view line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.push_back (p); - else - return -1; - } - pos = eol + CRLF.length(); - if (pos >= eoh) - break; - } - return eoh + HTTP_EOH.length(); - } + 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])) + return -1; + if (!is_http_version(tokens[2])) + return -1; + if (!url.parse(tokens[1])) + return -1; + /* all ok */ + method = tokens[0]; + uri = tokens[1]; + version = tokens[2]; + expect = HEADER_LINE; + } + else + { + 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 + strlen(CRLF); + if (pos >= eoh) + break; + } + return eoh + strlen(HTTP_EOH); + } - void HTTPReq::write(std::ostream & o) - { - o << method << " " << uri << " " << version << CRLF; - for (auto & h : headers) - o << h.first << ": " << h.second << CRLF; - o << CRLF; - } + void HTTPReq::write(std::ostream & o) + { + o << method << " " << uri << " " << version << CRLF; + for (auto & h : headers) + o << h.first << ": " << h.second << CRLF; + o << CRLF; + } std::string HTTPReq::to_string() { @@ -362,7 +306,7 @@ namespace http void HTTPReq::UpdateHeader (const std::string& name, const std::string& value) { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) { it.second = value; @@ -381,207 +325,179 @@ namespace http } } - std::string HTTPReq::GetHeader (std::string_view name) const + std::string HTTPReq::GetHeader (const std::string& name) const { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) return it.second; 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"); + if (it == headers.end()) + return false; + if (it->second.find("chunked") == std::string::npos) + return true; + return false; + } - bool HTTPRes::is_chunked() const - { - auto it = headers.find("Transfer-Encoding"); - if (it == headers.end()) - return false; - if (it->second.find("chunked") != std::string::npos) - return true; - return false; - } + bool HTTPRes::is_gzipped(bool includingI2PGzip) const + { + auto it = headers.find("Content-Encoding"); + if (it == headers.end()) + return false; /* no header */ + if (it->second.find("gzip") != std::string::npos) + return true; /* gotcha! */ + if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) + return true; + return false; + } - bool HTTPRes::is_gzipped(bool includingI2PGzip) const - { - auto it = headers.find("Content-Encoding"); - if (it == headers.end()) - return false; /* no header */ - if (it->second.find("gzip") != std::string::npos) - return true; /* gotcha! */ - if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) - return true; - return false; - } + long int HTTPMsg::content_length() const + { + unsigned long int length = 0; + auto it = headers.find("Content-Length"); + if (it == headers.end()) + return -1; + errno = 0; + length = std::strtoul(it->second.c_str(), (char **) NULL, 10); + if (errno != 0) + return -1; + return length; + } - long int HTTPMsg::content_length() const - { - unsigned long int length = 0; - auto it = headers.find("Content-Length"); - if (it == headers.end()) - return -1; - errno = 0; - length = std::strtoul(it->second.c_str(), (char **) NULL, 10); - if (errno != 0) - return -1; - return length; - } + int HTTPRes::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } - int HTTPRes::parse(const char *buf, size_t len) - { - return parse({buf,len}); - } + int HTTPRes::parse(const std::string& str) { + 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; - int HTTPRes::parse(std::string_view str) - { - enum { RES_LINE, HEADER_LINE } expect = RES_LINE; - std::size_t eoh = str.find(HTTP_EOH); /* request head size */ - std::size_t eol = 0, pos = 0; + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == RES_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' ', 3); + if (tokens.size() != 3) + return -1; + if (!is_http_version(tokens[0])) + 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 line = str.substr(pos, eol - pos); + auto p = parse_header_line(line); + if (p.first.length () > 0) + headers.insert (p); + else + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } - while ((eol = str.find(CRLF, pos)) != std::string::npos) - { - if (expect == RES_LINE) - { - std::string_view line = str.substr(pos, eol - pos); - std::vector tokens; - strsplit(line, tokens, ' ', 3); - if (tokens.size() != 3) - return -1; - if (!is_http_version(tokens[0])) - return -1; - auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); - if (res.ec != std::errc()) - return -1; - if (code < 100 || code >= 600) - return -1; - /* all ok */ - version = tokens[0]; - status = tokens[2]; - expect = HEADER_LINE; - } - else - { - std::string_view line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.insert (p); - else - return -1; - } - pos = eol + CRLF.length(); - if (pos >= eoh) - break; - } - return eoh + HTTP_EOH.length(); - } + return eoh + strlen(HTTP_EOH); + } - std::string HTTPRes::to_string() { - if (version == "HTTP/1.1" && headers.count("Date") == 0) { - std::string date; - gen_rfc7231_date(date); - add_header("Date", date.c_str()); - } - if (status == "OK" && code != 200) - status = HTTPCodeToStatus(code); // update - if (body.length() > 0 && headers.count("Content-Length") == 0) - add_header("Content-Length", std::to_string(body.length()).c_str()); - /* build response */ - std::stringstream ss; - ss << version << " " << code << " " << status << CRLF; - for (auto & h : headers) { - ss << h.first << ": " << h.second << CRLF; - } - ss << CRLF; - if (body.length() > 0) - ss << body; - return ss.str(); - } + std::string HTTPRes::to_string() { + if (version == "HTTP/1.1" && headers.count("Date") == 0) { + std::string date; + gen_rfc7231_date(date); + add_header("Date", date.c_str()); + } + if (status == "OK" && code != 200) + status = HTTPCodeToStatus(code); // update + if (body.length() > 0 && headers.count("Content-Length") == 0) + add_header("Content-Length", std::to_string(body.length()).c_str()); + /* build response */ + std::stringstream ss; + ss << version << " " << code << " " << status << CRLF; + for (auto & h : headers) { + ss << h.first << ": " << h.second << CRLF; + } + ss << CRLF; + if (body.length() > 0) + ss << body; + return ss.str(); + } - std::string_view HTTPCodeToStatus(int code) - { - std::string_view ptr; - switch (code) - { - case 105: ptr = "Name Not Resolved"; break; - /* success */ - case 200: ptr = "OK"; break; - case 206: ptr = "Partial Content"; break; - /* redirect */ - case 301: ptr = "Moved Permanently"; break; - case 302: ptr = "Found"; break; - case 304: ptr = "Not Modified"; break; - case 307: ptr = "Temporary Redirect"; break; - /* client error */ - case 400: ptr = "Bad Request"; break; - case 401: ptr = "Unauthorized"; break; - case 403: ptr = "Forbidden"; break; - case 404: ptr = "Not Found"; break; - case 407: ptr = "Proxy Authentication Required"; break; - case 408: ptr = "Request Timeout"; break; - /* server error */ - case 500: ptr = "Internal Server Error"; break; - case 502: ptr = "Bad Gateway"; break; - case 503: ptr = "Not Implemented"; break; - case 504: ptr = "Gateway Timeout"; break; - default: ptr = "Unknown Status"; break; - } - return ptr; - } + const char * HTTPCodeToStatus(int code) { + const char *ptr; + switch (code) { + case 105: ptr = "Name Not Resolved"; break; + /* success */ + case 200: ptr = "OK"; break; + case 206: ptr = "Partial Content"; break; + /* redirect */ + case 301: ptr = "Moved Permanently"; break; + case 302: ptr = "Found"; break; + case 304: ptr = "Not Modified"; break; + case 307: ptr = "Temporary Redirect"; break; + /* client error */ + case 400: ptr = "Bad Request"; break; + case 401: ptr = "Unauthorized"; break; + case 403: ptr = "Forbidden"; break; + case 404: ptr = "Not Found"; break; + case 407: ptr = "Proxy Authentication Required"; break; + case 408: ptr = "Request Timeout"; break; + /* server error */ + case 500: ptr = "Internal Server Error"; break; + case 502: ptr = "Bad Gateway"; break; + case 503: ptr = "Not Implemented"; break; + case 504: ptr = "Gateway Timeout"; break; + default: ptr = "Unknown Status"; break; + } + 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) - { - pos += 3; - continue; - } - decoded.replace(pos, 3, 1, c); - pos++; - } - return decoded; - } - - bool MergeChunkedResponse (std::istream& in, std::ostream& out) - { - std::string hexLen; - while (!in.eof ()) - { - std::getline (in, hexLen); - errno = 0; - long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); - if (errno != 0) - return false; /* conversion error */ - if (len == 0) - return true; /* end of stream */ - if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ - return false; /* too large chunk */ - char * buf = new char[len]; - in.read (buf, len); - out.write (buf, len); - delete[] buf; - std::getline (in, hexLen); // read \r\n after chunk - } - return true; - } - - std::string CreateBasicAuthorizationString (const std::string& user, const std::string& pass) - { - if (user.empty () && pass.empty ()) return ""; - return "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); - } + size_t pos = 0; + while ((pos = decoded.find('%', pos)) != std::string::npos) { + char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); + if (c == '\0' && !allow_null) { + pos += 3; + continue; + } + decoded.replace(pos, 3, 1, c); + pos++; + } + return decoded; + } + bool MergeChunkedResponse (std::istream& in, std::ostream& out) { + std::string hexLen; + while (!in.eof ()) { + std::getline (in, hexLen); + errno = 0; + long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); + if (errno != 0) + return false; /* conversion error */ + if (len == 0) + return true; /* end of stream */ + if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ + return false; /* too large chunk */ + char * buf = new char[len]; + in.read (buf, len); + out.write (buf, len); + delete[] buf; + std::getline (in, hexLen); // read \r\n after chunk + } + return true; + } } // http } // i2p diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index c65c1ce4..837faf01 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,164 +14,158 @@ #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 - { - std::string schema; - std::string user; - std::string pass; - std::string host; - unsigned short int port; - std::string path; - bool hasquery; - std::string query; - std::string frag; - bool ipv6; + struct URL + { + std::string schema; + std::string user; + std::string pass; + std::string host; + unsigned short int port; + std::string path; + std::string query; + std::string frag; - 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); + /** + * @brief Tries to parse url from string + * @return true on success, false on invalid url + */ + bool parse (const char *str, std::size_t len = 0); + bool parse (const std::string& url); - /** - * @brief Parse query part of url to key/value map - * @note Honestly, this should be implemented with std::multimap - */ - bool parse_query(std::map & params); + /** + * @brief Parse query part of url to key/value map + * @note Honestly, this should be implemented with std::multimap + */ + bool parse_query(std::map & params); - /** - * @brief Serialize URL structure to url - * @note Returns relative url if schema if empty, absolute url otherwise - */ - std::string to_string (); + /** + * @brief Serialize URL structure to url + * @note Returns relative url if schema if empty, absolute url otherwise + */ + std::string to_string (); - /** - * @brief return true if the host is inside i2p - */ - bool is_i2p() const; - }; + /** + * @brief return true if the host is inside i2p + */ + bool is_i2p() const; + }; - struct HTTPMsg - { - std::map headers; + struct HTTPMsg + { + std::map headers; - void add_header(const char *name, const std::string & value, bool replace = false); - void add_header(const char *name, const char *value, bool replace = false); - void del_header(const char *name); + 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); - /** @brief Returns declared message length or -1 if unknown */ - long int content_length() const; - }; + /** @brief Returns declared message length or -1 if unknown */ + long int content_length() const; + }; - struct HTTPReq - { - std::list > headers; - std::string version; - std::string method; - std::string uri; + struct HTTPReq + { + std::list > headers; + std::string version; + std::string method; + std::string uri; - HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; + HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; - /** - * @brief Tries to parse HTTP request from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(std::string_view buf); + /** + * @brief Tries to parse HTTP request from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); - /** @brief Serialize HTTP request to string */ - std::string to_string(); + /** @brief Serialize HTTP request to string */ + std::string to_string(); void write(std::ostream & o); - void AddHeader (const std::string& name, const std::string& value); - 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 (); }; - }; + void AddHeader (const std::string& name, const std::string& value); + void UpdateHeader (const std::string& name, const std::string& value); + void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt + void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; + std::string GetHeader (const std::string& name) const; + }; - struct HTTPRes : HTTPMsg { - std::string version; - std::string status; - unsigned short int code; - /** - * @brief Simplifies response generation - * - * If this variable is set, on @a to_string() call: - * * Content-Length header will be added if missing, - * * contents of @a body will be included in generated response - */ - std::string body; + struct HTTPRes : HTTPMsg { + std::string version; + std::string status; + unsigned short int code; + /** + * @brief Simplifies response generation + * + * If this variable is set, on @a to_string() call: + * * Content-Length header will be added if missing, + * * contents of @a body will be included in generated response + */ + std::string body; - HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} + HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} - /** - * @brief Tries to parse HTTP response from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(const std::string_view buf); + /** + * @brief Tries to parse HTTP response from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); - /** - * @brief Serialize HTTP response to string - * @note If @a version is set to HTTP/1.1, and Date header is missing, - * it will be generated based on current time and added to headers - * @note If @a body is set and Content-Length header is missing, - * this header will be added, based on body's length - */ - std::string to_string(); + /** + * @brief Serialize HTTP response to string + * @note If @a version is set to HTTP/1.1, and Date header is missing, + * it will be generated based on current time and added to headers + * @note If @a body is set and Content-Length header is missing, + * this header will be added, based on body's length + */ + std::string to_string(); void write(std::ostream & o); - /** @brief Checks that response declared as chunked data */ - bool is_chunked() const ; + /** @brief Checks that response declared as chunked data */ + bool is_chunked() const ; - /** @brief Checks that response contains compressed data */ - bool is_gzipped(bool includingI2PGzip = true) const; - }; + /** @brief Checks that response contains compressed data */ + bool is_gzipped(bool includingI2PGzip = true) const; + }; - /** - * @brief returns HTTP status string by integer code - * @param code HTTP code [100, 599] - * @return Immutable string with status - */ - std::string_view HTTPCodeToStatus(int code); + /** + * @brief returns HTTP status string by integer code + * @param code HTTP code [100, 599] + * @return Immutable string with status + */ + const char * HTTPCodeToStatus(int code); - /** - * @brief Replaces %-encoded characters in string with their values - * @param data Source string - * @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); - - /** - * @brief Merge HTTP response content with Transfer-Encoding: chunked - * @param in Input stream - * @param out Output stream - * @return true on success, false otherwise - */ - bool MergeChunkedResponse (std::istream& in, std::ostream& out); - - std::string CreateBasicAuthorizationString (const std::string& user, const std::string& pass); + /** + * @brief Replaces %-encoded characters in string with their values + * @param data Source string + * @param null If set to true - decode also %00 sequence, otherwise - skip + * @return Decoded string + */ + std::string UrlDecode(const std::string& data, bool null = false); + /** + * @brief Merge HTTP response content with Transfer-Encoding: chunked + * @param in Input stream + * @param out Output stream + * @return true on success, false otherwise + */ + bool MergeChunkedResponse (std::istream& in, std::ostream& out); } // http } // i2p diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e97a3596..9f6c609b 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,24 +1,20 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #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 "I2NPProtocol.h" #include "version.h" +using namespace i2p::transport; + namespace i2p { std::shared_ptr NewI2NPMessage () @@ -31,32 +27,26 @@ namespace i2p return std::make_shared >(); } - std::shared_ptr NewI2NPMediumMessage () + std::shared_ptr NewI2NPTunnelMessage () { - return std::make_shared >(); - } - - std::shared_ptr NewI2NPTunnelMessage (bool endpoint) - { - return i2p::tunnel::tunnels.NewI2NPTunnelMessage (endpoint); + auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 + msg->Align (12); + return std::shared_ptr(msg); } 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) + void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID) { SetTypeID (msgType); if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4); SetMsgID (replyMsgID); SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); UpdateSize (); - if (checksum) UpdateChks (); + UpdateChks (); } void I2NPMessage::RenewI2NPMessageHeader () @@ -67,22 +57,18 @@ 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) { auto msg = NewI2NPMessage (len); if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: Message length ", len, " exceeds max length ", msg->maxLen); + LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); return msg; } @@ -97,7 +83,7 @@ namespace i2p msg->from = from; } else - LogPrint (eLogError, "I2NP: Message length ", len, " exceeds max length"); + LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); return msg; } @@ -110,17 +96,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 +117,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 +140,7 @@ namespace i2p if (excludedPeers) { + int cnt = excludedPeers->size (); htobe16buf (buf, cnt); buf += 2; for (auto& it: *excludedPeers) @@ -187,29 +162,22 @@ namespace i2p } std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::unordered_set& excludedFloodfills, - std::shared_ptr replyTunnel, const uint8_t * replyKey, - const uint8_t * replyTag, bool replyECIES) + const std::set& excludedFloodfills, + std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { int cnt = excludedFloodfills.size (); - auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); + auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, dest, 32); // key buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags - *buf |= (replyECIES ? DATABASE_LOOKUP_ECIES_FLAG : DATABASE_LOOKUP_ENCRYPTION_FLAG); + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags buf ++; htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf += 4; // excluded - if (cnt > 512) - { - LogPrint (eLogWarning, "I2NP: Too many peers to exclude ", cnt, " for DatabaseLookup"); - cnt = 0; - } htobe16buf (buf, cnt); buf += 2; if (cnt > 0) @@ -222,17 +190,9 @@ namespace i2p } // encryption memcpy (buf, replyKey, 32); - buf[32] = 1; // 1 tag - if (replyECIES) - { - memcpy (buf + 33, replyTag, 8); // 8 bytes tag - buf += 41; - } - else - { - memcpy (buf + 33, replyTag, 32); // 32 bytes tag - buf += 65; - } + buf[32] = uint8_t( 1 ); // 1 tag + memcpy (buf + 33, replyTag, 32); + buf += 65; m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); @@ -240,7 +200,7 @@ namespace i2p } std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, - std::vector routers) + std::vector routers) { auto m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); @@ -261,18 +221,11 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, - uint32_t replyToken, std::shared_ptr replyTunnel) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) { if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); - if (!router->GetBuffer ()) - { - LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); - return nullptr; - } - auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); @@ -282,33 +235,17 @@ namespace i2p uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE; if (replyToken) { - if (replyTunnel) - { - htobe32buf (buf, replyTunnel->GetNextTunnelID ()); - buf += 4; // reply tunnelID - memcpy (buf, replyTunnel->GetNextIdentHash (), 32); - buf += 32; // reply tunnel gateway - } - else - { - memset (buf, 0, 4); // zero tunnelID means direct reply - buf += 4; - memcpy (buf, context.GetIdentHash (), 32); - buf += 32; - } + memset (buf, 0, 4); // zero tunnelID means direct reply + buf += 4; + memcpy (buf, router->GetIdentHash (), 32); + buf += 32; } uint8_t * sizePtr = buf; buf += 2; m->len += (buf - payload); // payload size - size_t size = 0; - if (router->GetBufferLen () + (buf - payload) <= 940) // fits one tunnel message - size = i2p::data::GzipNoCompression (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); - else - { - i2p::data::GzipDeflator deflator; - size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); - } + i2p::data::GzipDeflator deflator; + size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); if (size) { htobe16buf (sizePtr, size); // size @@ -321,13 +258,13 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr leaseSet) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); - memcpy (payload + DATABASE_STORE_KEY_OFFSET, storeHash, 32); - payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for LeaseSet + memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); + payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); size_t size = DATABASE_STORE_HEADER_SIZE; memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); @@ -337,7 +274,7 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); @@ -371,9 +308,172 @@ 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 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + g_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } + + 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"); + BN_CTX * ctx = BN_CTX_new (); + i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); + BN_CTX_free (ctx); + // 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 + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0; + } + else + record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30) + + //TODO: fill filler + SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret + record + BUILD_RESPONSE_RECORD_HASH_OFFSET); + // encrypt reply + i2p::crypto::CBCEncryption encryption; + for (int j = 0; j < num; j++) + { + encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + } + return true; + } + } + return false; + } + + void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); + if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + { + LogPrint (eLogError, "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[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) + { + if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } + } + + void HandleTunnelBuildMsg (uint8_t * buf, size_t len) + { + if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE) + { + LogPrint (eLogError, "TunnelBuild message is too short ", len); + return; + } + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) + { + if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel + { + // so we send it to reply tunnel + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPTunnelBuildReply, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } + + void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + { + LogPrint (eLogError, "VaribleTunnelBuildReply 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"); + } + + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { - auto msg = NewI2NPTunnelMessage (false); + auto msg = NewI2NPTunnelMessage (); msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; @@ -381,7 +481,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - auto msg = NewI2NPTunnelMessage (false); + auto msg = NewI2NPTunnelMessage (); htobe32buf (msg->GetPayload (), tunnelID); msg->len += 4; // tunnelID msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); @@ -389,9 +489,9 @@ namespace i2p return msg; } - std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint) + std::shared_ptr CreateEmptyTunnelDataMsg () { - auto msg = NewI2NPTunnelMessage (endpoint); + auto msg = NewI2NPTunnelMessage (); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; return msg; } @@ -404,7 +504,7 @@ namespace i2p htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); msg->len += TUNNEL_GATEWAY_HEADER_SIZE; if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: Tunnel gateway buffer overflow ", msg->maxLen); + LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (eI2NPTunnelGateway); return msg; } @@ -424,11 +524,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, @@ -439,7 +535,7 @@ namespace i2p msg->offset += gatewayMsgOffset; msg->len += gatewayMsgOffset; if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: Tunnel gateway buffer overflow ", msg->maxLen); + LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message len = msg->GetLength (); msg->offset -= gatewayMsgOffset; @@ -454,18 +550,55 @@ namespace i2p { if (len < I2NP_HEADER_SIZE_OFFSET + 2) { - LogPrint (eLogError, "I2NP: Message length ", len, " is smaller than header"); + LogPrint (eLogError, "I2NP: message length ", len, " is smaller than header"); return len; } auto l = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE; if (l > len) { - LogPrint (eLogError, "I2NP: Message length ", l, " exceeds buffer length ", len); + LogPrint (eLogError, "I2NP: message length ", l, " exceeds buffer length ", len); l = len; } 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 eI2NPVariableTunnelBuildReply: + HandleVariableTunnelBuildReplyMsg (msgID, buf, size); + 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 +608,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 +640,15 @@ 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 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 ()); } } } @@ -540,7 +658,7 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (std::shared_ptr&& msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) { if (msg) { @@ -561,8 +679,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 911a53bf..5160acec 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,20 +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 -*/ - #ifndef I2NP_PROTOCOL_H__ #define I2NP_PROTOCOL_H__ #include #include -#include +#include #include -#include -#include #include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" @@ -37,7 +27,7 @@ namespace i2p const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4; // I2NP NTCP2 header - const size_t I2NP_NTCP2_HEADER_SIZE = I2NP_HEADER_EXPIRATION_OFFSET + 4; + const size_t I2NP_NTCP2_HEADER_SIZE = I2NP_HEADER_EXPIRATION_OFFSET + 4; // Tunnel Gateway header const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0; @@ -49,11 +39,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; @@ -62,52 +47,35 @@ namespace i2p // TunnelBuild const size_t TUNNEL_BUILD_RECORD_SIZE = 528; - const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; + + //BuildRequestRecordClearText + const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32; + const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; + const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; + const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; + const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; + const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; + const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222; // BuildRequestRecordEncrypted const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0; const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16; - // ECIES BuildRequestRecordClearText - const size_t ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; - const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; - const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; - const size_t ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; - const size_t ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; - const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; - const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; - const size_t ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; - const size_t ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET = ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; - const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET + 3; - const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; - const size_t ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; - const size_t ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET = ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; - const size_t ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 464; - - // ECIES BuildResponseRecord - const size_t ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET = 0; - const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; - - // ShortRequestRecordClearText - const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; - const size_t SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; - const size_t SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET = SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_FLAG_OFFSET = SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; - const size_t SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET = SHORT_REQUEST_RECORD_FLAG_OFFSET + 1; - const size_t SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE = SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET + 2; - const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; - const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; - - // ShortResponseRecord - const size_t SHORT_RESPONSE_RECORD_OPTIONS_OFFSET = 0; - const size_t SHORT_RESPONSE_RECORD_RET_OFFSET = 201; + // BuildResponseRecord + const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0; + const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32; + const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495; + const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE; enum I2NPMessageType { + eI2NPDummyMsg = 0, eI2NPDatabaseStore = 1, eI2NPDatabaseLookup = 2, eI2NPDatabaseSearchReply = 3, @@ -119,20 +87,14 @@ namespace i2p eI2NPTunnelBuild = 21, eI2NPTunnelBuildReply = 22, eI2NPVariableTunnelBuild = 23, - eI2NPVariableTunnelBuildReply = 24, - eI2NPShortTunnelBuild = 25, - eI2NPShortTunnelBuildReply = 26, - eI2NPTunnelTest = 231 + eI2NPVariableTunnelBuildReply = 24 }; - const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; - const uint8_t TUNNEL_BUILD_RECORD_ENDPOINT_FLAG = 0x40; const int NUM_TUNNEL_BUILD_RECORDS = 8; // DatabaseLookup flags const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; const uint8_t DATABASE_LOOKUP_ENCRYPTION_FLAG = 0x02; - const uint8_t DATABASE_LOOKUP_ECIES_FLAG = 0x10; const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C; const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 @@ -145,16 +107,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_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT) const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds @@ -163,11 +117,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 (); }; @@ -177,9 +129,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 ()); }; @@ -258,7 +208,8 @@ namespace tunnel SetExpiration (bufbe32toh (ntcp2 + I2NP_HEADER_EXPIRATION_OFFSET)*1000LL); SetSize (len - offset - I2NP_HEADER_SIZE); SetChks (0); - } + } + void ToNTCP2 () { uint8_t * ntcp2 = GetNTCP2Header (); @@ -266,12 +217,9 @@ namespace tunnel memcpy (ntcp2 + I2NP_HEADER_TYPEID_OFFSET, GetHeader () + I2NP_HEADER_TYPEID_OFFSET, 5); // typeid + msgid } - void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0, bool checksum = true); + void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0); void RenewI2NPMessageHeader (); bool IsExpired () const; - bool IsExpired (uint64_t ts) const; // in milliseconds - - void Drop () { if (onDrop) { onDrop (); onDrop = nullptr; }; } }; template @@ -283,32 +231,34 @@ namespace tunnel std::shared_ptr NewI2NPMessage (); std::shared_ptr NewI2NPShortMessage (); - std::shared_ptr NewI2NPMediumMessage (); - std::shared_ptr NewI2NPTunnelMessage (bool endpoint); + std::shared_ptr NewI2NPTunnelMessage (); std::shared_ptr NewI2NPMessage (size_t len); std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); 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, - std::shared_ptr replyTunnel, - const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); + const std::set& excludedFloodfills, + std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); - std::shared_ptr CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr leaseSet); // for floodfill only + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet); // for floodfill only std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); + void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); + void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); + void HandleTunnelBuildMsg (uint8_t * buf, size_t len); + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); - std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint); + std::shared_ptr CreateEmptyTunnelDataMsg (); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, @@ -316,6 +266,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 @@ -323,13 +274,16 @@ namespace tunnel public: ~I2NPMessagesHandler (); - void PutNextMessage (std::shared_ptr&& msg); + void PutNextMessage (std::shared_ptr msg); void Flush (); 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); } #endif diff --git a/libi2pd/I2PEndian.cpp b/libi2pd/I2PEndian.cpp index 5496f144..b8a041d8 100644 --- a/libi2pd/I2PEndian.cpp +++ b/libi2pd/I2PEndian.cpp @@ -1,11 +1,3 @@ -/* -* 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 "I2PEndian.h" // http://habrahabr.ru/post/121811/ @@ -50,3 +42,42 @@ uint64_t be64toh(uint64_t big64) return u64.raw_value; } #endif + +/* it can be used in Windows 8 +#include + +uint16_t htobe16(uint16_t int16) +{ + return htons(int16); +} + +uint32_t htobe32(uint32_t int32) +{ + return htonl(int32); +} + +uint64_t htobe64(uint64_t int64) +{ + // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx + //return htonll(int64); + return 0; +} + + +uint16_t be16toh(uint16_t big16) +{ + return ntohs(big16); +} + +uint32_t be32toh(uint32_t big32) +{ + return ntohl(big32); +} + +uint64_t be64toh(uint64_t big64) +{ + // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx + //return ntohll(big64); + return 0; +} +*/ \ No newline at end of file diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index 681a4999..0798f688 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -1,11 +1,3 @@ -/* -* 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 I2PENDIAN_H__ #define I2PENDIAN_H__ #include @@ -13,11 +5,10 @@ #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__) + #include #define htobe16(x) OSSwapHostToBigInt16(x) @@ -35,40 +26,6 @@ #define be64toh(x) OSSwapBigToHostInt64(x) #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) -#define le16toh(x) (x) - -#define htobe32(x) __builtin_bswap32(x) -#define htole32(x) (x) -#define be32toh(x) __builtin_bswap32(x) -#define le32toh(x) (x) - -#define htobe64(x) __builtin_bswap64(x) -#define htole64(x) (x) -#define be64toh(x) __builtin_bswap64(x) -#define le64toh(x) (x) -#endif - #else #define NEEDS_LOCAL_ENDIAN #include @@ -171,20 +128,5 @@ inline void htole64buf(void *buf, uint64_t big64) htobuf64(buf, htole64(big64)); } -inline uint16_t bufle16toh(const void *buf) -{ - return le16toh(buf16toh(buf)); -} - -inline uint32_t bufle32toh(const void *buf) -{ - return le32toh(buf32toh(buf)); -} - -inline uint64_t bufle64toh(const void *buf) -{ - return le64toh(buf64toh(buf)); -} - #endif // I2PENDIAN_H__ diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 865beeb8..f088d3f2 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,16 +1,7 @@ -/* -* 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 "Crypto.h" #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" -#include "CryptoKey.h" #include "Identity.h" namespace i2p @@ -20,49 +11,37 @@ namespace data Identity& Identity::operator=(const Keys& keys) { // copy public and signing keys together - memcpy (publicKey, keys.publicKey, sizeof (publicKey)); - memcpy (signingKey, keys.signingKey, sizeof (signingKey)); + memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); memset (certificate, 0, sizeof (certificate)); return *this; } 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; } IdentityEx::IdentityEx (): - m_ExtendedLen (0) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { } - IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) + IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType): + m_IsVerifierCreated (false) { - 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); - } - 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); // publicKey in awlays assumed 256 regardless actual size, padding must be taken care of if (type != SIGNING_KEY_TYPE_DSA_SHA1) { size_t excessLen = 0; @@ -71,7 +50,7 @@ namespace data { case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { - size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 + size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH); break; @@ -100,8 +79,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 +98,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"); } @@ -139,19 +106,12 @@ namespace data m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY; htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen); // fill extended buffer + m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; htobe16buf (m_ExtendedBuffer, type); htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) - { - auto newBuf = new uint8_t[m_ExtendedLen]; - memcpy (newBuf, m_ExtendedBuffer, 4); - memcpy (newBuf + 4, excessBuf, excessLen); - m_ExtendedBufferPtr = newBuf; - } - else - memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); + memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } // calculate ident hash @@ -163,6 +123,7 @@ namespace data memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); m_IdentHash = m_StandardIdentity.Hash (); m_ExtendedLen = 0; + m_ExtendedBuffer = nullptr; } CreateVerifier (); } @@ -180,27 +141,26 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_ExtendedLen (0) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_ExtendedLen (0) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_ExtendedLen (0) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = standard; } IdentityEx::~IdentityEx () { - if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) - delete[] m_ExtendedBufferPtr; + delete[] m_ExtendedBuffer; } IdentityEx& IdentityEx::operator=(const IdentityEx& other) @@ -208,32 +168,18 @@ namespace data memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE); m_IdentHash = other.m_IdentHash; - size_t oldLen = m_ExtendedLen; + delete[] m_ExtendedBuffer; 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); - } + m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); } + else + m_ExtendedBuffer = nullptr; + m_Verifier = nullptr; - CreateVerifier (); + m_IsVerifierCreated = false; return *this; } @@ -242,10 +188,13 @@ namespace data { m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); + + delete[] m_ExtendedBuffer; + m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; m_Verifier = nullptr; - CreateVerifier (); + m_IsVerifierCreated = false; return *this; } @@ -254,33 +203,21 @@ namespace data { if (len < DEFAULT_IDENTITY_SIZE) { - LogPrint (eLogError, "Identity: Buffer length ", len, " is too small"); + LogPrint (eLogError, "Identity: buffer length ", len, " is too small"); return 0; } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - size_t oldLen = m_ExtendedLen; + if(m_ExtendedBuffer) delete[] m_ExtendedBuffer; + m_ExtendedBuffer = nullptr; + 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); + m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else { @@ -290,11 +227,13 @@ namespace data } } else + { m_ExtendedLen = 0; + m_ExtendedBuffer = nullptr; + } SHA256(buf, GetFullLen (), m_IdentHash); m_Verifier = nullptr; - CreateVerifier (); return GetFullLen (); } @@ -304,33 +243,33 @@ namespace data const size_t fullLen = GetFullLen(); 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); - } + if (m_ExtendedLen > 0 && m_ExtendedBuffer) + 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 +278,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 +292,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 +308,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 +321,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 +345,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: @@ -417,42 +355,59 @@ namespace data } return nullptr; } + + void IdentityEx::CreateVerifier () const + { + if (m_Verifier) return; // don't create again + auto verifier = CreateVerifier (GetSigningKeyType ()); + if (verifier) + { + auto keyLen = verifier->GetPublicKeyLen (); + if (keyLen <= 128) + verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); + 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; + } + } + UpdateVerifier (verifier); + } - void IdentityEx::CreateVerifier () + void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const { if (!m_Verifier) { - auto verifier = CreateVerifier (GetSigningKeyType ()); - if (verifier) + auto created = m_IsVerifierCreated.exchange (true); + if (!created) + m_Verifier.reset (verifier); + else { - auto keyLen = verifier->GetPublicKeyLen (); - if (keyLen <= 128) - verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); -#if OPENSSL_PQ - else if (keyLen > 384) + delete verifier; + int count = 0; + while (!m_Verifier && count < 500) // 5 seconds { - // 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; + std::this_thread::sleep_for (std::chrono::milliseconds(10)); + count++; } + if (!m_Verifier) + LogPrint (eLogError, "Identity: couldn't get verifier in 5 seconds"); } - m_Verifier.reset (verifier); } + else + delete verifier; + } + + void IdentityEx::DropVerifier () const + { + // TODO: potential race condition with Verify + m_IsVerifierCreated = false; + m_Verifier = nullptr; } std::shared_ptr IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key) @@ -462,20 +417,18 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: 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); }; return nullptr; - } + } std::shared_ptr IdentityEx::CreateEncryptor (const uint8_t * key) const { @@ -483,21 +436,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 (); - if (keyLen > 128) 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,15 +456,15 @@ 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; } - size_t PrivateKeys::GetFullLen () const - { - size_t ret = m_Public->GetFullLen () + GetPrivateKeyLen () + m_Public->GetSigningPrivateKeyLen (); + size_t PrivateKeys::GetFullLen () const + { + size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); if (IsOfflineSignature ()) ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; return ret; @@ -531,20 +474,18 @@ namespace data { m_Public = std::make_shared(); size_t ret = m_Public->FromBuffer (buf, len); - auto cryptoKeyLen = GetPrivateKeyLen (); - if (!ret || ret + cryptoKeyLen > len) return 0; // overflow - memcpy (m_PrivateKey, buf + ret, cryptoKeyLen); - ret += cryptoKeyLen; + if (!ret || ret + 256 > len) return 0; // overflow + memcpy (m_PrivateKey, buf + ret, 256); // private key always 256 + ret += 256; 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 bool allzeros = true; for (size_t i = 0; i < signingPrivateKeySize; i++) - if (m_SigningPrivateKey[i]) + if (m_SigningPrivateKey[i]) { allzeros = false; break; @@ -553,12 +494,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; @@ -566,9 +502,9 @@ namespace data if (keyLen + ret > len) return 0; transientVerifier->SetPublicKey (buf + ret); ret += keyLen; if (m_Public->GetSignatureLen () + ret > len) return 0; - if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret)) + if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret)) { - LogPrint (eLogError, "Identity: Offline signature verification failed"); + LogPrint (eLogError, "Identity: offline signature verification failed"); return 0; } ret += m_Public->GetSignatureLen (); @@ -577,11 +513,10 @@ namespace data size_t offlineInfoLen = buf + ret - offlineInfo; m_OfflineSignature.resize (offlineInfoLen); memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen); - // override signing private key + // 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); } @@ -593,15 +528,14 @@ namespace data size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const { size_t ret = m_Public->ToBuffer (buf, len); - auto cryptoKeyLen = GetPrivateKeyLen (); - memcpy (buf + ret, m_PrivateKey, cryptoKeyLen); - ret += cryptoKeyLen; + memcpy (buf + ret, m_PrivateKey, 256); // private key always 256 + ret += 256; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); if(ret + signingPrivateKeySize > len) return 0; // overflow 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 +546,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 @@ -646,20 +588,20 @@ namespace data else CreateSigner (m_Public->GetSigningKeyType ()); } - + void PrivateKeys::CreateSigner (SigningKeyType keyType) const { - if (m_Signer) return; + 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); - } + } } i2p::crypto::Signer * PrivateKeys::CreateSigner (SigningKeyType keyType, const uint8_t * priv) @@ -690,13 +632,8 @@ namespace data return new i2p::crypto::GOSTR3410_512_Signer (i2p::crypto::eGOSTR3410TC26A512, priv); break; 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); + return new i2p::crypto::RedDSA25519Signer (priv); break; -#endif default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } @@ -708,11 +645,6 @@ namespace data return IsOfflineSignature () ? m_TransientSignatureLen : m_Public->GetSignatureLen (); } - size_t PrivateKeys::GetPrivateKeyLen () const - { - return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); - } - uint8_t * PrivateKeys::GetPadding() { if(m_Public->GetSigningKeyType () == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) @@ -735,39 +667,32 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: 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,8 +717,7 @@ namespace data case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA512_4096: LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA"); - [[fallthrough]]; - // no break here + // no break here case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); break; @@ -805,12 +729,7 @@ namespace data break; 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,13 +744,11 @@ 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_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); + case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: + i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub); break; default: LogPrint (eLogError, "Identity: Crypto key type ", (int)type, " is not supported"); @@ -841,18 +758,17 @@ namespace data PrivateKeys PrivateKeys::CreateOfflineKeys (SigningKeyType type, uint32_t expires) const { PrivateKeys keys (*this); - std::unique_ptr verifier (IdentityEx::CreateVerifier (type)); + std::unique_ptr verifier (IdentityEx::CreateVerifier (type)); if (verifier) { size_t pubKeyLen = verifier->GetPublicKeyLen (); 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 - Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature + 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; keys.CreateSigner (type); @@ -870,14 +786,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 +799,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]; +#ifdef __AVX__ + 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..72fd14c5 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,30 +1,18 @@ -/* -* 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 IDENTITY_H__ #define IDENTITY_H__ #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 +47,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 (); @@ -69,11 +55,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,16 +66,14 @@ 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; - const size_t MAX_EXTENDED_BUFFER_SIZE = 8; // cryptoKeyType + signingKeyType + 4 extra bytes of P521 class IdentityEx { public: @@ -108,7 +90,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,32 +107,29 @@ 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(); } + bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } void RecalculateIdentHash(uint8_t * buff=nullptr); static i2p::crypto::Verifier * CreateVerifier (SigningKeyType keyType); - static std::shared_ptr CreateEncryptor (CryptoKeyType keyType, const uint8_t * key); + static std::shared_ptr CreateEncryptor (CryptoKeyType keyType, const uint8_t * key); 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 std::unique_ptr m_Verifier; + mutable std::atomic_bool m_IsVerifierCreated; // make sure we don't create twice size_t m_ExtendedLen; - union - { - uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; - uint8_t * m_ExtendedBufferPtr; - }; + uint8_t * m_ExtendedBuffer; }; - size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer - class PrivateKeys // for eepsites { public: @@ -164,10 +143,10 @@ 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(); + uint8_t * GetPadding(); void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const; @@ -175,32 +154,31 @@ 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 void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); + 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); // offline keys - PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; + PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; const std::vector& GetOfflineSignature () const { return m_OfflineSignature; }; private: void CreateSigner () const; void CreateSigner (SigningKeyType keyType) const; - size_t GetPrivateKeyLen () const; private: 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,10 +199,10 @@ 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 + // destination for delivery instuctions class RoutingDestination { public: @@ -232,12 +210,11 @@ namespace data RoutingDestination () {}; virtual ~RoutingDestination () {}; - virtual std::shared_ptr GetIdentity () const = 0; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) const = 0; // encrypt data for + virtual std::shared_ptr GetIdentity () const = 0; + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const = 0; // encrypt data for virtual bool IsDestination () const = 0; // for garlic const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; - virtual CryptoKeyType GetEncryptionType () const { return GetIdentity ()->GetCryptoKeyType (); }; // override in LeaseSet2 }; class LocalDestination @@ -245,14 +222,13 @@ namespace data public: virtual ~LocalDestination() {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; - virtual bool SupportsEncryptionType (CryptoKeyType keyType) const { return GetIdentity ()->GetCryptoKeyType () == keyType; }; // override for LeaseSet - virtual const uint8_t * GetEncryptionPublicKey (CryptoKeyType keyType) const { return GetIdentity ()->GetEncryptionPublicKey (); }; // override for LeaseSet }; } } + #endif 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 fc0e722d..8b9edb79 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,28 +1,23 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include +#include +#include +#include // for crc32 #include "I2PEndian.h" #include "Crypto.h" +#include "Ed25519.h" #include "Log.h" -#include "Tag.h" #include "Timestamp.h" #include "NetDb.hpp" #include "Tunnel.h" -#include "CryptoKey.h" #include "LeaseSet.h" namespace i2p { namespace data { + LeaseSet::LeaseSet (bool storeLeases): - m_IsValid (false), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr), + m_IsValid (false), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr), m_Buffer (nullptr), m_BufferLen (0) { } @@ -38,7 +33,14 @@ namespace data 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 +53,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; } @@ -63,32 +65,21 @@ namespace data { if (!m_EncryptionKey) m_EncryptionKey = new uint8_t[256]; memcpy (m_EncryptionKey, m_Buffer + size, 256); - } + } size += 256; // encryption key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key - if (size + 1 > m_BufferLen) - { - LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); - m_IsValid = false; - return; - } uint8_t num = m_Buffer[size]; size++; // num - LogPrint (eLogDebug, "LeaseSet: Read num=", (int)num); + LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); if (!num || num > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: Incorrect 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: incorrect number of leases", (int)num); m_IsValid = false; return; } UpdateLeasesBegin (); + // process leases m_ExpirationTime = 0; auto ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -106,27 +97,19 @@ namespace data } if (!m_ExpirationTime) { - LogPrint (eLogWarning, "LeaseSet: All leases are expired. Dropped"); + LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped"); m_IsValid = false; return; } m_ExpirationTime += LEASE_ENDDATE_THRESHOLD; + UpdateLeasesEnd (); // verify - if (verifySignature) + if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { - auto signedSize = leases - m_Buffer; - if (signedSize + m_Identity->GetSignatureLen () > m_BufferLen) - { - LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", int(m_BufferLen)); - m_IsValid = false; - } - else if (!m_Identity->Verify (m_Buffer, signedSize, leases)) - { - LogPrint (eLogWarning, "LeaseSet: Verification failed"); - m_IsValid = false; - } + LogPrint (eLogWarning, "LeaseSet: verification failed"); + m_IsValid = false; } } @@ -166,16 +149,23 @@ 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; + // check if lease's gateway is in our netDb + if (!netdb.FindRouter (lease.tunnelGateway)) + { + // if not found request it + LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); + netdb.RequestDestination (lease.tunnelGateway); + } } } else - LogPrint (eLogWarning, "LeaseSet: Lease is expired already"); + LogPrint (eLogWarning, "LeaseSet: Lease is expired already "); } - uint64_t LeaseSet::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const + uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const { if (!m_Identity) return 0; size_t size = m_Identity->GetFullLen (); @@ -200,7 +190,7 @@ namespace data bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const { - return ExtractExpirationTimestamp (buf, len) > ExtractExpirationTimestamp (m_Buffer, m_BufferLen); + return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); } bool LeaseSet::ExpiresSoon(const uint64_t dlt, const uint64_t fudge) const @@ -211,10 +201,10 @@ namespace data return m_ExpirationTime - now <= dlt; } - const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const - { - return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); - } + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const + { + return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); + } const std::vector > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const { @@ -248,83 +238,198 @@ namespace data return ts > m_ExpirationTime; } - void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted) const + void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { if (!m_EncryptionKey) return; auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); if (encryptor) - encryptor->Encrypt (data, encrypted); + encryptor->Encrypt (data, encrypted, ctx, true); } 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); } - void LeaseSet::SetBufferLen (size_t len) + BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, SigningKeyType blindedKeyType): + m_BlindedSigType (blindedKeyType) { - if (len <= m_BufferLen) m_BufferLen = len; - else - LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen)); + if (!identity) return; + auto len = identity->GetSigningPublicKeyLen (); + m_PublicKey.resize (len); + memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); + m_SigType = identity->GetSigningKeyType (); } - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): - LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) + BlindedPublicKey::BlindedPublicKey (const std::string& b33) { + uint8_t addr[40]; // TODO: define length from b33 + size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); + uint32_t checksum = crc32 (0, addr + 3, l - 3); + // checksum is Little Endian + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + uint8_t flag = addr[0]; + size_t offset = 1; + if (flag & 0x01) // two bytes signatures + { + m_SigType = bufbe16toh (addr + offset); offset += 2; + m_BlindedSigType = bufbe16toh (addr + offset); offset += 2; + } + else // one byte sig + { + m_SigType = addr[offset]; offset++; + m_BlindedSigType = addr[offset]; offset++; + } + std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (m_SigType)); + if (blindedVerifier) + { + auto len = blindedVerifier->GetPublicKeyLen (); + if (offset + len <= l) + { + m_PublicKey.resize (len); + memcpy (m_PublicKey.data (), addr + offset, len); + } + else + LogPrint (eLogError, "LeaseSet2: public key in b33 address is too short for signature type ", (int)m_SigType); + } + else + LogPrint (eLogError, "LeaseSet2: unknown signature type ", (int)m_SigType, " in b33"); + } + + std::string BlindedPublicKey::ToB33 () const + { + if (m_PublicKey.size () > 32) return ""; // assume 25519 + uint8_t addr[35]; char str[60]; // TODO: define actual length + addr[0] = 0; // flags + addr[1] = m_SigType; // sig type + addr[2] = m_BlindedSigType; // blinded sig type + memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ()); + uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); + // checksum is Little Endian + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); + return std::string (str, str + l); + } + + void BlindedPublicKey::GetCredential (uint8_t * credential) const + { + // A = destination's signing public key + // stA = signature type of A, 2 bytes big endian + uint16_t stA = htobe16 (GetSigType ()); + // stA1 = signature type of blinded A, 2 bytes big endian + uint16_t stA1 = htobe16 (GetBlindedSigType ()); + // credential = H("credential", A || stA || stA1) + H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential); + } + + void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const + { + uint8_t credential[32]; + GetCredential (credential); + // subcredential = H("subcredential", credential || blindedPublicKey) + H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential); + } + + void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const + { + uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ()); + uint8_t salt[32]; + //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) + H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt); + i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed); + } + + void BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const + { + uint8_t seed[64]; + GenerateAlpha (date, seed); + i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); + } + + void BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const + { + uint8_t seed[64]; + GenerateAlpha (date, seed); + i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); + } + + void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, p.c_str (), p.length ()); + for (const auto& it: bufs) + SHA256_Update (&ctx, it.first, it.second); + SHA256_Final (hash, &ctx); + } + + i2p::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const + { + i2p::data::IdentHash hash; + if (m_BlindedSigType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 || + m_BlindedSigType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) + { + uint8_t blinded[32]; + if (date) + GetBlindedKey (date, blinded); + else + { + char currentDate[9]; + i2p::util::GetCurrentDate (currentDate); + GetBlindedKey (currentDate, blinded); + } + auto stA1 = htobe16 (m_BlindedSigType); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, (const uint8_t *)&stA1, 2); + SHA256_Update (&ctx, blinded, 32); + SHA256_Final ((uint8_t *)hash, &ctx); + } + else + LogPrint (eLogError, "LeaseSet2: blinded key type ", (int)m_BlindedSigType, " is not supported"); + return hash; + } + + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases): + LeaseSet (storeLeases), m_StoreType (storeType), m_OrigStoreType (storeType) + { SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBufferEncrypted (buf, len, nullptr, nullptr); + ReadFromBufferEncrypted (buf, len, nullptr); else ReadFromBuffer (buf, len); } - LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, - const uint8_t * secret, CryptoKeyType preferredCrypto): - LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto) + LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key): + LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_OrigStoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { - ReadFromBufferEncrypted (buf, len, key, secret); + ReadFromBufferEncrypted (buf, len, key); } 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, false, verifySignature); + ReadFromBuffer (buf, len, false, verifySignature); // TODO: implement encrypted } - - bool LeaseSet2::IsNewer (const uint8_t * buf, size_t len) const - { - uint64_t expiration; - return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp; - } - + void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature) { // standard LS2 header std::shared_ptr identity; - if (readIdentity || !GetIdentity ()) - { - identity = netdb.NewIdentity (buf, len); + if (readIdentity) + { + 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 @@ -334,17 +439,11 @@ namespace data // transient key m_TransientVerifier = ProcessOfflineSignature (identity, buf, len, offset); if (!m_TransientVerifier) - { - LogPrint (eLogError, "LeaseSet2: Offline signature failed"); + { + LogPrint (eLogError, "LeaseSet2: offline signature failed"); return; } } - if (flags & LEASESET2_FLAG_UNPUBLISHED_LEASESET) m_IsPublic = false; - if (flags & LEASESET2_FLAG_PUBLISHED_ENCRYPTED) - { - m_IsPublishedEncrypted = true; - m_IsPublic = true; - } // type specific part size_t s = 0; switch (m_StoreType) @@ -361,20 +460,12 @@ namespace data if (!s) return; offset += s; if (verifySignature || m_TransientVerifier) - { + { // verify signature bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) : - VerifySignature (identity, buf, len, offset); - SetIsValid (verified); + 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); } template @@ -382,13 +473,13 @@ namespace data { if (signatureOffset + verifier->GetSignatureLen () > len) return false; // we assume buf inside DatabaseStore message, so buf[-1] is valid memory - // change it for signature verification, and restore back + // change it for signature verification, and restore back uint8_t c = buf[-1]; const_cast(buf)[-1] = m_StoreType; - bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); + bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); const_cast(buf)[-1] = c; if (!verified) - LogPrint (eLogWarning, "LeaseSet2: Verification failed"); + LogPrint (eLogWarning, "LeaseSet2: verification failed"); return verified; } @@ -396,55 +487,35 @@ namespace data { size_t offset = 0; // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; + 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; + uint16_t currentKeyType = 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 - uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; - if (offset + encryptionKeyLen > len) return 0; - if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only + if (offset + 2 >= len) return 0; + uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; + if (offset + encryptionKeyLen >= len) return 0; + if (IsStoreLeases ()) // 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) - { - 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, higher key type has higher priority 4-1-0 + // if two keys with of the same type, pick first + auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); + if (encryptor && (!m_Encryptor || keyType > currentKeyType)) + { + m_Encryptor = encryptor; // TODO: atomic + currentKeyType = keyType; + } } - offset += encryptionKeyLen; - } + 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 (); @@ -455,41 +526,35 @@ 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) { size_t offset = 0; // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; + uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties - // entries - if (offset + 1 > len) return 0; + // entries + 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 += 1; // cost offset += 4; // expires } // revocations - if (offset + 1 > len) return 0; - int numRevocations = buf[offset]; offset++; + if (offset + 1 >= len) return 0; + int numRevocations = buf[offset]; offset++; for (int i = 0; i < numRevocations; i++) { if (offset + 32 > len) return 0; @@ -498,7 +563,7 @@ namespace data return offset; } - void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret) + void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key) { size_t offset = 0; // blinded key @@ -507,7 +572,7 @@ namespace data uint16_t blindedKeyType = bufbe16toh (stA1); offset += 2; std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (blindedKeyType)); if (!blindedVerifier) return; - auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); + auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); if (offset + blindedKeyLen >= len) return; const uint8_t * blindedPublicKey = buf + offset; blindedVerifier->SetPublicKey (blindedPublicKey); offset += blindedKeyLen; @@ -522,43 +587,38 @@ namespace data { // transient key m_TransientVerifier = ProcessOfflineSignature (blindedVerifier, buf, len, offset); - if (!m_TransientVerifier) + if (!m_TransientVerifier) { - LogPrint (eLogError, "LeaseSet2: Offline signature failed"); + LogPrint (eLogError, "LeaseSet2: offline signature failed"); return; } } // outer ciphertext if (offset + 2 > len) return; uint16_t lenOuterCiphertext = bufbe16toh (buf + offset); offset += 2; - const uint8_t * outerCiphertext = buf + offset; - offset += lenOuterCiphertext; + const uint8_t * outerCiphertext = buf + offset; + offset += lenOuterCiphertext; // verify signature bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) : - VerifySignature (blindedVerifier, buf, len, offset); + VerifySignature (blindedVerifier, buf, len, offset); SetIsValid (verified); // handle ciphertext if (verified && key && lenOuterCiphertext >= 32) { - SetIsValid (false); // we must verify it again in Layer 2 - if (blindedKeyType == key->GetBlindedSigType ()) + SetIsValid (false); // we must verify it again in Layer 2 + if (blindedKeyType == i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519) { // verify blinding char date[9]; i2p::util::GetDateString (m_PublishedTimestamp, date); - std::vector blinded (blindedKeyLen); - key->GetBlindedKey (date, blinded.data ()); - if (memcmp (blindedPublicKey, blinded.data (), blindedKeyLen)) + uint8_t blinded[32]; + key->GetBlindedKey (date, blinded); + if (memcmp (blindedPublicKey, blinded, 32)) { - LogPrint (eLogError, "LeaseSet2: Blinded public key doesn't match"); + LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match"); return; - } - } - else - { - LogPrint (eLogError, "LeaseSet2: Unexpected blinded key type ", blindedKeyType, " instead ", key->GetBlindedSigType ()); - return; - } + } + } // outer key // outerInput = subcredential || publishedTimestamp uint8_t subcredential[36]; @@ -575,141 +635,40 @@ namespace data std::vector outerPlainText (lenOuterPlaintext); i2p::crypto::ChaCha20 (outerCiphertext + 32, lenOuterPlaintext, keys, keys + 32, outerPlainText.data ()); // inner key - // innerInput = authCookie || subcredential || publishedTimestamp + // innerInput = authCookie || subcredential || publishedTimestamp, TODO: non-empty authCookie // innerSalt = innerCiphertext[0:32] // keys = HKDF(innerSalt, innerInput, "ELS2_L2K", 44) - uint8_t innerInput[68]; - size_t authDataLen = ExtractClientAuthData (outerPlainText.data (), lenOuterPlaintext, secret, subcredential, innerInput); - if (authDataLen > 0) - { - memcpy (innerInput + 32, subcredential, 36); - i2p::crypto::HKDF (outerPlainText.data () + 1 + authDataLen, innerInput, 68, "ELS2_L2K", keys); - } - else - // no authData presented, innerInput = subcredential || publishedTimestamp - // skip 1 byte flags - i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie + // skip 1 byte flags + i2p::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie // decrypt Layer 2 // innerKey = keys[0:31] // innerIV = keys[32:43] - size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1 - authDataLen; + size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1; std::vector innerPlainText (lenInnerPlaintext); - i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1 + authDataLen, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ()); + i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ()); if (innerPlainText[0] == NETDB_STORE_TYPE_STANDARD_LEASESET2 || innerPlainText[0] == NETDB_STORE_TYPE_META_LEASESET2) { // override store type and buffer - m_StoreType = innerPlainText[0]; + m_StoreType = innerPlainText[0]; SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); // parse and verify Layer 2 ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); } else - LogPrint (eLogError, "LeaseSet2: Unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); - } - else - { - // we set actual length of encrypted buffer - offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : blindedVerifier->GetSignatureLen (); - SetBufferLen (offset); - } + LogPrint (eLogError, "LeaseSet2: unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); + } } - // helper for ExtractClientAuthData - static inline bool GetAuthCookie (const uint8_t * authClients, int numClients, const uint8_t * okm, uint8_t * authCookie) - { - // try to find clientCookie_i for clientID_i = okm[44:51] - for (int i = 0; i < numClients; i++) - { - if (!memcmp (okm + 44, authClients + i*40, 8)) // clientID_i - { - // clientKey_i = okm[0:31] - // clientIV_i = okm[32:43] - i2p::crypto::ChaCha20 (authClients + i*40 + 8, 32, okm, okm + 32, authCookie); // clientCookie_i - return true; - } - } - return false; - } - - size_t LeaseSet2::ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const - { - size_t offset = 0; - uint8_t flag = buf[offset]; offset++; // flag - if (flag & 0x01) // client auth - { - if (!(flag & 0x0E)) // DH, bit 1-3 all zeroes - { - const uint8_t * ephemeralPublicKey = buf + offset; offset += 32; // ephemeralPublicKey - uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients - const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients - if (offset > len) - { - LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in DH auth data"); - return 0; - } - // calculate authCookie - if (secret) - { - i2p::crypto::X25519Keys ck (secret, nullptr); // derive cpk_i from csk_i - uint8_t authInput[100]; - ck.Agree (ephemeralPublicKey, authInput); // sharedSecret is first 32 bytes of authInput - memcpy (authInput + 32, ck.GetPublicKey (), 32); // cpk_i - memcpy (authInput + 64, subcredential, 36); - uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (ephemeralPublicKey, authInput, 100, "ELS2_XCA", okm); - if (!GetAuthCookie (authClients, numClients, okm, authCookie)) - LogPrint (eLogError, "LeaseSet2: Client cookie DH not found"); - } - else - LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: csk_i is not provided"); - } - else if (flag & 0x02) // PSK, bit 1 is set to 1 - { - const uint8_t * authSalt = buf + offset; offset += 32; // authSalt - uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients - const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients - if (offset > len) - { - LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in PSK auth data"); - return 0; - } - // calculate authCookie - if (secret) - { - uint8_t authInput[68]; - memcpy (authInput, secret, 32); - memcpy (authInput + 32, subcredential, 36); - uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); - if (!GetAuthCookie (authClients, numClients, okm, authCookie)) - LogPrint (eLogError, "LeaseSet2: Client cookie PSK not found"); - } - else - LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: psk_i is not provided"); - } - else - LogPrint (eLogError, "LeaseSet2: Unknown client auth type ", (int)flag); - } - return offset - 1; - } - - void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted) const + void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted); + encryptor->Encrypt (data, encrypted, ctx, true); } - uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const + uint64_t LeaseSet2::ExtractTimestamp (const uint8_t * buf, size_t len) const { - uint64_t expiration = 0; - ExtractPublishedTimestamp (buf, len, expiration); - return expiration; - } - - uint64_t LeaseSet2::ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const - { - if (len < 8) return 0; + if (len < 8) return 0; if (m_StoreType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // encrypted LS2 @@ -717,13 +676,12 @@ namespace data uint16_t blindedKeyType = bufbe16toh (buf + offset); offset += 2; std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (blindedKeyType)); if (!blindedVerifier) return 0 ; - auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); + auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); if (offset + blindedKeyLen + 6 >= len) return 0; offset += blindedKeyLen; - uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; - uint16_t expires = bufbe16toh (buf + offset); offset += 2; - expiration = (timestamp + expires)* 1000LL; - return timestamp; + uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; + uint16_t expires = bufbe16toh (buf + offset); offset += 2; + return (timestamp + expires)* 1000LL; } else { @@ -731,10 +689,9 @@ namespace data if (!identity) return 0; size_t offset = identity->GetFullLen (); if (offset + 6 >= len) return 0; - uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; - uint16_t expires = bufbe16toh (buf + offset); offset += 2; - expiration = (timestamp + expires)* 1000LL; - return timestamp; + uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; + uint16_t expires = bufbe16toh (buf + offset); offset += 2; + return (timestamp + expires)* 1000LL; } } @@ -753,42 +710,26 @@ 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 + // we don't sign it yet. must be signed later on } LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * buf, size_t len): @@ -819,7 +760,7 @@ namespace data size_t size = ident.GetFullLen (); if (size > sz) { - LogPrint (eLogError, "LeaseSet: Identity length ", size, " exceeds buffer size ", sz); + LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", sz); return false; } // encryption key @@ -830,7 +771,7 @@ namespace data ++size; if (!numLeases || numLeases > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: Incorrect number of leases", (int)numLeases); + LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)numLeases); return false; } const uint8_t * leases = ptr + size; @@ -847,38 +788,30 @@ namespace data return ident.Verify(ptr, leases - ptr, leases); } - LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const EncryptionKeys& encryptionKeys, const std::vector >& tunnels, - bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted): + LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, + uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey, + std::vector > tunnels): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { auto identity = keys.GetPublic (); - // assume standard LS2 + // assume standard LS2 int num = tunnels.size (); 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*/; 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 (); + 1/*num keys*/ + 2/*key type*/ + 2/*key len*/ + keyLen/*key*/ + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); uint16_t flags = 0; - if (keys.IsOfflineSignature ()) + if (keys.IsOfflineSignature ()) { flags |= LEASESET2_FLAG_OFFLINE_KEYS; - m_BufferLen += keys.GetOfflineSignature ().size (); + m_BufferLen += keys.GetOfflineSignature ().size (); } - if (isPublishedEncrypted) - { - flags |= LEASESET2_FLAG_PUBLISHED_ENCRYPTED; - isPublic = true; - } - if (!isPublic) flags |= LEASESET2_FLAG_UNPUBLISHED_LEASESET; m_Buffer = new uint8_t[m_BufferLen + 1]; - m_Buffer[0] = storeType; + 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 ()) @@ -889,56 +822,29 @@ namespace data offset += offlineSignature.size (); } htobe16buf (m_Buffer + offset, 0); offset += 2; // properties len - // keys - 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 - } + // keys + m_Buffer[offset] = 1; offset++; // 1 key + htobe16buf (m_Buffer + offset, keyType); offset += 2; // key type + htobe16buf (m_Buffer + offset, keyLen); offset += 2; // key len + memcpy (m_Buffer + offset, encryptionPublicKey, keyLen); offset += 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; - htobe16buf (expiresBuf, expires > 0 ? expires : 0); - } - else - { - // no tunnels or withdraw - SetExpirationTime (publishedTimestamp*1000LL); - memset (expiresBuf, 0, 2); // expires immeditely - } + SetExpirationTime (expirationTime*1000LL); + auto expires = expirationTime - timestamp; + htobe16buf (expiresBuf, expires > 0 ? expires : 0); // sign keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type } @@ -952,81 +858,52 @@ namespace data m_Buffer[0] = storeType; } - LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, - int authType, std::shared_ptr > authKeys): + LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType): LocalLeaseSet2 (ls->GetIdentity ()), m_InnerLeaseSet (ls) { - size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1; - uint8_t layer1Flags = 0; - if (authKeys) - { - if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) layer1Flags |= 0x01; // DH, authentication scheme 0, auth bit 1 - else if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_PSK) layer1Flags |= 0x03; // PSK, authentication scheme 1, auth bit 1 - if (layer1Flags) - lenOuterPlaintext += 32 + 2 + authKeys->size ()*40; // auth data len - } - size_t lenOuterCiphertext = lenOuterPlaintext + 32; - + size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1, + lenOuterCiphertext = lenOuterPlaintext + 32; m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/; - m_Buffer = new uint8_t[m_BufferLen + 1]; + m_Buffer = new uint8_t[m_BufferLen + 1]; m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; BlindedPublicKey blindedKey (ls->GetIdentity ()); - auto timestamp = i2p::util::GetSecondsSinceEpoch (); + auto timestamp = i2p::util::GetSecondsSinceEpoch (); char date[9]; i2p::util::GetDateString (timestamp, date); - uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max - size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); - if (!blindedSigner) - { - LogPrint (eLogError, "LeaseSet2: Can't create blinded signer for signature type ", blindedKey.GetSigType ()); - return; - } + uint8_t blindedPriv[32], blindedPub[32]; + blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); + std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKeyType, blindedPriv)); auto offset = 1; - htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type - memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key + htobe16buf (m_Buffer + offset, blindedKeyType); offset += 2; // Blinded Public Key Sig Type + memcpy (m_Buffer + offset, blindedPub, 32); offset += 32; // Blinded Public Key htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds) auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds - auto expirationTime = ls->GetExpirationTime ()/1000LL; + auto expirationTime = ls->GetExpirationTime ()/1000LL; if (expirationTime > nextMidnight) expirationTime = nextMidnight; SetExpirationTime (expirationTime*1000LL); htobe16buf (m_Buffer + offset, expirationTime > timestamp ? expirationTime - timestamp : 0); offset += 2; // expires uint16_t flags = 0; - htobe16buf (m_Buffer + offset, flags); offset += 2; // flags + htobe16buf (m_Buffer + offset, flags); offset += 2; // flags htobe16buf (m_Buffer + offset, lenOuterCiphertext); offset += 2; // lenOuterCiphertext // outerChipherText - // Layer 1 + // Layer 1 uint8_t subcredential[36]; blindedKey.GetSubcredential (blindedPub, 32, subcredential); htobe32buf (subcredential + 32, timestamp); // outerInput = subcredential || publishedTimestamp // keys = HKDF(outerSalt, outerInput, "ELS2_L1K", 44) uint8_t keys1[64]; // 44 bytes actual data - RAND_bytes (m_Buffer + offset, 32); // outerSalt = CSRNG(32) + RAND_bytes (m_Buffer + offset, 32); // outerSalt = CSRNG(32) i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L1K", keys1); offset += 32; // outerSalt - uint8_t * outerPlainText = m_Buffer + offset; - m_Buffer[offset] = layer1Flags; offset++; // layer 1 flags - // auth data - uint8_t innerInput[68]; // authCookie || subcredential || publishedTimestamp - if (layer1Flags) - { - RAND_bytes (innerInput, 32); // authCookie - CreateClientAuthData (subcredential, authType, authKeys, innerInput, m_Buffer + offset); - offset += 32 + 2 + authKeys->size ()*40; // auth clients - } + uint8_t * outerPlainText = m_Buffer + offset; + m_Buffer[offset] = 0; offset++; // flag // Layer 2 // keys = HKDF(outerSalt, outerInput, "ELS2_L2K", 44) uint8_t keys2[64]; // 44 bytes actual data - RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32) - if (layer1Flags) - { - memcpy (innerInput + 32, subcredential, 36); // + subcredential || publishedTimestamp - i2p::crypto::HKDF (m_Buffer + offset, innerInput, 68, "ELS2_L2K", keys2); - } - else - i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2); // no authCookie - offset += 32; // innerSalt - m_Buffer[offset] = ls->GetStoreType (); + RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32) + i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2); + offset += 32; // innerSalt + m_Buffer[offset] = ls->GetStoreType (); memcpy (m_Buffer + offset + 1, ls->GetBuffer (), ls->GetBufferLen ()); i2p::crypto::ChaCha20 (m_Buffer + offset, lenInnerPlaintext, keys2, keys2 + 32, m_Buffer + offset); // encrypt Layer 2 offset += lenInnerPlaintext; @@ -1034,14 +911,14 @@ namespace data // signature blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset); // store hash - m_StoreHash = blindedKey.GetStoreHash (date); + m_StoreHash = blindedKey.GetStoreHash (date); } LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr identity, const uint8_t * buf, size_t len): - LocalLeaseSet2 (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, identity, buf, len) + LocalLeaseSet2 (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, identity, buf, len) { - // fill inner LeaseSet2 - auto blindedKey = std::make_shared(identity); + // fill inner LeaseSet2 + auto blindedKey = std::make_shared(identity); i2p::data::LeaseSet2 ls (buf, len, blindedKey); // inner layer if (ls.IsValid ()) { @@ -1049,46 +926,8 @@ namespace data m_StoreHash = blindedKey->GetStoreHash (); } else - LogPrint (eLogError, "LeaseSet2: Couldn't extract inner layer"); - } - - void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const - { - if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) - { - i2p::crypto::X25519Keys ek; - ek.GenerateKeys (); // esk and epk - memcpy (authData, ek.GetPublicKey (), 32); authData += 32; // epk - htobe16buf (authData, authKeys->size ()); authData += 2; // num clients - uint8_t authInput[100]; // sharedSecret || cpk_i || subcredential || publishedTimestamp - memcpy (authInput + 64, subcredential, 36); - for (auto& it: *authKeys) - { - ek.Agree (it, authInput); // sharedSecret = DH(esk, cpk_i) - memcpy (authInput + 32, it, 32); - uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (ek.GetPublicKey (), authInput, 100, "ELS2_XCA", okm); - memcpy (authData, okm + 44, 8); authData += 8; // clientID_i - i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i - } - } - else // assume PSK - { - uint8_t authSalt[32]; - RAND_bytes (authSalt, 32); - memcpy (authData, authSalt, 32); authData += 32; // authSalt - htobe16buf (authData, authKeys->size ()); authData += 2; // num clients - uint8_t authInput[68]; // authInput = psk_i || subcredential || publishedTimestamp - memcpy (authInput + 32, subcredential, 36); - for (auto& it: *authKeys) - { - memcpy (authInput, it, 32); - uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); - memcpy (authData, okm + 44, 8); authData += 8; // clientID_i - i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i - } - } + LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer"); } + } } diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index f5197eb5..94a19b1b 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,25 +1,14 @@ -/* -* 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 LEASE_SET_H__ #define LEASE_SET_H__ #include #include #include -#include #include #include #include "Identity.h" #include "Timestamp.h" #include "I2PEndian.h" -#include "Blinding.h" -#include "CryptoKey.h" namespace i2p { @@ -58,17 +47,13 @@ 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 + typedef std::function LeaseInspectFunc; + + 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 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 { @@ -77,14 +62,14 @@ namespace data LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); virtual ~LeaseSet () { delete[] m_EncryptionKey; delete[] m_Buffer; }; virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true); - virtual bool IsNewer (const uint8_t * buf, size_t len) const; + bool IsNewer (const uint8_t * buf, size_t len) const; void PopulateLeases (); // from buffer const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; - const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; + const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; @@ -93,18 +78,15 @@ namespace data bool operator== (const LeaseSet& other) const { return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); }; virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; }; + virtual uint8_t GetOrigStoreType () const { return NETDB_STORE_TYPE_LEASESET; }; virtual uint32_t GetPublishedTimestamp () const { return 0; }; // should be set for LeaseSet2 only - virtual std::shared_ptr GetTransientVerifier () const { return nullptr; }; - virtual bool IsPublishedEncrypted () const { return false; }; + virtual std::shared_ptr GetTransientVerifier () const { return nullptr; }; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_Identity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; bool IsDestination () const { return true; }; - // used in webconsole - void ExpireLease () { m_ExpirationTime = i2p::util::GetSecondsSinceEpoch (); }; - protected: void UpdateLeasesBegin (); @@ -114,7 +96,6 @@ namespace data // called from LeaseSet2 LeaseSet (bool storeLeases); void SetBuffer (const uint8_t * buf, size_t len); - void SetBufferLen (size_t len); void SetIdentity (std::shared_ptr identity) { m_Identity = identity; }; void SetExpirationTime (uint64_t t) { m_ExpirationTime = t; }; void SetIsValid (bool isValid) { m_IsValid = isValid; }; @@ -123,7 +104,7 @@ namespace data private: void ReadFromBuffer (bool readIdentity = true, bool verifySignature = true); - virtual uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const; // returns max expiration time + virtual uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // returns max expiration time private: @@ -137,8 +118,8 @@ namespace data }; /** - * validate lease set buffer signature and extract expiration timestamp - * @returns true if the leaseset is well formed and signature is valid + validate lease set buffer signature and extract expiration timestamp + @returns true if the leaseset is well formed and signature is valid */ bool LeaseSetBufferValidate(const uint8_t * ptr, size_t sz, uint64_t & expires); @@ -147,53 +128,73 @@ namespace data const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7; const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001; - const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002; - const uint16_t LEASESET2_FLAG_PUBLISHED_ENCRYPTED = 0x0004; + class BlindedPublicKey // for encrypted LS2 + { + public: + + BlindedPublicKey (std::shared_ptr identity, SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); + BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + std::string ToB33 () const; + + const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; + size_t GetPublicKeyLen () const { return m_PublicKey.size (); }; + SigningKeyType GetSigType () const { return m_SigType; }; + SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; + + void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes + void GetBlindedKey (const char * date, uint8_t * blindedKey) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD" + void BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD" + i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null + + private: + + void GetCredential (uint8_t * credential) const; // 32 bytes + void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" + void H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const; + + private: + + std::vector m_PublicKey; + i2p::data::SigningKeyType m_SigType, m_BlindedSigType; + }; + class LeaseSet2: public LeaseSet { public: - LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update - LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); - LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // store type 5, called from local netdb only + LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true); + LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key); // store type 5, called from local netdb only uint8_t GetStoreType () const { return m_StoreType; }; + uint8_t GetOrigStoreType () const { return m_OrigStoreType; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; - bool IsPublic () const { return m_IsPublic; }; - bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; }; std::shared_ptr GetTransientVerifier () const { return m_TransientVerifier; }; void Update (const uint8_t * buf, size_t len, bool verifySignature); - bool IsNewer (const uint8_t * buf, size_t len) const; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted) const; - CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; private: void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true); - void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret); + void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key); 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; - 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 + uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; private: - uint8_t m_StoreType; - uint32_t m_PublishedTimestamp = 0; // seconds - bool m_IsPublic = true, m_IsPublishedEncrypted = false; + uint8_t m_StoreType, m_OrigStoreType; + uint32_t m_PublishedTimestamp = 0; std::shared_ptr m_TransientVerifier; - CryptoKeyType m_EncryptionType; std::shared_ptr m_Encryptor; // for standardLS2 }; - // also called from Streaming.cpp + // also called from Streaming.cpp template std::shared_ptr ProcessOfflineSignature (const Verifier& verifier, const uint8_t * buf, size_t len, size_t& offset) { @@ -209,7 +210,7 @@ namespace data transientVerifier->SetPublicKey (buf + offset); offset += keyLen; if (offset + verifier->GetSignatureLen () >= len) return nullptr; if (!verifier->Verify (signedData, keyLen + 6, buf + offset)) return nullptr; - offset += verifier->GetSignatureLen (); + offset += verifier->GetSignatureLen (); return transientVerifier; } @@ -252,18 +253,13 @@ namespace data { public: - typedef std::list > EncryptionKeys; - - LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const EncryptionKeys& encryptionKeys, - const std::vector >& tunnels, - bool isPublic, uint64_t publishedTimestamp, - bool isPublishedEncrypted = false); - - LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP - + LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, + uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey, + std::vector > tunnels); + LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP + virtual ~LocalLeaseSet2 () { delete[] m_Buffer; }; - + uint8_t * GetBuffer () const { return m_Buffer + 1; }; size_t GetBufferLen () const { return m_BufferLen; }; @@ -279,28 +275,17 @@ namespace data size_t m_BufferLen; }; - - const int ENCRYPTED_LEASESET_AUTH_TYPE_NONE = 0; - const int ENCRYPTED_LEASESET_AUTH_TYPE_DH = 1; - const int ENCRYPTED_LEASESET_AUTH_TYPE_PSK = 2; - - typedef i2p::data::Tag<32> AuthPublicKey; - class LocalEncryptedLeaseSet2: public LocalLeaseSet2 { public: - LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE, std::shared_ptr > authKeys = nullptr); + LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); LocalEncryptedLeaseSet2 (std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP const IdentHash& GetStoreHash () const { return m_StoreHash; }; std::shared_ptr GetInnerLeaseSet () const { return m_InnerLeaseSet; }; - private: - - void CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const; - private: IdentHash m_StoreHash; diff --git a/libi2pd/LittleBigEndian.h b/libi2pd/LittleBigEndian.h index 8c081187..69f10ee9 100644 --- a/libi2pd/LittleBigEndian.h +++ b/libi2pd/LittleBigEndian.h @@ -1,11 +1,3 @@ -/* -* 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 -*/ - // LittleBigEndian.h fixed for 64-bits added union // @@ -37,35 +29,35 @@ struct BigEndian; template struct LittleEndian { - union - { - unsigned char bytes[sizeof(T)]; - T raw_value; - }; + union + { + unsigned char bytes[sizeof(T)]; + T raw_value; + }; - LittleEndian(T t = T()) - { - operator =(t); - } + LittleEndian(T t = T()) + { + operator =(t); + } - LittleEndian(const LittleEndian & t) - { - raw_value = t.raw_value; - } + LittleEndian(const LittleEndian & t) + { + raw_value = t.raw_value; + } - LittleEndian(const BigEndian & t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[i] = t.bytes[sizeof(T)-1-i]; - } + LittleEndian(const BigEndian & t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[i] = t.bytes[sizeof(T)-1-i]; + } - operator const T() const - { - T t = T(); - for (unsigned i = 0; i < sizeof(T); i++) - t |= T(bytes[i]) << (i << 3); - return t; - } + operator const T() const + { + T t = T(); + for (unsigned i = 0; i < sizeof(T); i++) + t |= T(bytes[i]) << (i << 3); + return t; + } const T operator = (const T t) { @@ -74,68 +66,68 @@ struct LittleEndian return t; } - // operators + // operators - const T operator += (const T t) - { - return (*this = *this + t); - } + const T operator += (const T t) + { + return (*this = *this + t); + } - const T operator -= (const T t) - { - return (*this = *this - t); - } + const T operator -= (const T t) + { + return (*this = *this - t); + } - const T operator *= (const T t) - { - return (*this = *this * t); - } + const T operator *= (const T t) + { + return (*this = *this * t); + } - const T operator /= (const T t) - { - return (*this = *this / t); - } + const T operator /= (const T t) + { + return (*this = *this / t); + } - const T operator %= (const T t) - { - return (*this = *this % t); - } + const T operator %= (const T t) + { + return (*this = *this % t); + } - LittleEndian operator ++ (int) - { - LittleEndian tmp(*this); - operator ++ (); - return tmp; - } + LittleEndian operator ++ (int) + { + LittleEndian tmp(*this); + operator ++ (); + return tmp; + } - LittleEndian & operator ++ () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - ++bytes[i]; - if (bytes[i] != 0) - break; - } - return (*this); - } + LittleEndian & operator ++ () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + ++bytes[i]; + if (bytes[i] != 0) + break; + } + return (*this); + } - LittleEndian operator -- (int) - { - LittleEndian tmp(*this); - operator -- (); - return tmp; - } + LittleEndian operator -- (int) + { + LittleEndian tmp(*this); + operator -- (); + return tmp; + } - LittleEndian & operator -- () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - --bytes[i]; - if (bytes[i] != (T)(-1)) - break; - } - return (*this); - } + LittleEndian & operator -- () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + --bytes[i]; + if (bytes[i] != (T)(-1)) + break; + } + return (*this); + } }; #pragma pack(pop) @@ -145,105 +137,105 @@ struct LittleEndian template struct BigEndian { - union - { - unsigned char bytes[sizeof(T)]; - T raw_value; - }; + union + { + unsigned char bytes[sizeof(T)]; + T raw_value; + }; - BigEndian(T t = T()) - { - operator =(t); - } + BigEndian(T t = T()) + { + operator =(t); + } - BigEndian(const BigEndian & t) - { - raw_value = t.raw_value; - } + BigEndian(const BigEndian & t) + { + raw_value = t.raw_value; + } - BigEndian(const LittleEndian & t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[i] = t.bytes[sizeof(T)-1-i]; - } + BigEndian(const LittleEndian & t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[i] = t.bytes[sizeof(T)-1-i]; + } - operator const T() const - { - T t = T(); - for (unsigned i = 0; i < sizeof(T); i++) - t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3); - return t; - } + operator const T() const + { + T t = T(); + for (unsigned i = 0; i < sizeof(T); i++) + t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3); + return t; + } - const T operator = (const T t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[sizeof(T) - 1 - i] = t >> (i << 3); - return t; - } + const T operator = (const T t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[sizeof(T) - 1 - i] = t >> (i << 3); + return t; + } - // operators + // operators - const T operator += (const T t) - { - return (*this = *this + t); - } + const T operator += (const T t) + { + return (*this = *this + t); + } - const T operator -= (const T t) - { - return (*this = *this - t); - } + const T operator -= (const T t) + { + return (*this = *this - t); + } - const T operator *= (const T t) - { - return (*this = *this * t); - } + const T operator *= (const T t) + { + return (*this = *this * t); + } - const T operator /= (const T t) - { - return (*this = *this / t); - } + const T operator /= (const T t) + { + return (*this = *this / t); + } - const T operator %= (const T t) - { - return (*this = *this % t); - } + const T operator %= (const T t) + { + return (*this = *this % t); + } - BigEndian operator ++ (int) - { - BigEndian tmp(*this); - operator ++ (); - return tmp; - } + BigEndian operator ++ (int) + { + BigEndian tmp(*this); + operator ++ (); + return tmp; + } - BigEndian & operator ++ () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - ++bytes[sizeof(T) - 1 - i]; - if (bytes[sizeof(T) - 1 - i] != 0) - break; - } - return (*this); - } + BigEndian & operator ++ () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + ++bytes[sizeof(T) - 1 - i]; + if (bytes[sizeof(T) - 1 - i] != 0) + break; + } + return (*this); + } - BigEndian operator -- (int) - { - BigEndian tmp(*this); - operator -- (); - return tmp; - } + BigEndian operator -- (int) + { + BigEndian tmp(*this); + operator -- (); + return tmp; + } - BigEndian & operator -- () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - --bytes[sizeof(T) - 1 - i]; - if (bytes[sizeof(T) - 1 - i] != (T)(-1)) - break; - } - return (*this); - } + BigEndian & operator -- () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + --bytes[sizeof(T) - 1 - i]; + if (bytes[sizeof(T) - 1 - i] != (T)(-1)) + break; + } + return (*this); + } }; #pragma pack(pop) diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 76e85d4a..79b4a511 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,7 +7,6 @@ */ #include "Log.h" -#include "util.h" //for std::transform #include @@ -18,14 +17,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,29 +31,27 @@ 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 #ifndef _WIN32 /** - * @brief Maps our log levels to syslog one + * @brief Maps our log levels to syslog one * @return syslog priority LOG_*, as defined in syslog.h */ static inline int GetSyslogPrio (enum LogLevel l) { 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; @@ -114,29 +110,28 @@ namespace log { } } - std::string str_tolower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - // static_cast(std::tolower) // wrong - // [](int c){ return std::tolower(c); } // wrong - // [](char c){ return std::tolower(c); } // wrong - [](unsigned char c){ return std::tolower(c); } // correct - ); - return s; - } + std::string str_tolower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + // static_cast(std::tolower) // wrong + // [](int c){ return std::tolower(c); } // wrong + // [](char c){ return std::tolower(c); } // wrong + [](unsigned char c){ return std::tolower(c); } // correct + ); + return s; + } - 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; } + void Log::SetLogLevel (const std::string& level_) { + std::string level=str_tolower(level_); + if (level == "none") { m_MinLevel = eLogNone; } + else if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } else { - LogPrint(eLogCritical, "Log: Unknown loglevel: ", level); + LogPrint(eLogError, "Log: unknown loglevel: ", level); return; } - LogPrint(eLogInfo, "Log: Logging level set to ", level); + LogPrint(eLogInfo, "Log: min messages level set to ", level); } const char * Log::TimeAsString(std::time_t t) { @@ -174,7 +169,7 @@ namespace log { break; case eLogStdout: default: - std::cout << TimeAsString(msg->timestamp) + std::cout << TimeAsString(msg->timestamp) << "@" << short_tid << "/" << LogMsgColors[msg->level] << g_LogLevelStr[msg->level] << LogMsgColors[eNumLogLevels] << " - " << msg->text << std::endl; @@ -184,8 +179,6 @@ namespace log { void Log::Run () { - i2p::util::SetThreadName("Logging"); - Reopen (); while (m_IsRunning) { @@ -206,6 +199,7 @@ namespace log { void Log::SendTo (const std::string& path) { if (m_LogStream) m_LogStream = nullptr; // close previous + if (m_MinLevel == eLogNone) return; auto flags = std::ofstream::out | std::ofstream::app; auto os = std::make_shared (path, flags); if (os->is_open ()) @@ -216,7 +210,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) { @@ -243,11 +237,5 @@ namespace log { Log & Logger() { return logger; } - - static ThrowFunction g_ThrowFunction; - ThrowFunction GetThrowFunction () { return g_ThrowFunction; } - void SetThrowFunction (ThrowFunction f) { g_ThrowFunction = f; } - } // log } // i2p - diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 18592c9e..b11c6763 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -17,7 +17,6 @@ #include #include #include -#include #include "Queue.h" #ifndef _WIN32 @@ -27,7 +26,6 @@ enum LogLevel { eLogNone = 0, - eLogCritical, eLogError, eLogWarning, eLogInfo, @@ -53,7 +51,7 @@ namespace log { { private: - enum LogType m_Destination; + enum LogType m_Destination; enum LogLevel m_MinLevel; std::shared_ptr m_LogStream; std::string m_Logfile; @@ -76,7 +74,7 @@ namespace log { /** * @brief Makes formatted string from unix timestamp - * @param ts Second since epoch + * @param ts Second since epoch * * This function internally caches the result for last provided value */ @@ -87,52 +85,52 @@ 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 (); /** - * @brief Sets minimal allowed level for log messages - * @param level String with wanted minimal msg level + * @brief Sets minimal allowed level for log messages + * @param level String with wanted minimal msg level */ - void SetLogLevel (const std::string& level); + void SetLogLevel (const std::string& level); /** * @brief Sets log destination to logfile - * @param path Path to logfile + * @param path Path to logfile */ void SendTo (const std::string &path); /** * @brief Sets log destination to given output stream - * @param os Output stream + * @param os Output stream */ void SendTo (std::shared_ptr os); /** - * @brief Sets format for timestamps in log - * @param format String with timestamp format + * @brief Sets format for timestamps in log + * @param format String with timestamp format */ void SetTimeFormat (std::string format) { m_TimeFormat = format; }; #ifndef _WIN32 /** * @brief Sets log destination to syslog - * @param name Wanted program name + * @param name Wanted program name * @param facility Wanted log category */ void SendTo (const char *name, int facility); #endif /** - * @brief Format log message and write to output stream/syslog - * @param msg Pointer to processed message + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message */ void Append(std::shared_ptr &); - /** @brief Reopen log file */ + /** @brief Reopen log file */ void Reopen(); }; @@ -145,25 +143,16 @@ namespace log { */ struct LogMsg { std::time_t timestamp; - std::string text; /**< message text as single string */ - LogLevel level; /**< message level */ + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ std::thread::id tid; /**< id of thread that generated message */ - LogMsg (LogLevel lvl, std::time_t ts, std::string&& txt): timestamp(ts), text(std::move(txt)), level(lvl) {} + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; }; Log & Logger(); - - typedef std::function ThrowFunction; - ThrowFunction GetThrowFunction (); - void SetThrowFunction (ThrowFunction f); } // log -} // i2p - -inline bool CheckLogLevel (LogLevel level) noexcept -{ - return level <= i2p::log::Logger().GetLogLevel (); -} +} /** internal usage only -- folding args array to single string */ template @@ -172,6 +161,14 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept s << std::forward(arg); } +/** 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)...); +} + /** * @brief Create log message and send it to queue * @param level Message level (eLogError, eLogInfo, ...) @@ -180,29 +177,18 @@ 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; - (LogPrint (ss, std::forward(args)), ...); - 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); -} - -/** - * @brief Throw fatal error message with the list of arguments - * @param args Array of message parts - */ -template -void ThrowFatal (TArgs&&... args) noexcept -{ - auto f = i2p::log::GetThrowFunction (); - if (!f) return; // fold message to single string std::stringstream ss(""); - (LogPrint (ss, std::forward(args)), ...); - f (ss.str ()); + + LogPrint (ss, std::forward(args)...); + + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + msg->tid = std::this_thread::get_id(); + log.Append(msg); } #endif // LOG_H__ diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index b3a51488..0976720e 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,10 +1,12 @@ /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2018, 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 @@ -19,225 +21,234 @@ #include "RouterContext.h" #include "Transports.h" #include "NetDb.hpp" -#include "HTTP.h" -#include "util.h" -#include "Socks5.h" #include "NTCP2.h" -#if defined(__linux__) && !defined(_NETINET_IN_H) - #include -#endif - namespace i2p { namespace transport { NTCP2Establisher::NTCP2Establisher (): - m_SessionConfirmedBuffer (nullptr) - { + m_SessionRequestBuffer (nullptr), m_SessionCreatedBuffer (nullptr), m_SessionConfirmedBuffer (nullptr) + { } - NTCP2Establisher::~NTCP2Establisher () - { + NTCP2Establisher::~NTCP2Establisher () + { + delete[] m_SessionRequestBuffer; + delete[] m_SessionCreatedBuffer; delete[] m_SessionConfirmedBuffer; } - bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) + void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial) { - i2p::crypto::InitNoiseXKState (*this, rs); + // temp_key = HMAC-SHA256(ck, input_key_material) + uint8_t tempKey[32]; unsigned int len; + HMAC(EVP_sha256(), m_CK, 32, inputKeyMaterial, 32, tempKey, &len); + // ck = HMAC-SHA256(temp_key, byte(0x01)) + static uint8_t one[1] = { 1 }; + HMAC(EVP_sha256(), tempKey, 32, one, 1, m_CK, &len); + // derived = HMAC-SHA256(temp_key, ck || byte(0x02)) + m_CK[32] = 2; + HMAC(EVP_sha256(), tempKey, 32, m_CK, 33, m_K, &len); + } + + void NTCP2Establisher::MixHash (const uint8_t * buf, size_t len) + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (m_H, &ctx); + } + + void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) + { + static const uint8_t protocolNameHash[] = + { + 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, + 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 + }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") + static const uint8_t hh[32] = + { + 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) + memcpy (m_CK, protocolNameHash, 32); + // h = SHA256(hh || rs) + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, rs, 32); + SHA256_Final (m_H, &ctx); // 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 ()); + } + + void NTCP2Establisher::KDF1Bob () + { + KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetStaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); } - bool NTCP2Establisher::KDF1Bob () - { - return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); - } + void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) + { + MixHash (sessionRequest + 32, 32); // encrypted payload - bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) - { - MixHash (sessionRequest + 32, 32); // encrypted payload - - int paddingLength = sessionRequestLen - 64; + int paddingLength = sessionRequestLen - 64; if (paddingLength > 0) MixHash (sessionRequest + 64, paddingLength); - MixHash (epub, 32); + MixHash (epub, 32); // 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.GetStaticKeys ().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 () { - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + m_EphemeralKeys.GenerateKeys (); } - 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 () % (287 - 64); // message length doesn't exceed 287 bytes m_SessionRequestBufferLen = paddingLength + 64; + m_SessionRequestBuffer = new uint8_t[m_SessionRequestBufferLen]; 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); - options[0] = i2p::context.GetNetID (); // network ID - options[1] = 2; // ver + options[1] = 2; // ver htobe16buf (options + 2, paddingLength); // padLen - // m3p2Len - auto riBuffer = i2p::context.CopyRouterInfoBuffer (); - auto bufLen = riBuffer->GetBufferLen (); - m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options - htobe16buf (options + 4, m3p2Len); - // fill m3p2 payload (RouterInfo block) + // m3p2Len + 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) m_SessionConfirmedBuffer = new uint8_t[m3p2Len + 48]; // m3p1 is 48 bytes uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; 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 + m3p2[3] = 0; // flag + memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex // 2 bytes reserved - htobe32buf (options + 8, (i2p::util::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, m_H, 32, m_K, 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 () % (287 - 64); m_SessionCreatedBufferLen = paddingLen + 64; + m_SessionCreatedBuffer = new uint8_t[m_SessionCreatedBufferLen]; 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, m_H, 32, m_K, 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 int paddingLength = m_SessionCreatedBufferLen - 64; if (paddingLength > 0) - MixHash (m_SessionCreatedBuffer + 64, paddingLength); + 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, m_H, 32, m_K, 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); + 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, m_H, 32, m_K, 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) + bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen) { - clockSkew = false; // 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, m_H, 32, m_K, nonce, options, 16, false)) // decrypt { // options - if (options[0] && options[0] != i2p::context.GetNetID ()) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest networkID ", (int)options[0], " mismatch. Expected ", i2p::context.GetNetID ()); - return false; - } - if (options[1] == 2) // ver is always 2 + if (options[1] == 2) // ver is always 2 { paddingLen = bufbe16toh (options + 2); m_SessionRequestBufferLen = paddingLen + 64; @@ -245,16 +256,15 @@ namespace transport if (m3p2Len < 16) { LogPrint (eLogWarning, "NTCP2: SessionRequest m3p2len=", m3p2Len, " is too short"); - return false; - } + return false; + } // check timestamp auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t tsA = bufbe32toh (options + 8); + uint32_t tsA = bufbe32toh (options + 8); if (tsA < ts - NTCP2_CLOCK_SKEW || tsA > ts + NTCP2_CLOCK_SKEW) { LogPrint (eLogWarning, "NTCP2: SessionRequest time difference ", (int)(ts - tsA), " exceeds clock skew"); - clockSkew = true; - // we send SessionCreate to let Alice know our time and then close session + return false; } } else @@ -267,8 +277,8 @@ namespace transport { LogPrint (eLogWarning, "NTCP2: SessionRequest AEAD verification failed "); return false; - } - return true; + } + return true; } bool NTCP2Establisher::ProcessSessionCreatedMessage (uint16_t& paddingLen) @@ -277,22 +287,21 @@ 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, m_H, 32, m_K, nonce, payload, 16, false)) // decrypt { - // options + // options paddingLen = bufbe16toh(payload + 2); // check timestamp auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t tsB = bufbe32toh (payload + 8); + uint32_t tsB = bufbe32toh (payload + 8); if (tsB < ts - NTCP2_CLOCK_SKEW || tsB > ts + NTCP2_CLOCK_SKEW) { LogPrint (eLogWarning, "NTCP2: SessionCreated time difference ", (int)(ts - tsB), " exceeds clock skew"); @@ -300,23 +309,22 @@ namespace transport } } else - { + { LogPrint (eLogWarning, "NTCP2: SessionCreated AEAD verification failed "); return false; - } + } return true; } - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () + bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce) { // update AD MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload int paddingLength = m_SessionCreatedBufferLen - 64; 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, m_H, 32, m_K, nonce, m_RemoteStaticKey, 32, false)) // decrypt S { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); return false; @@ -324,19 +332,18 @@ 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); + MixHash (m_SessionConfirmedBuffer, 48); - if (!KDF3Bob ()) // MixKey, n = 0 + KDF3Bob (); + if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, m_H, 32, m_K, nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed"); - return false; - } - if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16)) - // calculate new h again for KDF data - MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) + // caclulate new h again for KDF data + memcpy (m_SessionConfirmedBuffer + 16, m_H, 32); // h || ciphertext + SHA256 (m_SessionConfirmedBuffer + 16, m3p2Len + 32, m_H); //h = SHA256(h || ciphertext); + } else { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 AEAD verification failed "); @@ -345,36 +352,30 @@ namespace transport return true; } - NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter, - std::shared_ptr addr): - TransportSession (in_RemoteRouter, NTCP2_ESTABLISH_TIMEOUT), - m_Server (server), m_Socket (m_Server.GetService ()), + NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter): + TransportSession (in_RemoteRouter, NTCP2_ESTABLISH_TIMEOUT), + m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_Establisher (new NTCP2Establisher), - m_SendKey (nullptr), m_ReceiveKey (nullptr), + m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #if OPENSSL_SIPHASH m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), -#else - m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), - m_NextReceivedBufferSize (0), m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), - m_IsSending (false), m_IsReceiving (false), m_NextPaddingSize (16) + m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false) { if (in_RemoteRouter) // Alice { m_Establisher->m_RemoteIdentHash = GetRemoteIdentity ()->GetIdentHash (); + auto addr = in_RemoteRouter->GetNTCP2Address (true); // we need a published address if (addr) { - memcpy (m_Establisher->m_RemoteStaticKey, addr->s, 32); - memcpy (m_Establisher->m_IV, addr->i, 16); - m_RemoteEndpoint = boost::asio::ip::tcp::endpoint (addr->host, addr->port); + memcpy (m_Establisher->m_RemoteStaticKey, addr->ntcp2->staticKey, 32); + memcpy (m_Establisher->m_IV, addr->ntcp2->iv, 16); } else - LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); + LogPrint (eLogWarning, "NTCP2: Missing NTCP2 parameters"); } - m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + - m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } NTCP2Session::~NTCP2Session () @@ -382,6 +383,8 @@ namespace transport delete[] m_NextReceivedBuffer; delete[] m_NextSendBuffer; #if OPENSSL_SIPHASH + if (m_SendSipKey) EVP_PKEY_free (m_SendSipKey); + if (m_ReceiveSipKey) EVP_PKEY_free (m_ReceiveSipKey); if (m_SendMDCtx) EVP_MD_CTX_destroy (m_SendMDCtx); if (m_ReceiveMDCtx) EVP_MD_CTX_destroy (m_ReceiveMDCtx); #endif @@ -393,37 +396,14 @@ namespace transport { m_IsTerminated = true; m_IsEstablished = false; - boost::system::error_code ec; - m_Socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - if (ec) - LogPrint (eLogDebug, "NTCP2: Couldn't shutdown socket: ", ec.message ()); 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,88 +411,65 @@ 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 ()); } void NTCP2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); } - void NTCP2Session::CreateNextReceivedBuffer (size_t size) - { - if (m_NextReceivedBuffer) - { - if (size <= m_NextReceivedBufferSize) - return; // buffer is good, do nothing - else - delete[] m_NextReceivedBuffer; - } - m_NextReceivedBuffer = new uint8_t[size]; - m_NextReceivedBufferSize = size; - } - - void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) - { - if (m_NextReceivedBuffer && !m_IsReceiving && - ts > GetLastActivityTimestamp () + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) - { - delete[] m_NextReceivedBuffer; - m_NextReceivedBuffer = nullptr; - m_NextReceivedBufferSize = 0; - } - } void NTCP2Session::KeyDerivationFunctionDataPhase () { - uint8_t k[64]; - i2p::crypto::HKDF (m_Establisher->GetCK (), nullptr, 0, "", k); // k_ab, k_ba = HKDF(ck, zerolen) - memcpy (m_Kab, k, 32); memcpy (m_Kba, k + 32, 32); - uint8_t master[32]; - i2p::crypto::HKDF (m_Establisher->GetCK (), nullptr, 0, "ask", master, 32); // ask_master = HKDF(ck, zerolen, info="ask") + uint8_t tempKey[32]; unsigned int len; + HMAC(EVP_sha256(), m_Establisher->GetCK (), 32, nullptr, 0, tempKey, &len); // temp_key = HMAC-SHA256(ck, zerolen) + static uint8_t one[1] = { 1 }; + HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Kab, &len); // k_ab = HMAC-SHA256(temp_key, byte(0x01)). + m_Kab[32] = 2; + HMAC(EVP_sha256(), tempKey, 32, m_Kab, 33, m_Kba, &len); // k_ba = HMAC-SHA256(temp_key, k_ab || byte(0x02)) + static uint8_t ask[4] = { 'a', 's', 'k', 1 }, master[32]; + HMAC(EVP_sha256(), tempKey, 32, ask, 4, master, &len); // ask_master = HMAC-SHA256(temp_key, "ask" || byte(0x01)) uint8_t h[39]; memcpy (h, m_Establisher->GetH (), 32); memcpy (h + 32, "siphash", 7); - i2p::crypto::HKDF (master, h, 39, "", master, 32); // sip_master = HKDF(ask_master, h || "siphash") - i2p::crypto::HKDF (master, nullptr, 0, "", k); // sipkeys_ab, sipkeys_ba = HKDF(sip_master, zerolen) - memcpy (m_Sipkeysab, k, 32); memcpy (m_Sipkeysba, k + 32, 32); + HMAC(EVP_sha256(), master, 32, h, 39, tempKey, &len); // temp_key = HMAC-SHA256(ask_master, h || "siphash") + HMAC(EVP_sha256(), tempKey, 32, one, 1, master, &len); // sip_master = HMAC-SHA256(temp_key, byte(0x01)) + HMAC(EVP_sha256(), master, 32, nullptr, 0, tempKey, &len); // temp_key = HMAC-SHA256(sip_master, zerolen) + HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Sipkeysab, &len); // sipkeys_ab = HMAC-SHA256(temp_key, byte(0x01)). + m_Sipkeysab[32] = 2; + HMAC(EVP_sha256(), tempKey, 32, m_Sipkeysab, 33, m_Sipkeysba, &len); // sipkeys_ba = HMAC-SHA256(temp_key, sipkeys_ab || byte(0x02)) } 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)); - } + std::bind(&NTCP2Session::HandleSessionRequestSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } void NTCP2Session::HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { (void) bytes_transferred; if (ecode) { - LogPrint (eLogWarning, "NTCP2: Couldn't send SessionRequest message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: couldn't send SessionRequest message: ", ecode.message ()); Terminate (); } else { + m_Establisher->m_SessionCreatedBuffer = new uint8_t[287]; // TODO: determine actual max size // we receive first 64 bytes (32 Y, and 32 ChaCha/Poly frame) first boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -521,6 +478,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,47 +486,31 @@ namespace transport } else { - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this (), bytes_transferred] () + LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred); + uint16_t paddingLen = 0; + if (m_Establisher->ProcessSessionRequestMessage (paddingLen)) + { + if (paddingLen > 0) { - s->ProcessSessionRequest (bytes_transferred);; - }); + if (paddingLen <= 287 - 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 - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -577,27 +519,15 @@ 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; - } - // send message - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch (); + m_Establisher->CreateSessionCreatedMessage (); + // send message 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)); + std::bind(&NTCP2Session::HandleSessionCreatedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void NTCP2Session::HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -609,45 +539,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 <= 287 - 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) @@ -658,27 +574,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)); @@ -686,29 +592,20 @@ namespace transport void NTCP2Session::HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogWarning, "NTCP2: Couldn't send SessionConfirmed message: ", ecode.message ()); - Terminate (); - } - else - { - LogPrint (eLogDebug, "NTCP2: SessionConfirmed sent"); - KeyDerivationFunctionDataPhase (); - // Alice data phase keys - m_SendKey = m_Kab; - m_ReceiveKey = m_Kba; - SetSipKeys (m_Sipkeysab, m_Sipkeysba); - memcpy (m_ReceiveIV.buf, m_Sipkeysba + 16, 8); - memcpy (m_SendIV.buf, m_Sipkeysab + 16, 8); - Established (); - ReceiveLength (); + LogPrint (eLogDebug, "NTCP2: SessionConfirmed sent"); + KeyDerivationFunctionDataPhase (); + // Alice data phase keys + m_SendKey = m_Kab; + m_ReceiveKey = m_Kba; + SetSipKeys (m_Sipkeysab, m_Sipkeysba); + memcpy (m_ReceiveIV.buf, m_Sipkeysba + 16, 8); + memcpy (m_SendIV.buf, m_Sipkeysab + 16, 8); + Established (); + ReceiveLength (); - // TODO: remove - // m_SendQueue.push_back (CreateDeliveryStatusMsg (1)); - // SendQueue (); - } + // TODO: remove + // m_SendQueue.push_back (CreateDeliveryStatusMsg (1)); + // SendQueue (); } void NTCP2Session::HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -716,13 +613,13 @@ namespace transport (void) bytes_transferred; if (ecode) { - LogPrint (eLogWarning, "NTCP2: Couldn't send SessionCreated message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: couldn't send SessionCreated message: ", ecode.message ()); Terminate (); } else { LogPrint (eLogDebug, "NTCP2: SessionCreated sent"); - m_Establisher->m_SessionConfirmedBuffer = new uint8_t[m_Establisher->m3p2Len + 48]; + m_Establisher->m_SessionConfirmedBuffer = new uint8_t[m_Establisher->m3p2Len + 48]; boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionConfirmedBuffer, m_Establisher->m3p2Len + 48), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionConfirmedReceived , shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -730,7 +627,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 ()); @@ -738,166 +634,98 @@ 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.GetNTCP2Address (false); // any NTCP2 address + if (!addr) + { + LogPrint (eLogError, "NTCP2: No NTCP2 address found in SessionConfirmed"); + Terminate (); + return; + } + if (memcmp (addr->ntcp2->staticKey, m_Establisher->m_RemoteStaticKey, 32)) + { + LogPrint (eLogError, "NTCP2: Static key mismatch in SessionConfirmed"); + SendTerminationAndTerminate (eNTCP2IncorrectSParameter); + 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 ()); + m_Server.AddNTCP2Session (shared_from_this ()); + Established (); + ReceiveLength (); + } + 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 - EVP_PKEY * sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); - m_SendMDCtx = EVP_MD_CTX_create (); + m_SendSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); + m_SendMDCtx = EVP_MD_CTX_create (); EVP_PKEY_CTX *ctx = nullptr; - EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, sipKey); - EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); - EVP_PKEY_free (sipKey); + EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, m_SendSipKey); + EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); - sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); - m_ReceiveMDCtx = EVP_MD_CTX_create (); + m_ReceiveSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); + m_ReceiveMDCtx = EVP_MD_CTX_create (); ctx = nullptr; - EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, sipKey); + EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, m_ReceiveSipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); - EVP_PKEY_free (sipKey); #else m_SendSipKey = sendSipKey; m_ReceiveSipKey = receiveSipKey; @@ -907,30 +735,21 @@ 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 (); + m_Establisher->m_SessionRequestBuffer = new uint8_t[287]; // 287 bytes max for now 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)); + std::placeholders::_1, std::placeholders::_2)); } void NTCP2Session::ReceiveLength () { if (IsTerminated ()) return; -#ifdef __linux__ - const int one = 1; - setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); -#endif boost::asio::async_read (m_Socket, boost::asio::buffer(&m_NextReceivedLen, 2), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceivedLength, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -940,57 +759,48 @@ namespace transport if (ecode) { if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogWarning, "NTCP2: Receive length read error: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: receive length read error: ", ecode.message ()); Terminate (); } else { #if OPENSSL_SIPHASH EVP_DigestSignInit (m_ReceiveMDCtx, nullptr, nullptr, nullptr, nullptr); - EVP_DigestSignUpdate (m_ReceiveMDCtx, m_ReceiveIV.buf, 8); - size_t l = 8; - EVP_DigestSignFinal (m_ReceiveMDCtx, m_ReceiveIV.buf, &l); + EVP_DigestSignUpdate (m_ReceiveMDCtx, m_ReceiveIV.buf, 8); + size_t l = 8; + EVP_DigestSignFinal (m_ReceiveMDCtx, m_ReceiveIV.buf, &l); #else i2p::crypto::Siphash<8> (m_ReceiveIV.buf, m_ReceiveIV.buf, 8, m_ReceiveSipKey); #endif // m_NextReceivedLen comes from the network in BigEndian m_NextReceivedLen = be16toh (m_NextReceivedLen) ^ le16toh (m_ReceiveIV.key); - LogPrint (eLogDebug, "NTCP2: Received length ", m_NextReceivedLen); + LogPrint (eLogDebug, "NTCP2: received length ", m_NextReceivedLen); if (m_NextReceivedLen >= 16) - { - CreateNextReceivedBuffer (m_NextReceivedLen); + { + if (m_NextReceivedBuffer) delete[] m_NextReceivedBuffer; + m_NextReceivedBuffer = new uint8_t[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 { - LogPrint (eLogError, "NTCP2: Received length ", m_NextReceivedLen, " is too short"); + LogPrint (eLogError, "NTCP2: received length ", m_NextReceivedLen, " is too short"); Terminate (); - } + } } } void NTCP2Session::Receive () { if (IsTerminated ()) return; -#ifdef __linux__ - const int one = 1; - setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); -#endif - m_IsReceiving = true; boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -999,28 +809,29 @@ 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)) - { - LogPrint (eLogDebug, "NTCP2: Received message decrypted"); + 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); - m_IsReceiving = false; + delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; // we don't need received buffer anymore ReceiveLength (); } else { LogPrint (eLogWarning, "NTCP2: Received AEAD verification failed "); SendTerminationAndTerminate (eNTCP2DataPhaseAEADFailure); - } + } } } @@ -1034,7 +845,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 +853,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; - } + LogPrint (eLogDebug, "NTCP2: datetime"); + break; case eNTCP2BlkOptions: - LogPrint (eLogDebug, "NTCP2: Options"); + 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: @@ -1085,29 +872,25 @@ namespace transport LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); break; } - auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); + auto nextMsg = NewI2NPMessage (size); + nextMsg->Align (12); // for possible tunnel msg nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header - if (nextMsg->len <= nextMsg->maxLen) - { - memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); - nextMsg->FromNTCP2 (); - m_Handler.PutNextMessage (std::move (nextMsg)); - } - else - LogPrint (eLogError, "NTCP2: I2NP block is too long for I2NP message"); + memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); + nextMsg->FromNTCP2 (); + m_Handler.PutNextMessage (nextMsg); break; } case eNTCP2BlkTermination: - if (size >= 9) + if (size >= 9) { - LogPrint (eLogDebug, "NTCP2: Termination. reason=", (int)(frame[offset + 8])); + LogPrint (eLogDebug, "NTCP2: termination. reason=", (int)(frame[offset + 8])); Terminate (); } else LogPrint (eLogWarning, "NTCP2: Unexpected termination block size ", size); break; case eNTCP2BlkPadding: - LogPrint (eLogDebug, "NTCP2: Padding"); + LogPrint (eLogDebug, "NTCP2: padding"); break; default: LogPrint (eLogWarning, "NTCP2: Unknown block type ", (int)blk); @@ -1119,48 +902,48 @@ namespace transport void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) { -#if OPENSSL_SIPHASH + #if OPENSSL_SIPHASH EVP_DigestSignInit (m_SendMDCtx, nullptr, nullptr, nullptr, nullptr); - EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); - size_t l = 8; - EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); + EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); + size_t l = 8; + EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); #else i2p::crypto::Siphash<8> (m_SendIV.buf, m_SendIV.buf, 8, m_SendSipKey); #endif // length must be in BigEndian htobe16buf (lengthBuf, frameLen ^ le16toh (m_SendIV.key)); - LogPrint (eLogDebug, "NTCP2: Sent length ", frameLen); - } + LogPrint (eLogDebug, "NTCP2: sent length ", frameLen); + } void NTCP2Session::SendI2NPMsgs (std::vector >& msgs) { if (msgs.empty () || IsTerminated ()) return; - + size_t totalLen = 0; std::vector > encryptBufs; - std::vector bufs; + std::vector bufs; std::shared_ptr first; uint8_t * macBuf = nullptr; - for (auto& it: msgs) - { + for (auto& it: msgs) + { it->ToNTCP2 (); auto buf = it->GetNTCP2Header (); auto len = it->GetNTCP2Length (); // block header - buf -= 3; + buf -= 3; buf[0] = eNTCP2BlkI2NPMessage; // blk htobe16buf (buf + 1, len); // size - len += 3; - totalLen += len; + len += 3; + totalLen += len; encryptBufs.push_back ( {buf, len} ); if (&it == &msgs.front ()) // first message { // allocate two bytes for length buf -= 2; len += 2; first = it; - } + } if (&it == &msgs.back () && it->len + 16 < it->maxLen) // last message - { + { // if it's long enough we add padding and MAC to it // create padding block auto paddingLen = CreatePaddingBlock (totalLen, buf + len, it->maxLen - it->len - 16); @@ -1173,8 +956,8 @@ namespace transport macBuf = buf + len; // allocate 16 bytes for MAC len += 16; - } - + } + bufs.push_back (boost::asio::buffer (buf, len)); } @@ -1182,57 +965,46 @@ namespace transport { // allocate send buffer m_NextSendBuffer = new uint8_t[287]; // can be any size > 16, we just allocate 287 frequently - // create padding block + // crate padding block auto paddingLen = CreatePaddingBlock (totalLen, m_NextSendBuffer, 287 - 16); // and padding block to encrypt and send if (paddingLen) - encryptBufs.push_back ( {m_NextSendBuffer, paddingLen} ); + encryptBufs.push_back ( {m_NextSendBuffer, paddingLen} ); bufs.push_back (boost::asio::buffer (m_NextSendBuffer, paddingLen + 16)); macBuf = m_NextSendBuffer + paddingLen; - totalLen += 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 - m_IsSending = true; + m_IsSending = true; boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleI2NPMsgsSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); - } + } void NTCP2Session::HandleI2NPMsgsSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs) { HandleNextFrameSent (ecode, bytes_transferred); // msgs get destroyed here } - + void NTCP2Session::EncryptAndSendNextBuffer (size_t payloadLen) { - if (IsTerminated ()) + if (IsTerminated ()) { delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; - return; + 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; + m_IsSending = true; boost::asio::async_write (m_Socket, boost::asio::buffer (m_NextSendBuffer, payloadLen + 16 + 2), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleNextFrameSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -1241,130 +1013,74 @@ namespace transport { m_IsSending = false; delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; - + if (ecode) { - if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogWarning, "NTCP2: Couldn't send frame ", ecode.message ()); - Terminate (); - } + LogPrint (eLogWarning, "NTCP2: Couldn't send frame ", ecode.message ()); + } 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) - { - m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + - m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; - SendRouterInfo (); - } - else - { - SendQueue (); - SetSendQueueSize (m_SendQueue.size ()); - } - } + SendQueue (); + } } - + 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 (); + 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 break; } SendI2NPMsgs (msgs); - } + } } - 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; + 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) - { - if (m_NextPaddingSize >= 16) - { - RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); - m_NextPaddingSize = 0; - } - paddingSize = m_PaddingSizes[m_NextPaddingSize++] % (paddingSize + 1); - } + if (paddingSize) paddingSize = rand () % paddingSize; buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size - memset (buf + 3, 0, paddingSize); + memset (buf + 3, 0, paddingSize); return paddingSize + 3; - } - + } + 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; @@ -1374,18 +1090,12 @@ namespace transport void NTCP2Session::SendTermination (NTCP2TerminationReason reason) { - if (!m_SendKey || -#if OPENSSL_SIPHASH - !m_SendMDCtx -#else - !m_SendSipKey -#endif - ) return; - m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block + if (!m_SendKey || !m_SendSipKey) return; + m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block // termination block m_NextSendBuffer[2] = eNTCP2BlkTermination; m_NextSendBuffer[3] = 0; m_NextSendBuffer[4] = 9; // 9 bytes block size - htobe64buf (m_NextSendBuffer + 5, m_ReceiveSequenceNumber); + htobe64buf (m_NextSendBuffer + 5, m_ReceiveSequenceNumber); m_NextSendBuffer[13] = (uint8_t)reason; // padding block auto paddingSize = CreatePaddingBlock (12, m_NextSendBuffer + 14, 19); @@ -1396,73 +1106,37 @@ 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::SendI2NPMessages (std::list >& msgs) + void NTCP2Session::SendI2NPMessages (const std::vector >& 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) - SendQueue (); + for (auto it: msgs) + m_SendQueue.push_back (it); + if (!m_IsSending) + SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { - LogPrint (eLogWarning, "NTCP2: Outgoing messages queue size to ", - GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); + LogPrint (eLogWarning, "NTCP2: outgoing messages queue size 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_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + m_TerminationTimer (m_Service) { } @@ -1473,90 +1147,48 @@ namespace transport void NTCP2Server::Start () { - m_EstablisherService.Start (); - if (!IsRunning ()) + if (!m_IsRunning) { - StartIOService (); - if(UsingProxy()) - { - LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers"); - // TODO: resolve proxy until it is resolved - boost::system::error_code e; - auto itr = m_Resolver.resolve(m_ProxyAddress, std::to_string(m_ProxyPort), e); - if(e) - LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message()); - else - { - m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ())); - if (m_ProxyEndpoint) - LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint); - } - } - else - LogPrint(eLogInfo, "NTCP2: Proxy is not used"); - // start acceptors - auto addresses = context.GetRouterInfo ().GetAddresses (); - if (!addresses) return; - for (const auto& address: *addresses) + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&NTCP2Server::Run, this)); + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) { if (!address) continue; - if (address->IsPublishedNTCP2 () && address->port) + if (address->IsPublishedNTCP2 ()) { - if (address->IsV4()) + if (address->host.is_v4()) { try { - auto ep = m_Address4 ? boost::asio::ip::tcp::endpoint (m_Address4->address(), address->port): - boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), address->port); - m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), ep)); - } - catch ( std::exception & ex ) + m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port))); + } + catch ( std::exception & ex ) { - LogPrint(eLogCritical, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); - ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); + LogPrint(eLogError, "NTCP2: Failed to bind to ip4 port ",address->port, ex.what()); continue; } - LogPrint (eLogInfo, "NTCP2: Start listening v4 TCP port ", address->port); + LogPrint (eLogInfo, "NTCP2: Start listening TCP port ", address->port); auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } - else if (address->IsV6() && (context.SupportsV6 () || context.SupportsMesh ())) + else if (address->host.is_v6() && context.SupportsV6 ()) { - m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); + m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (m_Service)); try { m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); -#if defined(__linux__) && !defined(_NETINET_IN_H) - if (!m_Address6 && !m_YggdrasilAddress) // 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_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); - } -#endif - auto ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port); - if (m_Address6 && !context.SupportsMesh ()) - ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port); - else if (m_YggdrasilAddress && !context.SupportsV6 ()) - ep = boost::asio::ip::tcp::endpoint (m_YggdrasilAddress->address(), address->port); - m_NTCP2V6Acceptor->bind (ep); + m_NTCP2V6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); m_NTCP2V6Acceptor->listen (); - LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); + LogPrint (eLogInfo, "NTCP2: Start listening V6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); - } - catch ( std::exception & ex ) - { - LogPrint(eLogCritical, "NTCP2: Failed to bind to v6 port ", address->port, ": ", ex.what()); - ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); + } catch ( std::exception & ex ) { + LogPrint(eLogError, "NTCP2: failed to bind to ip6 port ", address->port); continue; } } @@ -1568,67 +1200,64 @@ 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 (); - if (IsRunning ()) + if (m_IsRunning) { + m_IsRunning = false; m_TerminationTimer.cancel (); - m_ProxyEndpoint = nullptr; + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } } - StopIOService (); } - bool NTCP2Server::AddNTCP2Session (std::shared_ptr session, bool incoming) + void NTCP2Server::Run () { - if (!session) return false; - if (incoming) - m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ()); - if (!session->GetRemoteIdentity ()) + while (m_IsRunning) { - LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ()); - session->Terminate (); - return false; + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "NTCP2: runtime exception: ", ex.what ()); + } } + } + + bool NTCP2Server::AddNTCP2Session (std::shared_ptr session) + { + if (!session || !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"); - if (incoming) - { - // replace by new session - auto s = it->second; - s->MoveSendQueue (session); - m_NTCP2Sessions.erase (it); - s->Terminate (); - } - else - { - session->Terminate (); - return false; - } + LogPrint (eLogWarning, "NTCP2: session to ", ident.ToBase64 (), " already exists"); + 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) @@ -1639,57 +1268,28 @@ namespace transport return nullptr; } - void NTCP2Server::Connect(std::shared_ptr conn) + void NTCP2Server::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) { - if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) - { - 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 ", address ,":", port); + m_Service.post([this, address, port, conn]() { if (this->AddNTCP2Session (conn)) { - auto timer = std::make_shared(GetService ()); + auto timer = std::make_shared(m_Service); auto timeout = NTCP2_CONNECT_TIMEOUT * 5; conn->SetTerminationTimeout(timeout * 2); timer->expires_from_now (boost::posix_time::seconds(timeout)); - timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) + timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); + //i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); - // bind to local address - std::shared_ptr localAddress; - if (conn->GetRemoteEndpoint ().address ().is_v6 ()) - { - if (i2p::util::net::IsYggdrasilAddress (conn->GetRemoteEndpoint ().address ())) - localAddress = m_YggdrasilAddress; - else - localAddress = m_Address6; - conn->GetSocket ().open (boost::asio::ip::tcp::v6 ()); - } - else - { - localAddress = m_Address4; - conn->GetSocket ().open (boost::asio::ip::tcp::v4 ()); - } - if (localAddress) - { - boost::system::error_code ec; - conn->GetSocket ().bind (*localAddress, ec); - if (ec) - LogPrint (eLogError, "NTCP2: Can't bind to ", localAddress->address ().to_string (), ": ", ec.message ()); - } - conn->GetSocket ().async_connect (conn->GetRemoteEndpoint (), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); + conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); } - else - conn->Terminate (); }); } @@ -1703,54 +1303,35 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); + LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetSocket ().remote_endpoint ()); + if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 + context.UpdateNTCP2V6Address (conn->GetSocket ().local_endpoint ().address ()); 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->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 error ", error.message ()); - if (error == boost::asio::error::no_descriptors) - { - i2p::context.SetError (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_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } @@ -1758,47 +1339,26 @@ 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->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)); } @@ -1806,8 +1366,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)); } @@ -1825,196 +1384,23 @@ namespace transport LogPrint (eLogDebug, "NTCP2: No activity for ", session->GetTerminationTimeout (), " seconds"); session->TerminateByTimeout (); // it doesn't change m_NTCP2Session right a way } - else - it.second->DeleteNextReceiveBuffer (ts); // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { - if (it->second->IsEstablished () || it->second->IsTerminationTimeoutExpired (ts)) + if ((*it)->IsEstablished () || (*it)->IsTerminated ()) + it = m_PendingIncomingSessions.erase (it); // established or terminated + else if ((*it)->IsTerminationTimeoutExpired (ts)) { - it->second->Terminate (); - it = m_PendingIncomingSessions.erase (it); // established of expired + (*it)->Terminate (); + it = m_PendingIncomingSessions.erase (it); // expired } - else if (it->second->IsTerminated ()) - it = m_PendingIncomingSessions.erase (it); // already terminated else it++; } + ScheduleTermination (); - - // try to restart acceptors if no description - // we do it after timer to let timer take descriptor first - if (i2p::context.GetError () == eRouterErrorNoDescriptors) - { - i2p::context.SetError (eRouterErrorNone); - auto conn = std::make_shared (*this); - m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, - conn, std::placeholders::_1)); - } - if (i2p::context.GetErrorV6 () == eRouterErrorNoDescriptors) - { - i2p::context.SetErrorV6 (eRouterErrorNone); - auto conn = std::make_shared (*this); - m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, - conn, std::placeholders::_1)); - } } } - - void NTCP2Server::ConnectWithProxy (std::shared_ptr conn) - { - if(!m_ProxyEndpoint) return; - if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) - { - LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); - return; - } - boost::asio::post (GetService(), [this, conn]() - { - if (this->AddNTCP2Session (conn)) - { - auto timer = std::make_shared(GetService()); - auto timeout = NTCP2_CONNECT_TIMEOUT * 5; - conn->SetTerminationTimeout(timeout * 2); - timer->expires_from_now (boost::posix_time::seconds(timeout)); - timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - conn->Terminate (); - } - }); - conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer)); - } - }); - } - - void NTCP2Server::UseProxy(ProxyType proxytype, const std::string& addr, uint16_t port, - const std::string& user, const std::string& pass) - { - m_ProxyType = proxytype; - m_ProxyAddress = addr; - m_ProxyPort = port; - if (m_ProxyType == eHTTPProxy ) - m_ProxyAuthorization = i2p::http::CreateBasicAuthorizationString (user, pass); - } - - void NTCP2Server::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) - { - if (ecode) - { - LogPrint(eLogWarning, "NTCP2: Failed to connect to proxy ", ecode.message()); - timer->cancel(); - conn->Terminate(); - return; - } - switch (m_ProxyType) - { - 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 - { - LogPrint(eLogError, "NTCP2: SOCKS proxy handshake error ", ec.message()); - conn->Terminate(); - } - }); - break; - } - case eHTTPProxy: - { - auto& ep = conn->GetRemoteEndpoint (); - i2p::http::HTTPReq req; - req.method = "CONNECT"; - req.version ="HTTP/1.1"; - if(ep.address ().is_v6 ()) - req.uri = "[" + ep.address ().to_string() + "]:" + std::to_string(ep.port ()); - else - req.uri = ep.address ().to_string() + ":" + std::to_string(ep.port ()); - if (!m_ProxyAuthorization.empty ()) - req.AddHeader("Proxy-Authorization", m_ProxyAuthorization); - - boost::asio::streambuf writebuff; - std::ostream out(&writebuff); - out << req.to_string(); - - boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), - [](const boost::system::error_code & ec, std::size_t transferred) - { - (void) transferred; - if(ec) - LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message()); - }); - - auto readbuff = std::make_shared(); - boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", - [readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) - { - if(ec) - { - LogPrint(eLogError, "NTCP2: HTTP proxy read error ", ec.message()); - timer->cancel(); - conn->Terminate(); - } - else - { - 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.code == 200) - { - timer->cancel(); - conn->ClientLogin(); - return; - } - else - LogPrint(eLogError, "NTCP2: HTTP proxy rejected request ", res.code); - } - else - LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response"); - timer->cancel(); - conn->Terminate(); - } - }); - break; - } - default: - LogPrint(eLogError, "NTCP2: Unknown proxy type, invalid state"); - } - } - - void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) - { - auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); - if (localAddress.is_v6 ()) - { - if (i2p::util::net::IsYggdrasilAddress (localAddress)) - m_YggdrasilAddress = addr; - else - m_Address6 = addr; - } - 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 5ad5b955..2bfc72b9 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,20 +1,22 @@ /* -* Copyright (c) 2013-2024, The PurpleI2P Project +* Copyright (c) 2013-2018, 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 NTCP2_H__ #define NTCP2_H__ #include #include +#include #include #include #include -#include #include #include #include @@ -28,23 +30,15 @@ namespace i2p 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 size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519; 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_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 + const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes + const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds - const int NTCP2_CLOCK_SKEW = 60; // in seconds + const int NTCP2_CLOCK_SKEW = 60; // in seconds const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up enum NTCP2BlockType @@ -54,8 +48,8 @@ namespace transport eNTCP2BlkRouterInfo, // 2 eNTCP2BlkI2NPMessage, // 3 eNTCP2BlkTermination, // 4 - eNTCP2BlkPadding = 254 - }; + eNTCP2BlkPadding = 254 + }; enum NTCP2TerminationReason { @@ -77,91 +71,85 @@ namespace transport eNTCP2RouterInfoSignatureVerificationFail, // 15 eNTCP2IncorrectSParameter, // 16 eNTCP2Banned, // 17 - }; - + }; + // RouterInfo flags - const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; + const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; - struct NTCP2Establisher: private i2p::crypto::NoiseSymmetricState + struct NTCP2Establisher { NTCP2Establisher (); ~NTCP2Establisher (); - - const uint8_t * GetPub () const { return m_EphemeralKeys->GetPublicKey (); }; + + const uint8_t * GetPub () const { return m_EphemeralKeys.GetPublicKey (); }; 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_K; }; 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 MixKey (const uint8_t * inputKeyMaterial); + void MixHash (const uint8_t * buf, size_t len); + 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 ProcessSessionRequestMessage (uint16_t& paddingLen); 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; + i2p::crypto::X25519Keys m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 - uint8_t m_RemoteStaticKey[32], m_IV[16]; + uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[33] /*ck*/, m_K[32] /*k*/; i2p::data::IdentHash m_RemoteIdentHash; - uint16_t m3p2Len; + uint16_t m3p2Len; - uint8_t m_SessionRequestBuffer[NTCP2_SESSION_REQUEST_MAX_SIZE], - m_SessionCreatedBuffer[NTCP2_SESSION_CREATED_MAX_SIZE], * m_SessionConfirmedBuffer; + uint8_t * m_SessionRequestBuffer, * m_SessionCreatedBuffer, * m_SessionConfirmedBuffer; size_t m_SessionRequestBufferLen, m_SessionCreatedBufferLen; - }; + }; class NTCP2Server; class NTCP2Session: public TransportSession, public std::enable_shared_from_this { public: - NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr, - std::shared_ptr addr = nullptr); + NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr); ~NTCP2Session (); void Terminate (); void TerminateByTimeout (); - void Done () override; - void Close (); // for accept - void DeleteNextReceiveBuffer (uint64_t ts); + void Done (); 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 ClientLogin (); // Alice void ServerLogin (); // Bob + + void SendLocalRouterInfo (); // after handshake + void SendI2NPMessages (const std::vector >& msgs); - void SendLocalRouterInfo (bool update) override; // after handshake or by update - void SendI2NPMessages (std::list >& msgs) override; - void MoveSendQueue (std::shared_ptr other); - private: void Established (); void CreateNonce (uint64_t seqn, uint8_t * nonce); - void CreateNextReceivedBuffer (size_t size); void KeyDerivationFunctionDataPhase (); void SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey); @@ -172,17 +160,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,27 +184,26 @@ namespace transport void SendRouterInfo (); void SendTermination (NTCP2TerminationReason reason); void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void PostI2NPMessages (); + void PostI2NPMessages (std::vector > msgs); private: NTCP2Server& m_Server; boost::asio::ip::tcp::socket m_Socket; - boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsEstablished, m_IsTerminated; std::unique_ptr m_Establisher; // data phase - uint8_t m_Kab[32], m_Kba[32], m_Sipkeysab[32], m_Sipkeysba[32]; + uint8_t m_Kab[33], m_Kba[32], m_Sipkeysab[33], m_Sipkeysba[32]; const uint8_t * m_SendKey, * m_ReceiveKey; -#if OPENSSL_SIPHASH +#if OPENSSL_SIPHASH + EVP_PKEY * m_SendSipKey, * m_ReceiveSipKey; EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx; #else const uint8_t * m_SendSipKey, * m_ReceiveSipKey; #endif - uint16_t m_NextReceivedLen; + uint16_t m_NextReceivedLen; uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer; - size_t m_NextReceivedBufferSize; union { uint8_t buf[8]; @@ -230,97 +213,51 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; - bool m_IsSending, m_IsReceiving; + bool m_IsSending; 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 + class NTCP2Server { - private: - - class EstablisherService: public i2p::util::RunnableServiceWithWork - { - public: - - EstablisherService (): RunnableServiceWithWork ("NTCP2e") {}; - auto& GetService () { return GetIOService (); }; - void Start () { StartIOService (); }; - void Stop () { StopIOService (); }; - }; - public: - enum ProxyType - { - eNoProxy, - 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); - - bool AddNTCP2Session (std::shared_ptr session, bool incoming = false); + bool AddNTCP2Session (std::shared_ptr session); void RemoveNTCP2Session (std::shared_ptr session); std::shared_ptr FindNTCP2Session (const i2p::data::IdentHash& ident); - void ConnectWithProxy (std::shared_ptr conn); - void Connect(std::shared_ptr conn); - - bool UsingProxy() const { return m_ProxyType != eNoProxy; }; - void UseProxy(ProxyType proxy, const std::string& address, uint16_t port, const std::string& user, const std::string& pass); - - void SetLocalAddress (const boost::asio::ip::address& localAddress); + boost::asio::io_service& GetService () { return m_Service; }; + + void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); private: + void Run (); void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); - 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 HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); + // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); private: + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; + boost::asio::io_service::work m_Work; boost::asio::deadline_timer m_TerminationTimer; std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; - std::map > m_NTCP2Sessions; - std::map > m_PendingIncomingSessions; + std::map > m_NTCP2Sessions; + 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/NTCPSession.cpp b/libi2pd/NTCPSession.cpp new file mode 100644 index 00000000..68a4133f --- /dev/null +++ b/libi2pd/NTCPSession.cpp @@ -0,0 +1,1314 @@ +#include +#include +#include + +#include "I2PEndian.h" +#include "Base.h" +#include "Crypto.h" +#include "Log.h" +#include "Timestamp.h" +#include "I2NPProtocol.h" +#include "RouterContext.h" +#include "Transports.h" +#include "NetDb.hpp" +#include "NTCPSession.h" +#include "HTTP.h" +#include "util.h" +#ifdef WITH_EVENTS +#include "Event.h" +#endif + +using namespace i2p::crypto; + +namespace i2p +{ +namespace transport +{ + + struct NTCPWork + { + std::shared_ptr session; + }; + + NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): + TransportSession (in_RemoteRouter, NTCP_ESTABLISH_TIMEOUT), + m_Server (server), m_Socket (m_Server.GetService ()), + m_IsEstablished (false), m_IsTerminated (false), + m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) + { + m_Establisher = new Establisher; + } + + NTCPSession::~NTCPSession () + { + delete m_Establisher; + } + + void NTCPSession::CreateAESKey (uint8_t * pubKey) + { + uint8_t sharedKey[256]; + m_DHKeysPair->Agree (pubKey, sharedKey); // time consuming operation + + i2p::crypto::AESKey aesKey; + if (sharedKey[0] & 0x80) + { + aesKey[0] = 0; + memcpy (aesKey + 1, sharedKey, 31); + } + else if (sharedKey[0]) + memcpy (aesKey, sharedKey, 32); + else + { + // find first non-zero byte + uint8_t * nonZero = sharedKey + 1; + while (!*nonZero) + { + nonZero++; + if (nonZero - sharedKey > 32) + { + LogPrint (eLogWarning, "NTCP: First 32 bytes of shared key is all zeros, ignored"); + return; + } + } + memcpy (aesKey, nonZero, 32); + } + + m_Decryption.SetKey (aesKey); + m_Encryption.SetKey (aesKey); + } + + void NTCPSession::Done () + { + m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ())); + } + + void NTCPSession::Terminate () + { + if (!m_IsTerminated) + { + m_IsTerminated = true; + m_IsEstablished = false; + m_Socket.close (); + transports.PeerDisconnected (shared_from_this ()); + m_Server.RemoveNTCPSession (shared_from_this ()); + m_SendQueue.clear (); + m_NextMessage = nullptr; + LogPrint (eLogDebug, "NTCP: session terminated"); + } + } + + void NTCPSession::Connected () + { + m_IsEstablished = true; + + delete m_Establisher; + m_Establisher = nullptr; + + m_DHKeysPair = nullptr; + + SetTerminationTimeout (NTCP_TERMINATION_TIMEOUT); + SendTimeSyncMessage (); + transports.PeerConnected (shared_from_this ()); + } + + boost::asio::io_service & NTCPSession::GetService() + { + return m_Server.GetService(); + } + + void NTCPSession::ClientLogin () + { + if (!m_DHKeysPair) + m_DHKeysPair = transports.GetNextDHKeysPair (); + // send Phase1 + const uint8_t * x = m_DHKeysPair->GetPublicKey (); + memcpy (m_Establisher->phase1.pubKey, x, 256); + SHA256(x, 256, m_Establisher->phase1.HXxorHI); + const uint8_t * ident = m_RemoteIdentity->GetIdentHash (); + for (int i = 0; i < 32; i++) + m_Establisher->phase1.HXxorHI[i] ^= ident[i]; + + boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + + void NTCPSession::ServerLogin () + { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + // receive Phase1 + boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } + + void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } + } + + void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + // verify ident + uint8_t digest[32]; + SHA256(m_Establisher->phase1.pubKey, 256, digest); + const uint8_t * ident = i2p::context.GetIdentHash (); + for (int i = 0; i < 32; i++) + { + if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i]) + { + LogPrint (eLogError, "NTCP: phase 1 error: ident mismatch"); + Terminate (); + return; + } + } + // TODO: check for number of pending keys + auto work = new NTCPWork{shared_from_this()}; + m_Server.Work(work->session, [work, this]() -> std::function { + if (!work->session->m_DHKeysPair) + work->session->m_DHKeysPair = transports.GetNextDHKeysPair (); + work->session->CreateAESKey (work->session->m_Establisher->phase1.pubKey); + return std::bind(&NTCPSession::SendPhase2, work->session, work); + }); + } + } + + void NTCPSession::SendPhase2 (NTCPWork * work) + { + if(work) + delete work; + const uint8_t * y = m_DHKeysPair->GetPublicKey (); + memcpy (m_Establisher->phase2.pubKey, y, 256); + uint8_t xy[512]; + memcpy (xy, m_Establisher->phase1.pubKey, 256); + memcpy (xy + 256, y, 256); + SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy); + uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ()); + memcpy (m_Establisher->phase2.encrypted.timestamp, &tsB, 4); + RAND_bytes (m_Establisher->phase2.encrypted.filler, 12); + + m_Encryption.SetIV (y + 240); + m_Decryption.SetIV (m_Establisher->phase1.HXxorHI + 16); + + m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted); + boost::asio::async_write(m_Socket, boost::asio::buffer (&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all(), + std::bind(&NTCPSession::HandlePhase2Sent, shared_from_this(), std::placeholders::_1, std::placeholders::_2, tsB)); + } + + void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, tsB)); + } + } + + void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); + if (ecode != boost::asio::error::operation_aborted) + { + // this RI is not valid + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); + transports.ReuseDHKeysPair (m_DHKeysPair); + m_DHKeysPair = nullptr; + Terminate (); + } + } + else + { + auto work = new NTCPWork{shared_from_this()}; + m_Server.Work(work->session, [work, this]() -> std::function { + work->session->CreateAESKey (work->session->m_Establisher->phase2.pubKey); + return std::bind(&NTCPSession::HandlePhase2, work->session, work); + }); + } + } + + void NTCPSession::HandlePhase2 (NTCPWork * work) + { + if(work) delete work; + m_Decryption.SetIV (m_Establisher->phase2.pubKey + 240); + m_Encryption.SetIV (m_Establisher->phase1.HXxorHI + 16); + + m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted); + // verify + uint8_t xy[512]; + memcpy (xy, m_DHKeysPair->GetPublicKey (), 256); + memcpy (xy + 256, m_Establisher->phase2.pubKey, 256); + uint8_t digest[32]; + SHA256 (xy, 512, digest); + if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32)) + { + LogPrint (eLogError, "NTCP: Phase 2 process error: incorrect hash"); + transports.ReuseDHKeysPair (m_DHKeysPair); + m_DHKeysPair = nullptr; + Terminate (); + return ; + } + SendPhase3 (); + } + + void NTCPSession::SendPhase3 () + { + auto& keys = i2p::context.GetPrivateKeys (); + uint8_t * buf = m_ReceiveBuffer; + htobe16buf (buf, keys.GetPublic ()->GetFullLen ()); + buf += 2; + buf += i2p::context.GetIdentity ()->ToBuffer (buf, NTCP_BUFFER_SIZE); + uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ()); + htobuf32(buf,tsA); + buf += 4; + size_t signatureLen = keys.GetPublic ()->GetSignatureLen (); + size_t len = (buf - m_ReceiveBuffer) + signatureLen; + size_t paddingSize = len & 0x0F; // %16 + if (paddingSize > 0) + { + paddingSize = 16 - paddingSize; + // fill padding with random data + RAND_bytes(buf, paddingSize); + buf += paddingSize; + len += paddingSize; + } + + SignedData s; + s.Insert (m_Establisher->phase1.pubKey, 256); // x + s.Insert (m_Establisher->phase2.pubKey, 256); // y + s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident + s.Insert (tsA); // tsA + s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB + s.Sign (keys, buf); + + m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer); + boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, len), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase3Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsA)); + } + + void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + // wait for phase4 + auto signatureLen = m_RemoteIdentity->GetSignatureLen (); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) signatureLen += (16 - paddingSize); + boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase4Received, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, tsA)); + } + } + + void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB) + { + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Phase 3 read error: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); + uint8_t * buf = m_ReceiveBuffer; + uint16_t size = bufbe16toh (buf); + auto identity = std::make_shared (buf + 2, size); + if (m_Server.FindNTCPSession (identity->GetIdentHash ())) + { + LogPrint (eLogInfo, "NTCP: session already exists"); + Terminate (); + } + auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already + SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); + + size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen (); + size_t paddingLen = expectedSize & 0x0F; + if (paddingLen) paddingLen = (16 - paddingLen); + if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE) + { + // we need more bytes for Phase3 + expectedSize += paddingLen; + boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize - NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, tsB, paddingLen)); + } + else + HandlePhase3 (tsB, paddingLen); + } + } + + void NTCPSession::HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen) + { + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Phase 3 extra read error: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE); + HandlePhase3 (tsB, paddingLen); + } + } + + void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen) + { + uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity->GetFullLen () + 2 /*size*/; + uint32_t tsA = buf32toh(buf); + buf += 4; + buf += paddingLen; + + // check timestamp + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t tsA1 = be32toh (tsA); + if (tsA1 < ts - NTCP_CLOCK_SKEW || tsA1 > ts + NTCP_CLOCK_SKEW) + { + LogPrint (eLogError, "NTCP: Phase3 time difference ", (int)(ts - tsA1), " exceeds clock skew"); + Terminate (); + return; + } + + // check signature + SignedData s; + s.Insert (m_Establisher->phase1.pubKey, 256); // x + s.Insert (m_Establisher->phase2.pubKey, 256); // y + s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident + s.Insert (tsA); // tsA + s.Insert (tsB); // tsB + if (!s.Verify (m_RemoteIdentity, buf)) + { + LogPrint (eLogError, "NTCP: signature verification failed"); + Terminate (); + return; + } + + SendPhase4 (tsA, tsB); + } + + void NTCPSession::SendPhase4 (uint32_t tsA, uint32_t tsB) + { + SignedData s; + s.Insert (m_Establisher->phase1.pubKey, 256); // x + s.Insert (m_Establisher->phase2.pubKey, 256); // y + s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident + s.Insert (tsA); // tsA + s.Insert (tsB); // tsB + auto& keys = i2p::context.GetPrivateKeys (); + auto signatureLen = keys.GetPublic ()->GetSignatureLen (); + s.Sign (keys, m_ReceiveBuffer); + size_t paddingSize = signatureLen & 0x0F; // %16 + if (paddingSize > 0) signatureLen += (16 - paddingSize); + m_Encryption.Encrypt (m_ReceiveBuffer, signatureLen, m_ReceiveBuffer); + + boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase4Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + + void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + (void) bytes_transferred; + if (ecode) + { + LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + LogPrint (eLogDebug, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected"); + m_Server.AddNTCPSession (shared_from_this ()); + + Connected (); + m_ReceiveBufferOffset = 0; + m_NextMessage = nullptr; + Receive (); + } + } + + void NTCPSession::HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA) + { + if (ecode) + { + LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock"); + if (ecode != boost::asio::error::operation_aborted) + { + // this router doesn't like us + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); + Terminate (); + } + } + else + { + m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); + + // check timestamp + uint32_t tsB = bufbe32toh (m_Establisher->phase2.encrypted.timestamp); + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (tsB < ts - NTCP_CLOCK_SKEW || tsB > ts + NTCP_CLOCK_SKEW) + { + LogPrint (eLogError, "NTCP: Phase4 time difference ", (int)(ts - tsB), " exceeds clock skew"); + Terminate (); + return; + } + + // verify signature + SignedData s; + s.Insert (m_Establisher->phase1.pubKey, 256); // x + s.Insert (m_Establisher->phase2.pubKey, 256); // y + s.Insert (i2p::context.GetIdentHash (), 32); // ident + s.Insert (tsA); // tsA + s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB + + if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer)) + { + LogPrint (eLogError, "NTCP: Phase 4 process error: signature verification failed"); + Terminate (); + return; + } + LogPrint (eLogDebug, "NTCP: session to ", m_Socket.remote_endpoint (), " connected"); + Connected (); + + m_ReceiveBufferOffset = 0; + m_NextMessage = nullptr; + Receive (); + } + } + + void NTCPSession::Receive () + { + m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, NTCP_BUFFER_SIZE - m_ReceiveBufferOffset), + std::bind(&NTCPSession::HandleReceived, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } + + void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + { + if (ecode != boost::asio::error::operation_aborted) + LogPrint (eLogDebug, "NTCP: Read error: ", ecode.message ()); + //if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else + { + m_NumReceivedBytes += bytes_transferred; + i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); + m_ReceiveBufferOffset += bytes_transferred; + + if (m_ReceiveBufferOffset >= 16) + { + // process received data + uint8_t * nextBlock = m_ReceiveBuffer; + while (m_ReceiveBufferOffset >= 16) + { + if (!DecryptNextBlock (nextBlock)) // 16 bytes + { + Terminate (); + return; + } + nextBlock += 16; + m_ReceiveBufferOffset -= 16; + } + if (m_ReceiveBufferOffset > 0) + memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); + } + + // read and process more is available + boost::system::error_code ec; + size_t moreBytes = m_Socket.available(ec); + if (moreBytes && !ec) + { + uint8_t * buf = nullptr, * moreBuf = m_ReceiveBuffer; + if (moreBytes + m_ReceiveBufferOffset > NTCP_BUFFER_SIZE) + { + buf = new uint8_t[moreBytes + m_ReceiveBufferOffset + 16]; + moreBuf = buf; + uint8_t rem = ((size_t)buf) & 0x0f; + if (rem) moreBuf += (16 - rem); // align 16 + if (m_ReceiveBufferOffset) + memcpy (moreBuf, m_ReceiveBuffer, m_ReceiveBufferOffset); + } + moreBytes = m_Socket.read_some (boost::asio::buffer (moreBuf + m_ReceiveBufferOffset, moreBytes), ec); + if (ec) + { + LogPrint (eLogInfo, "NTCP: Read more bytes error: ", ec.message ()); + delete[] buf; + Terminate (); + return; + } + m_ReceiveBufferOffset += moreBytes; + m_NumReceivedBytes += moreBytes; + i2p::transport::transports.UpdateReceivedBytes (moreBytes); + // process more data + uint8_t * nextBlock = moreBuf; + while (m_ReceiveBufferOffset >= 16) + { + if (!DecryptNextBlock (nextBlock)) // 16 bytes + { + delete[] buf; + Terminate (); + return; + } + nextBlock += 16; + m_ReceiveBufferOffset -= 16; + } + if (m_ReceiveBufferOffset > 0) + memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); // nextBlock points to memory inside buf + delete[] buf; + } + m_Handler.Flush (); + + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + Receive (); + } + } + + bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes + { + if (!m_NextMessage) // new message, header expected + { + // decrypt header and extract length + uint8_t buf[16]; + m_Decryption.Decrypt (encrypted, buf); + uint16_t dataSize = bufbe16toh (buf); + if (dataSize) + { + // new message + if (dataSize + 16U + 15U > NTCP_MAX_MESSAGE_SIZE - 2) // + 6 + padding + { + LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size"); + return false; + } + m_NextMessage = (dataSize + 16U + 15U) <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); + m_NextMessage->Align (16); + m_NextMessage->offset += 2; // size field + m_NextMessage->len = m_NextMessage->offset + dataSize; + memcpy (m_NextMessage->GetBuffer () - 2, buf, 16); + m_NextMessageOffset = 16; + } + else + { + // timestamp + int diff = (int)bufbe32toh (buf + 2) - (int)i2p::util::GetSecondsSinceEpoch (); + LogPrint (eLogInfo, "NTCP: Timestamp. Time difference ", diff, " seconds"); + return true; + } + } + else // message continues + { + m_Decryption.Decrypt (encrypted, m_NextMessage->GetBuffer () - 2 + m_NextMessageOffset); + m_NextMessageOffset += 16; + } + + if (m_NextMessageOffset >= m_NextMessage->GetLength () + 2 + 4) // +checksum + { + // we have a complete I2NP message + uint8_t checksum[4]; + htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->GetBuffer () - 2, m_NextMessageOffset - 4)); + if (!memcmp (m_NextMessage->GetBuffer () - 2 + m_NextMessageOffset - 4, checksum, 4)) + { + if (!m_NextMessage->IsExpired ()) + { +#ifdef WITH_EVENTS + QueueIntEvent("transport.recvmsg", GetIdentHashBase64(), 1); +#endif + m_Handler.PutNextMessage (m_NextMessage); + } + else + LogPrint (eLogInfo, "NTCP: message expired"); + } + else + LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped"); + m_NextMessage = nullptr; + } + return true; + } + + void NTCPSession::Send (std::shared_ptr msg) + { + m_IsSending = true; + boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector >{ msg })); + } + + boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr msg) + { + uint8_t * sendBuffer; + int len; + + if (msg) + { + // regular I2NP + if (msg->offset < 2) + LogPrint (eLogError, "NTCP: Malformed I2NP message"); // TODO: + sendBuffer = msg->GetBuffer () - 2; + len = msg->GetLength (); + htobe16buf (sendBuffer, len); + } + else + { + // prepare timestamp + sendBuffer = m_TimeSyncBuffer; + len = 4; + htobuf16(sendBuffer, 0); + htobe32buf (sendBuffer + 2, i2p::util::GetSecondsSinceEpoch ()); + } + int rem = (len + 6) & 0x0F; // %16 + int padding = 0; + if (rem > 0) { + padding = 16 - rem; + // fill with random padding + RAND_bytes(sendBuffer + len + 2, padding); + } + htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding)); + + int l = len + padding + 6; + m_Encryption.Encrypt(sendBuffer, l, sendBuffer); + return boost::asio::buffer ((const uint8_t *)sendBuffer, l); + } + + + void NTCPSession::Send (const std::vector >& msgs) + { + m_IsSending = true; + std::vector bufs; + for (const auto& it: msgs) + bufs.push_back (CreateMsgBuffer (it)); + boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); + } + + void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs) + { + (void) msgs; + m_IsSending = false; + if (ecode) + { + LogPrint (eLogWarning, "NTCP: Couldn't send msgs: ", ecode.message ()); + // we shouldn't call Terminate () here, because HandleReceive takes care + // TODO: 'delete this' statement in Terminate () must be eliminated later + // Terminate (); + } + else + { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumSentBytes += bytes_transferred; + i2p::transport::transports.UpdateSentBytes (bytes_transferred); + if (!m_SendQueue.empty()) + { + Send (m_SendQueue); + m_SendQueue.clear (); + } + } + } + + + void NTCPSession::SendTimeSyncMessage () + { + Send (nullptr); + } + + + void NTCPSession::SendI2NPMessages (const std::vector >& msgs) + { + m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs)); + } + + void NTCPSession::PostI2NPMessages (std::vector > msgs) + { + if (m_IsTerminated) return; + if (m_IsSending) + { + if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE) + { + for (const auto& it: msgs) + m_SendQueue.push_back (it); + } + else + { + LogPrint (eLogWarning, "NTCP: outgoing messages queue size exceeds ", NTCP_MAX_OUTGOING_QUEUE_SIZE); + Terminate (); + } + } + else + Send (msgs); + } + +//----------------------------------------- + NTCPServer::NTCPServer (int workers): + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + m_TerminationTimer (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr), + m_ProxyType(eNoProxy), m_Resolver(m_Service), m_ProxyEndpoint(nullptr), + m_SoftLimit(0), m_HardLimit(0) + { + if(workers <= 0) workers = 1; + m_CryptoPool = std::make_shared(workers); + } + + NTCPServer::~NTCPServer () + { + Stop (); + } + + void NTCPServer::Start () + { + if (!m_IsRunning) + { + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); + // we are using a proxy, don't create any acceptors + if(UsingProxy()) + { + // TODO: resolve proxy until it is resolved + boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort)); + boost::system::error_code e; + auto itr = m_Resolver.resolve(q, e); + if(e) + { + LogPrint(eLogError, "NTCP: Failed to resolve proxy ", e.message()); + } + else + { + m_ProxyEndpoint = new boost::asio::ip::tcp::endpoint(*itr); + } + } + else + { + // create acceptors + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address: addresses) + { + if (!address) continue; + if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !address->IsNTCP2 ()) + { + if (address->host.is_v4()) + { + try + { + m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); + } catch ( std::exception & ex ) { + /** fail to bind ip4 */ + LogPrint(eLogError, "NTCP: Failed to bind to ip4 port ",address->port, ex.what()); + continue; + } + + LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port); + auto conn = std::make_shared(*this); + m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, conn, std::placeholders::_1)); + } + else if (address->host.is_v6() && context.SupportsV6 ()) + { + m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); + try + { + m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); + m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); + m_NTCPV6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); + m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); + m_NTCPV6Acceptor->listen (); + + LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port); + auto conn = std::make_shared (*this); + m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); + } catch ( std::exception & ex ) { + LogPrint(eLogError, "NTCP: failed to bind to ip6 port ", address->port); + continue; + } + } + } + } + } + ScheduleTermination (); + } + } + + void NTCPServer::Stop () + { + { + // we have to copy it because Terminate changes m_NTCPSessions + auto ntcpSessions = m_NTCPSessions; + for (auto& it: ntcpSessions) + it.second->Terminate (); + for (auto& it: m_PendingIncomingSessions) + it->Terminate (); + } + m_NTCPSessions.clear (); + + if (m_IsRunning) + { + m_IsRunning = false; + m_TerminationTimer.cancel (); + if (m_NTCPAcceptor) + { + delete m_NTCPAcceptor; + m_NTCPAcceptor = nullptr; + } + if (m_NTCPV6Acceptor) + { + delete m_NTCPV6Acceptor; + m_NTCPV6Acceptor = nullptr; + } + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + if(m_ProxyEndpoint) + { + delete m_ProxyEndpoint; + m_ProxyEndpoint = nullptr; + } + } + } + + + void NTCPServer::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "NTCP: runtime exception: ", ex.what ()); + } + } + } + + bool NTCPServer::AddNTCPSession (std::shared_ptr session) + { + if (!session || !session->GetRemoteIdentity ()) return false; + auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); + auto it = m_NTCPSessions.find (ident); + if (it != m_NTCPSessions.end ()) + { + LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists"); + session->Terminate(); + return false; + } + m_NTCPSessions.insert (std::pair >(ident, session)); + return true; + } + + void NTCPServer::RemoveNTCPSession (std::shared_ptr session) + { + if (session && session->GetRemoteIdentity ()) + m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); + } + + std::shared_ptr NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident) + { + auto it = m_NTCPSessions.find (ident); + if (it != m_NTCPSessions.end ()) + return it->second; + return nullptr; + } + + void NTCPServer::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) + { + if (!error) + { + boost::system::error_code ec; + auto ep = conn->GetSocket ().remote_endpoint(ec); + if (!ec) + { + if(ShouldLimit()) + { + // hit limit, close premature + LogPrint(eLogWarning, "NTCP: limiting with backoff session from ", ep); + conn->Terminate(); + return; + } + LogPrint (eLogDebug, "NTCP: Connected from ", ep); + if (conn) + { + conn->ServerLogin (); + m_PendingIncomingSessions.push_back (conn); + } + } + else + LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); + } + + + if (error != boost::asio::error::operation_aborted) + { + conn = std::make_shared (*this); + m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, + conn, std::placeholders::_1)); + } + } + + void NTCPServer::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) + { + if (!error) + { + boost::system::error_code ec; + auto ep = conn->GetSocket ().remote_endpoint(ec); + if (!ec) + { + if(ShouldLimit()) + { + // hit limit, close premature + LogPrint(eLogWarning, "NTCP: limiting with backoff on session from ", ep); + conn->Terminate(); + return; + } + + LogPrint (eLogDebug, "NTCP: Connected from ", ep); + if (conn) + { + conn->ServerLogin (); + m_PendingIncomingSessions.push_back (conn); + } + } + else + LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); + } + + if (error != boost::asio::error::operation_aborted) + { + conn = std::make_shared (*this); + m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, + conn, std::placeholders::_1)); + } + } + + void NTCPServer::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) + { + LogPrint (eLogDebug, "NTCP: Connecting to ", address ,":", port); + m_Service.post([=]() { + if (this->AddNTCPSession (conn)) + { + + auto timer = std::make_shared(m_Service); + timer->expires_from_now (boost::posix_time::seconds(NTCP_CONNECT_TIMEOUT)); + timer->async_wait ([conn](const boost::system::error_code& ecode) { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "NTCP: Not connected in ", NTCP_CONNECT_TIMEOUT, " seconds"); + conn->Terminate (); + } + }); + conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn, timer)); + } + }); + } + + void NTCPServer::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) + { + if(m_ProxyEndpoint == nullptr) + { + return; + } + m_Service.post([=]() { + if (this->AddNTCPSession (conn)) + { + + auto timer = std::make_shared(m_Service); + auto timeout = NTCP_CONNECT_TIMEOUT * 5; + conn->SetTerminationTimeout(timeout * 2); + timer->expires_from_now (boost::posix_time::seconds(timeout)); + timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "NTCP: Not connected in ", timeout, " seconds"); + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + conn->Terminate (); + } + }); + conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCPServer::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); + } + }); + } + + void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) + { + timer->cancel (); + if (ecode) + { + LogPrint (eLogInfo, "NTCP: Connect error ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + conn->Terminate (); + } + else + { + LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ()); + if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 + context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); + conn->ClientLogin (); + } + } + + void NTCPServer::UseProxy(ProxyType proxytype, const std::string & addr, uint16_t port) + { + m_ProxyType = proxytype; + m_ProxyAddress = addr; + m_ProxyPort = port; + } + + void NTCPServer::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + { + if(ecode) + { + LogPrint(eLogWarning, "NTCP: failed to connect to proxy ", ecode.message()); + timer->cancel(); + conn->Terminate(); + return; + } + if(m_ProxyType == eSocksProxy) + { + // TODO: support username/password auth etc + uint8_t buff[3] = {0x05, 0x01, 0x00}; + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), [=] (const boost::system::error_code & ec, std::size_t transferred) { + (void) transferred; + if(ec) + { + LogPrint(eLogWarning, "NTCP: socks5 write error ", ec.message()); + } + }); + uint8_t readbuff[2]; + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, 2), [=](const boost::system::error_code & ec, std::size_t transferred) { + if(ec) + { + LogPrint(eLogError, "NTCP: socks5 read error ", ec.message()); + timer->cancel(); + conn->Terminate(); + return; + } + else if(transferred == 2) + { + if(readbuff[1] == 0x00) + { + AfterSocksHandshake(conn, timer, host, port, addrtype); + return; + } + else if (readbuff[1] == 0xff) + { + LogPrint(eLogError, "NTCP: socks5 proxy rejected authentication"); + timer->cancel(); + conn->Terminate(); + return; + } + } + LogPrint(eLogError, "NTCP: socks5 server gave invalid response"); + timer->cancel(); + conn->Terminate(); + }); + } + else if(m_ProxyType == eHTTPProxy) + { + i2p::http::HTTPReq req; + req.method = "CONNECT"; + req.version ="HTTP/1.1"; + if(addrtype == eIP6Address) + req.uri = "[" + host + "]:" + std::to_string(port); + else + req.uri = host + ":" + std::to_string(port); + + boost::asio::streambuf writebuff; + std::ostream out(&writebuff); + out << req.to_string(); + + boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), [=](const boost::system::error_code & ec, std::size_t transferred) { + (void) transferred; + if(ec) + LogPrint(eLogError, "NTCP: http proxy write error ", ec.message()); + }); + + boost::asio::streambuf * readbuff = new boost::asio::streambuf; + boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", [=] (const boost::system::error_code & ec, std::size_t transferred) { + if(ec) + { + LogPrint(eLogError, "NTCP: http proxy read error ", ec.message()); + timer->cancel(); + conn->Terminate(); + } + else + { + readbuff->commit(transferred); + i2p::http::HTTPRes res; + if(res.parse(boost::asio::buffer_cast(readbuff->data()), readbuff->size()) > 0) + { + if(res.code == 200) + { + timer->cancel(); + conn->ClientLogin(); + delete readbuff; + return; + } + else + { + LogPrint(eLogError, "NTCP: http proxy rejected request ", res.code); + } + } + else + LogPrint(eLogError, "NTCP: http proxy gave malformed response"); + timer->cancel(); + conn->Terminate(); + delete readbuff; + } + }); + } + else + LogPrint(eLogError, "NTCP: unknown proxy type, invalid state"); + } + + void NTCPServer::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + { + + // build request + size_t sz = 0; + uint8_t buff[256]; + uint8_t readbuff[256]; + buff[0] = 0x05; + buff[1] = 0x01; + buff[2] = 0x00; + + if(addrtype == eIP4Address) + { + buff[3] = 0x01; + auto addr = boost::asio::ip::address::from_string(host).to_v4(); + auto addrbytes = addr.to_bytes(); + auto addrsize = addrbytes.size(); + memcpy(buff+4, addrbytes.data(), addrsize); + } + else if (addrtype == eIP6Address) + { + buff[3] = 0x04; + auto addr = boost::asio::ip::address::from_string(host).to_v6(); + auto addrbytes = addr.to_bytes(); + auto addrsize = addrbytes.size(); + memcpy(buff+4, addrbytes.data(), addrsize); + } + else if (addrtype == eHostname) + { + buff[3] = 0x03; + size_t addrsize = host.size(); + sz = addrsize + 1 + 4; + if (2 + sz > sizeof(buff)) + { + // too big + return; + } + buff[4] = (uint8_t) addrsize; + memcpy(buff+5, host.c_str(), addrsize); + } + htobe16buf(buff+sz, port); + sz += 2; + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, sz), boost::asio::transfer_all(), [=](const boost::system::error_code & ec, std::size_t written) { + if(ec) + { + LogPrint(eLogError, "NTCP: failed to write handshake to socks proxy ", ec.message()); + return; + } + }); + + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, 10), [=](const boost::system::error_code & e, std::size_t transferred) { + if(e) + { + LogPrint(eLogError, "NTCP: socks proxy read error ", e.message()); + } + else if(transferred == sz) + { + if( readbuff[1] == 0x00) + { + timer->cancel(); + conn->ClientLogin(); + return; + } + } + if(!e) + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + timer->cancel(); + conn->Terminate(); + }); + } + + void NTCPServer::ScheduleTermination () + { + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimer.async_wait (std::bind (&NTCPServer::HandleTerminationTimer, + this, std::placeholders::_1)); + } + + void NTCPServer::HandleTerminationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + // established + for (auto& it: m_NTCPSessions) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + // Terminate modifies m_NTCPSession, so we postpone it + m_Service.post ([session] { + LogPrint (eLogDebug, "NTCP: No activity for ", session->GetTerminationTimeout (), " seconds"); + session->Terminate (); + }); + } + // pending + for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) + { + if ((*it)->IsEstablished () || (*it)->IsTerminated ()) + it = m_PendingIncomingSessions.erase (it); // established or terminated + else if ((*it)->IsTerminationTimeoutExpired (ts)) + { + (*it)->Terminate (); + it = m_PendingIncomingSessions.erase (it); // expired + } + else + it++; + } + + ScheduleTermination (); + } + } +} +} diff --git a/libi2pd/NTCPSession.h b/libi2pd/NTCPSession.h new file mode 100644 index 00000000..e2207eb7 --- /dev/null +++ b/libi2pd/NTCPSession.h @@ -0,0 +1,234 @@ +#ifndef NTCP_SESSION_H__ +#define NTCP_SESSION_H__ + +#include +#include +#include +#include +#include +#include +#include "Crypto.h" +#include "Identity.h" +#include "RouterInfo.h" +#include "I2NPProtocol.h" +#include "TransportSession.h" +#include "CryptoWorker.h" + +namespace i2p +{ +namespace transport +{ + struct NTCPPhase1 + { + uint8_t pubKey[256]; + uint8_t HXxorHI[32]; + }; + + struct NTCPPhase2 + { + uint8_t pubKey[256]; + struct + { + uint8_t hxy[32]; + uint8_t timestamp[4]; + uint8_t filler[12]; + } encrypted; + }; + + struct NTCPWork; + + const size_t NTCP_MAX_MESSAGE_SIZE = 16384; + const size_t NTCP_BUFFER_SIZE = 1028; // fits 1 tunnel data message + const int NTCP_CONNECT_TIMEOUT = 5; // 5 seconds + const int NTCP_ESTABLISH_TIMEOUT = 10; // 10 seconds + const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes + const int NTCP_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 + const int NTCP_CLOCK_SKEW = 60; // in seconds + const int NTCP_MAX_OUTGOING_QUEUE_SIZE = 200; // how many messages we can queue up + + class NTCPServer; + class NTCPSession: public TransportSession, public std::enable_shared_from_this + { + public: + + NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter = nullptr); + ~NTCPSession (); + void Terminate (); + void Done (); + + boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; + boost::asio::io_service & GetService(); + bool IsEstablished () const { return m_IsEstablished; }; + bool IsTerminated () const { return m_IsTerminated; }; + + void ClientLogin (); + void ServerLogin (); + void SendI2NPMessages (const std::vector >& msgs); + + private: + + void PostI2NPMessages (std::vector > msgs); + void Connected (); + void SendTimeSyncMessage (); + void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } + + void CreateAESKey (uint8_t * pubKey); + + // client + void SendPhase3 (); + void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandlePhase2 (NTCPWork * work=nullptr); + void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA); + void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA); + + //server + void SendPhase2 (NTCPWork * work=nullptr); + void SendPhase4 (uint32_t tsA, uint32_t tsB); + void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB); + void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB); + void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen); + void HandlePhase3 (uint32_t tsB, size_t paddingLen); + void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred); + + // common + void Receive (); + void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + bool DecryptNextBlock (const uint8_t * encrypted); + + void Send (std::shared_ptr msg); + boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr msg); + void Send (const std::vector >& msgs); + void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs); + + private: + + NTCPServer& m_Server; + boost::asio::ip::tcp::socket m_Socket; + bool m_IsEstablished, m_IsTerminated; + + i2p::crypto::CBCDecryption m_Decryption; + i2p::crypto::CBCEncryption m_Encryption; + + struct Establisher + { + NTCPPhase1 phase1; + NTCPPhase2 phase2; + } * m_Establisher; + + i2p::crypto::AESAlignedBuffer m_ReceiveBuffer; + i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer; + int m_ReceiveBufferOffset; + + std::shared_ptr m_NextMessage; + size_t m_NextMessageOffset; + i2p::I2NPMessagesHandler m_Handler; + + bool m_IsSending; + std::vector > m_SendQueue; + }; + + // TODO: move to NTCP.h/.cpp + class NTCPServer + { + public: + + typedef i2p::worker::ThreadPool Pool; + + enum RemoteAddressType + { + eIP4Address, + eIP6Address, + eHostname + }; + + enum ProxyType + { + eNoProxy, + eSocksProxy, + eHTTPProxy + }; + + + NTCPServer (int workers=4); + ~NTCPServer (); + + void Start (); + void Stop (); + + bool AddNTCPSession (std::shared_ptr session); + void RemoveNTCPSession (std::shared_ptr session); + std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); + void ConnectWithProxy (const std::string& addr, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn); + void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); + + bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; }; + bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; }; + bool NetworkIsReady() const { return IsBoundV4() || IsBoundV6() || UsingProxy(); }; + bool UsingProxy() const { return m_ProxyType != eNoProxy; }; + + void UseProxy(ProxyType proxy, const std::string & address, uint16_t port); + + boost::asio::io_service& GetService () { return m_Service; }; + + void SetSessionLimits(uint16_t softLimit, uint16_t hardLimit) { m_SoftLimit = softLimit; m_HardLimit = hardLimit; } + bool ShouldLimit() const { return ShouldHardLimit() || ShouldSoftLimit(); } + void Work(std::shared_ptr conn, Pool::WorkFunc work) + { + m_CryptoPool->Offer({conn, work}); + } + private: + + /** @brief return true for hard limit */ + bool ShouldHardLimit() const { return m_HardLimit && m_NTCPSessions.size() >= m_HardLimit; } + + /** @brief return true for probabalistic soft backoff */ + bool ShouldSoftLimit() const + { + auto sessions = m_NTCPSessions.size(); + return sessions && m_SoftLimit && m_SoftLimit < sessions && ( rand() % sessions ) <= m_SoftLimit; + } + void Run (); + void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); + void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); + + 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, const std::string & host, uint16_t port, RemoteAddressType adddrtype); + void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype); + + // timer + void ScheduleTermination (); + void HandleTerminationTimer (const boost::system::error_code& ecode); + + private: + + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; + boost::asio::io_service::work m_Work; + boost::asio::deadline_timer m_TerminationTimer; + boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; + std::map > m_NTCPSessions; // access from m_Thread only + std::list > m_PendingIncomingSessions; + + ProxyType m_ProxyType; + std::string m_ProxyAddress; + uint16_t m_ProxyPort; + boost::asio::ip::tcp::resolver m_Resolver; + boost::asio::ip::tcp::endpoint * m_ProxyEndpoint; + + std::shared_ptr m_CryptoPool; + + uint16_t m_SoftLimit, m_HardLimit; + public: + + // for HTTP/I2PControl + const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; }; + }; +} +} + +#endif diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index e53738e5..02ae2ae8 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,15 +1,6 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include -#include #include #include @@ -24,10 +15,8 @@ #include "NTCP2.h" #include "RouterContext.h" #include "Garlic.h" -#include "ECIESX25519AEADRatchetSession.h" -#include "Config.h" #include "NetDb.hpp" -#include "util.h" +#include "Config.h" using namespace i2p::transport; @@ -37,9 +26,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 +44,26 @@ 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) // reseed if # of router less than threshold Reseed (); - } - else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false)) - Reseed (); // we don't have a router we can connect to. Trying to reseed - - auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); - if (it != m_RouterInfos.end ()) - { - // remove own router - m_Floodfills.Remove (it->second->GetIdentHash ()); - 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 ()); 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,121 +73,114 @@ 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; - - std::list > msgs; + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; 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 ()); + 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 eI2NPDummyMsg: + // plain RouterInfo from NTCP2 with flags for now + HandleNTCP2RouterInfoMsg (msg); + break; default: // WTF? - LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ()); + 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 - 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 (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) + { + i2p::context.CleanupDestination (); + lastDestinationCleanup = ts; } - if (mts >= lastProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance)*1000) + if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // update timestamp and publish { - 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.UpdateTimestamp (ts); + if (!m_HiddenMode) Publish (); + lastPublish = ts; } - - if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) + if (ts - lastExploratory >= 30) // exploratory every 30 seconds { - bool isDeleting = m_DeletingProfiles.valid (); - if (isDeleting && m_DeletingProfiles.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + 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) { - m_DeletingProfiles.get (); - isDeleting = false; - } - if (!isDeleting) - m_DeletingProfiles = DeleteObsoleteProfiles (); - else - LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk"); - lastObsoleteProfilesCleanup = mts; - obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; - } - if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance) - { - bool isApplying = m_ApplyingProfileUpdates.valid (); - if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? - { - 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; - } + 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) { - LogPrint (eLogError, "NetDb: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "NetDb: runtime exception: ", ex.what ()); } } } - 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 +195,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 +208,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 ()); - else if (r->IsEligibleFloodfill ()) - { - if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || r->GetProfile ()->IsReal ()) - m_Floodfills.Insert (r); - else - r->ResetFloodfill (); - } - } + m_Floodfills.remove (r); + else + 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()); - if (r->IsFloodfill () && r->IsEligibleFloodfill ()) + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); + if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable { - 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 +255,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) @@ -362,11 +273,10 @@ namespace data if(it->second->GetExpirationTime() < expires) { it->second->Update (buf, len, false); // signature is verified already - if (CheckLogLevel (eLogInfo)) - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); updated = true; } - else if (CheckLogLevel (eLogDebug)) + else LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); } else @@ -377,50 +287,40 @@ 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; } else - LogPrint (eLogError, "NetDb: New LeaseSet validation failed: ", ident.ToBase32()); + LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); } return updated; } 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 ()) { - if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) - { - // TODO: implement actual update - if (CheckLogLevel (eLogInfo)) - 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()); - m_LeaseSets.erase (ident); - } + // TODO: implement actual update + LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); + m_LeaseSets[ident] = leaseSet; + return true; } } else - LogPrint (eLogError, "NetDb: New LeaseSet2 validation failed: ", ident.ToBase32()); + LogPrint (eLogError, "NetDb: new LeaseSet2 validation failed: ", ident.ToBase32()); return false; } 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 +330,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 +349,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 +362,32 @@ namespace data m_Reseeder->LoadCertificates (); // we need certificates for SU3 verification } - m_Reseeder->Bootstrap (); + // 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; + LogPrint(eLogInfo, "NetDB: reseeding from floodfill ", ri.GetIdentHashBase64()); + std::vector > requests; i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); i2p::data::IdentHash ih = ri.GetIdentHash(); @@ -516,25 +410,24 @@ 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) + bool NetDb::LoadRouterInfo (const std::string & path) { 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 + if (r->GetRouterIdentity () && !r->IsUnreachable () && + (!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour { r->DeleteBuffer (); - if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) - { - if (r->IsFloodfill () && r->IsEligibleFloodfill ()) - m_Floodfills.Insert (r); - } + r->ClearProperties (); // properties are not used for regular routers + m_RouterInfos[r->GetIdentHash ()] = r; + if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable + m_Floodfills.push_back (r); } else { - LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid or too old. Delete"); + LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid. Delete"); i2p::fs::Remove(path); } return true; @@ -542,7 +435,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 +451,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 +463,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,181 +506,123 @@ 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(); + m_LastLoad = i2p::util::GetSecondsSinceEpoch(); std::vector files; m_Storage.Traverse(files); for (const auto& path : files) - LoadRouterInfo (path, ts); + LoadRouterInfo(path); - 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; + int updatedCount = 0, deletedCount = 0; auto total = m_RouterInfos.size (); - auto totalFloodfills = m_Floodfills.GetSize (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); - auto uptime = i2p::context.GetUptime (); - double minTunnelCreationSuccessRate; - i2p::config::GetOption("limits.zombies", minTunnelCreationSuccessRate); - bool isLowRate = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < minTunnelCreationSuccessRate; // routers don't expire if less than 90 or uptime is less than 1 hour - bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 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 && ts > (i2p::context.GetStartupTime () + 600)*1000LL; // 10 minutes + if (checkForExpiration && ts > (i2p::context.GetStartupTime () + 3600)*1000LL) // 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) + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + + for (auto& it: m_RouterInfos) { - if (!r || r == own) continue; // skip own - if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete + std::string ident = it.second->GetIdentHashBase64(); + std::string path = m_Storage.Path(ident); + 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 (path); + 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 ()) + // find & mark expired routers + if (it.second->UsesIntroducer ()) { - // 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++; // 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"); + LogPrint (eLogInfo, "NetDb: saved ", updatedCount, " new/updated routers"); if (deletedCount > 0) { - LogPrint (eLogInfo, "NetDb: Deleting ", deletedCount, " unreachable routers"); + 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) + void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) { - 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) + transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); 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,63 +641,36 @@ 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 ()) { - LogPrint (eLogDebug, "NetDb: Database store with zero ident, dropped"); + LogPrint (eLogDebug, "NetDb: database store with zero ident, dropped"); return; } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); 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; } // we must send reply back before this check if (ident == i2p::context.GetIdentHash ()) { - LogPrint (eLogDebug, "NetDb: Database store with own RouterInfo received, dropped"); + LogPrint (eLogDebug, "NetDb: database store with own RouterInfo received, dropped"); return; } size_t payloadOffset = offset; @@ -871,50 +679,37 @@ 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) + if (size > 2048 || size > len - offset) { - LogPrint (eLogError, "NetDb: Invalid RouterInfo length ", (int)size); + LogPrint (eLogError, "NetDb: invalid RouterInfo length ", (int)size); return; } - uint8_t uncompressed[MAX_RI_BUFFER_SIZE]; - size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, MAX_RI_BUFFER_SIZE); - if (uncompressedSize && uncompressedSize < MAX_RI_BUFFER_SIZE) + uint8_t uncompressed[2048]; + size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048); + if (uncompressedSize && uncompressedSize < 2048) updated = AddRouterInfo (ident, uncompressed, uncompressedSize); else { - LogPrint (eLogInfo, "NetDb: Decompression failed ", uncompressedSize); + LogPrint (eLogInfo, "NetDb: decompression failed ", uncompressedSize); return; } } @@ -932,16 +727,105 @@ 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) + { + std::vector msgs; + auto count = dest->GetExcludedPeers ().size (); + if (count < 7) + { + auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); + if (nextFloodfill) + { + // tell floodfill about us + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeRouter, + nextFloodfill->GetIdentHash (), 0, + CreateDatabaseStoreMsg () + }); + + // request destination + LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); + auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeRouter, + nextFloodfill->GetIdentHash (), 0, msg + }); + deleteDest = false; + } + } + else + LogPrint (eLogWarning, "NetDb: ", key, " was not found on ", count, " floodfills"); + + if (msgs.size () > 0) + outbound->SendTunnelDataMsg (msgs); + } + } + + 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 +835,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,85 +854,79 @@ 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; + LogPrint (eLogInfo, "NetDb: exploratory close to ", key, " ", numExcluded, " 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) + 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)) + LogPrint (eLogDebug, "NetDb: requested RouterInfo ", key, " found"); + router->LoadBuffer (); + if (router->GetBuffer ()) replyMsg = CreateDatabaseStoreMsg (router); } } - if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || + 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 (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) @@ -1055,91 +934,118 @@ namespace data if (replyTunnelID) { // encryption might be used though tunnel only - if (flag & (DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_ECIES_FLAG)) // encrypted reply requested + if (flag & DATABASE_LOOKUP_ENCRYPTION_FLAG) // encrypted reply requested { const uint8_t * sessionKey = excluded; const uint8_t numTags = excluded[32]; if (numTags) { - if (flag & DATABASE_LOOKUP_ECIES_FLAG) - { - uint64_t tag; - memcpy (&tag, excluded + 33, 8); - replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); - } - else - { - const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag - i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); - replyMsg = garlic.WrapSingleMessage (replyMsg); - } - if (!replyMsg) - LogPrint (eLogError, "NetDb: Failed to wrap message"); + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag + i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); + replyMsg = garlic.WrapSingleMessage (replyMsg); + if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); } else - LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); + 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::Explore (int numDestinations) { - std::unordered_set excluded; + // 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 + std::set excluded; // TODO: fill up later + for (int i = 0; i < 2; i++) + { + auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded); + 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); + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); + excluded.insert (floodfill->GetIdentHash ()); + } + } + } + + 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 +1057,42 @@ 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) 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](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) + router->IsCompatible (*compatibleWith); }); } - std::shared_ptr NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set& excluded) const + std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4only) const { return GetRandomRouter ( - [v4, &excluded](std::shared_ptr router)->bool + [v4only](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsECIES () && - router->IsSSU2PeerTesting (v4) && !excluded.count (router->GetIdentHash ()); + return !router->IsHidden () && router->IsPeerTesting () && router->IsSSU (v4only); }); } - std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::unordered_set& excluded) const + std::shared_ptr NetDb::GetRandomIntroducer () const { return GetRandomRouter ( - [v4, &excluded](std::shared_ptr router)->bool + [](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsSSU2Introducer (v4) && - !excluded.count (router->GetIdentHash ()); + return !router->IsHidden () && router->IsIntroducer (); }); } - std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, - bool reverse, bool endpoint) const + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) 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](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)) : - router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) && - (router->GetCaps () & RouterInfo::eHighBandwidth) && - router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && - router->IsECIES () && !router->IsHighCongestion (true) && - (!checkIsReal || router->GetProfile ()->IsReal ()) && - (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) - + router->IsCompatible (*compatibleWith) && + (router->GetCaps () & RouterInfo::eHighBandwidth); }); } @@ -1212,58 +1100,24 @@ namespace data std::shared_ptr NetDb::GetRandomRouter (Filter filter) const { if (m_RouterInfos.empty()) - return nullptr; - 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; - auto it = m_RouterInfos.begin (); - std::advance (it, inds[0]); - // try random router - if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) - return it->second; - // try some routers around - auto it1 = m_RouterInfos.begin (); - if (inds[0]) + return 0; + uint32_t ind = rand () % m_RouterInfos.size (); + for (int j = 0; j < 2; j++) { - // before - inds[1] %= inds[0]; - std::advance (it1, (inds[1] + inds[0])/2); - } - else - it1 = it; - auto it2 = it; - if (inds[0] < m_RouterInfos.size () - 1) - { - // after - inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); inds[2] /= 2; - std::advance (it2, inds[2]); - } - // it1 - from, it2 - to - it = it1; - while (it != it2 && it != m_RouterInfos.end ()) - { - if (!it->second->IsUnreachable () && filter (it->second)) - return it->second; - it++; - } - // still not found, try from the beginning - it = m_RouterInfos.begin (); - while (it != it1 && it != m_RouterInfos.end ()) - { - if (!it->second->IsUnreachable () && filter (it->second)) - return it->second; - it++; - } - // still not found, try to the beginning - it = it2; - while (it != m_RouterInfos.end ()) - { - if (!it->second->IsUnreachable () && filter (it->second)) - return it->second; - it++; + uint32_t i = 0; + std::unique_lock l(m_RouterInfosMutex); + for (const auto& it: m_RouterInfos) + { + if (i >= ind) + { + if (!it.second->IsUnreachable () && filter (it.second)) + return it.second; + } + else + i++; + } + // we couldn't find anything, try second pass + ind = 0; } return nullptr; // seems we have too few routers } @@ -1273,52 +1127,85 @@ 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; } - std::shared_ptr NetDb::GetRandomRouterInFamily (FamilyID fam) const - { + std::shared_ptr NetDb::GetRandomRouterInFamily(const std::string & fam) const { return GetRandomRouter( [fam](std::shared_ptr router)->bool { @@ -1326,61 +1213,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 +1249,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 f2a7019b..93e9e48f 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,22 +1,13 @@ -/* -* 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 NETDB_H__ #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 #include "Base.h" #include "Gzip.h" @@ -30,35 +21,17 @@ #include "Reseed.h" #include "NetDbRequests.h" #include "Family.h" -#include "version.h" -#include "util.h" -#include "KadDHT.h" namespace i2p { namespace data { const int NETDB_MIN_ROUTERS = 90; - const int NETDB_MIN_FLOODFILLS = 5; - const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline - const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1200; - const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD; - const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in % - const int NETDB_CHECK_FOR_EXPIRATION_UPTIME = 600; // 10 minutes, in seconds - const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds - const int NETDB_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_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 + 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_PUBLISH_INTERVAL = 60*40; /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; @@ -79,7 +52,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); @@ -87,30 +60,37 @@ namespace data std::shared_ptr FindLeaseSet (const IdentHash& destination) const; std::shared_ptr FindRouterProfile (const IdentHash& ident) const; - void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true); - + void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); + 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); + 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) const; + std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; + std::shared_ptr GetRandomPeerTestRouter (bool v4only = true) const; + std::shared_ptr GetRandomIntroducer () 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::shared_ptr GetRandomRouterInFamily (FamilyID fam) const; + std::set& excluded, bool closeThanUsOnly = false) const; + std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; + std::shared_ptr GetRandomRouterInFamily(const std::string & 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,59 +103,37 @@ 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 (); }; private: void Load (); - bool LoadRouterInfo (const std::string& path, uint64_t ts); + bool LoadRouterInfo (const std::string & path); 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 (); - void ReseedFromFloodfill(const RouterInfo & ri, int numRouters = 40, int numFloodfills = 20); + void ReseedFromFloodfill(const RouterInfo & ri, int numRouters=40, int numFloodfills=20); std::shared_ptr AddRouterInfo (const uint8_t * buf, int len, bool& updated); std::shared_ptr AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len, bool& updated); - - 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); + template + std::shared_ptr GetRandomRouter (Filter filter) const; private: mutable std::mutex m_LeaseSetsMutex; - std::unordered_map > m_LeaseSets; + std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; - std::unordered_map > m_RouterInfos; + std::map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; - DHTTable m_Floodfills; + std::list > m_Floodfills; bool m_IsRunning; + uint64_t m_LastLoad; std::thread * m_Thread; i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg @@ -184,21 +142,17 @@ 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; - 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; + + /** true if in hidden mode */ + bool m_HiddenMode; }; extern NetDb netdb; diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index 94633e10..f1853124 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,54 +1,26 @@ -/* -* 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 "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) { std::shared_ptr msg; if(replyTunnel) msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, - replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, - &m_ExcludedPeers); + replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, + &m_ExcludedPeers); else 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 +29,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 +111,51 @@ 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..7a7a55ab 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -1,131 +1,69 @@ -/* -* 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 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; }; } } #endif + diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp new file mode 100644 index 00000000..bf94c9a9 --- /dev/null +++ b/libi2pd/Poly1305.cpp @@ -0,0 +1,25 @@ +#include "Poly1305.h" +/** + This code is licensed under the MCGSI Public License + Copyright 2018 Jeff Becker + + Kovri go write your own code + + */ + +#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..800fbfd9 --- /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..3840eb32 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,48 +1,32 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include -#include -#include -#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 +39,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,14 +59,13 @@ 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)) { - LogPrint(eLogWarning, "Profiling: No profile yet for ", ident); + LogPrint(eLogWarning, "Profiling: no profile yet for ", ident); return; } @@ -103,24 +81,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,12 +104,10 @@ 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) { - LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); + LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); } } else @@ -160,44 +123,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 +145,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 +160,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) - { - LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); + 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..4ba6702f 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,18 +1,8 @@ -/* -* 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 PROFILING_H__ #define PROFILING_H__ #include -#include -#include -#include +#include #include "Identity.h" namespace i2p @@ -23,81 +13,42 @@ 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) + 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 +56,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..0438cc37 100644 --- a/libi2pd/Queue.h +++ b/libi2pd/Queue.h @@ -1,15 +1,8 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef QUEUE_H__ #define QUEUE_H__ -#include +#include +#include #include #include #include @@ -27,21 +20,23 @@ namespace util void Put (Element e) { - std::unique_lock l(m_QueueMutex); - m_Queue.push_back (std::move(e)); + std::unique_lock l(m_QueueMutex); + 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); + std::unique_lock l(m_QueueMutex); + 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 +79,7 @@ namespace util return m_Queue.empty (); } - int GetSize () const + int GetSize () { std::unique_lock l(m_QueueMutex); return m_Queue.size (); @@ -104,28 +99,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 +115,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 e58e898b..98f95dc3 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -26,7 +18,6 @@ #include "HTTP.h" #include "util.h" #include "Config.h" -#include "Socks5.h" namespace i2p { @@ -41,100 +32,77 @@ namespace data { } - /** - @brief tries to bootstrap into I2P network (from local files and servers, with respect of options) - */ - void Reseeder::Bootstrap () - { - std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); - std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); + /** @brief tries to bootstrap into I2P network (from local files and servers, with respect of options) + */ + void Reseeder::Bootstrap () + { + std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); + std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); - if (su3FileName.length() > 0) // bootstrap from SU3 file or URL - { - int num; - if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") - { - num = ReseedFromSU3Url (su3FileName); // from https URL - } - else - { - num = ProcessSU3File (su3FileName.c_str ()); - } - if (num == 0) - LogPrint (eLogWarning, "Reseed: Failed to reseed from ", su3FileName); - } - else if (zipFileName.length() > 0) // bootstrap from ZIP file - { - int num = ProcessZIPFile (zipFileName.c_str ()); - if (num == 0) - LogPrint (eLogWarning, "Reseed: Failed to reseed from ", zipFileName); - } - else // bootstrap from reseed servers - { - int num = ReseedFromServers (); - if (num == 0) - LogPrint (eLogWarning, "Reseed: Failed to reseed from servers"); - } - } + if (su3FileName.length() > 0) // bootstrap from SU3 file or URL + { + int num; + if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") + { + num = ReseedFromSU3Url (su3FileName); // from https URL + } + else + { + num = ProcessSU3File (su3FileName.c_str ()); + } + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from ", su3FileName); + } + else if (zipFileName.length() > 0) // bootstrap from ZIP file + { + int num = ProcessZIPFile (zipFileName.c_str ()); + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from ", zipFileName); + } + else // bootstrap from reseed servers + { + int num = ReseedFromServers (); + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from servers"); + } + } - /** - * @brief bootstrap from random server, retry 10 times - * @return number of entries added to netDb - */ + /** @brief bootstrap from random server, retry 10 times + * @return number of entries added to netDb + */ int Reseeder::ReseedFromServers () { - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool yggdrasil; i2p::config::GetOption("meshnets.yggdrasil", yggdrasil); + std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); + std::vector httpsReseedHostList; + boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); - std::vector httpsReseedHostList; - if (ipv4 || ipv6) - { - std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); - if (!reseedURLs.empty ()) - boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); - } + if (reseedURLs.length () == 0) + { + LogPrint (eLogWarning, "Reseed: No reseed servers specified"); + return 0; + } - std::vector yggReseedHostList; - if (yggdrasil && !i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) - { - LogPrint (eLogInfo, "Reseed: Yggdrasil is supported"); - std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); - if (!yggReseedURLs.empty ()) - boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); - } - - if (httpsReseedHostList.empty () && yggReseedHostList.empty()) - { - LogPrint (eLogWarning, "Reseed: No reseed servers specified"); - return 0; - } - - int reseedRetries = 0; - while (reseedRetries < 10) - { - auto ind = rand () % (httpsReseedHostList.size () + yggReseedHostList.size ()); - bool isHttps = ind < httpsReseedHostList.size (); - std::string reseedUrl = isHttps ? httpsReseedHostList[ind] : - yggReseedHostList[ind - httpsReseedHostList.size ()]; - reseedUrl += "i2pseeds.su3"; - auto num = ReseedFromSU3Url (reseedUrl, isHttps); - if (num > 0) return num; // success - reseedRetries++; - } - LogPrint (eLogWarning, "Reseed: Failed to reseed from servers after 10 attempts"); - return 0; + int reseedRetries = 0; + while (reseedRetries < 10) + { + auto ind = rand () % httpsReseedHostList.size (); + std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3"; + auto num = ReseedFromSU3Url (reseedUrl); + if (num > 0) return num; // success + reseedRetries++; + } + LogPrint (eLogWarning, "Reseed: failed to reseed from servers after 10 attempts"); + return 0; } - /** - * @brief bootstrap from HTTPS URL with SU3 file - * @param url - * @return number of entries added to netDb - */ - int Reseeder::ReseedFromSU3Url (const std::string& url, bool isHttps) + /** @brief bootstrap from HTTPS URL with SU3 file + * @param url + * @return number of entries added to netDb + */ + int Reseeder::ReseedFromSU3Url (const std::string& url) { LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); - std::string su3 = isHttps ? HttpsRequest (url) : YggdrasilRequest (url); + std::string su3 = HttpsRequest (url); if (su3.length () > 0) { std::stringstream s(su3); @@ -154,7 +122,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; } } @@ -171,7 +139,7 @@ namespace data } else { - LogPrint (eLogCritical, "Reseed: Can't open file ", filename); + LogPrint (eLogError, "Reseed: Can't open file ", filename); return 0; } } @@ -188,31 +156,31 @@ namespace data } s.seekg (1, std::ios::cur); // su3 file format version SigningKeyType signatureType; - s.read ((char *)&signatureType, 2); // signature type + s.read ((char *)&signatureType, 2); // signature type signatureType = be16toh (signatureType); uint16_t signatureLength; - s.read ((char *)&signatureLength, 2); // signature length + s.read ((char *)&signatureLength, 2); // signature length signatureLength = be16toh (signatureLength); s.seekg (1, std::ios::cur); // unused uint8_t versionLength; - s.read ((char *)&versionLength, 1); // version length + s.read ((char *)&versionLength, 1); // version length s.seekg (1, std::ios::cur); // unused uint8_t signerIDLength; - s.read ((char *)&signerIDLength, 1); // signer ID length + s.read ((char *)&signerIDLength, 1); // signer ID length uint64_t contentLength; - s.read ((char *)&contentLength, 8); // content length + s.read ((char *)&contentLength, 8); // content length contentLength = be64toh (contentLength); s.seekg (1, std::ios::cur); // unused uint8_t fileType; - s.read ((char *)&fileType, 1); // file type - if (fileType != 0x00) // zip file + s.read ((char *)&fileType, 1); // file type + if (fileType != 0x00) // zip file { LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType); return 0; } s.seekg (1, std::ios::cur); // unused uint8_t contentType; - s.read ((char *)&contentType, 1); // content type + s.read ((char *)&contentType, 1); // content type if (contentType != 0x03) // reseed data { LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType); @@ -279,7 +247,7 @@ namespace data if (verify) // not verified { - LogPrint (eLogCritical, "Reseed: SU3 verification failed"); + LogPrint (eLogError, "Reseed: SU3 verification failed"); return 0; } @@ -321,7 +289,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; @@ -416,13 +384,13 @@ namespace data { if (r && ts > r->GetTimestamp () + 10*i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT*1000LL) // 270 hours { - LogPrint (eLogError, "Reseed: Router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); + LogPrint (eLogError, "Reseed: router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); numOutdated++; } }); if (numOutdated > numFiles/2) // more than half { - LogPrint (eLogError, "Reseed: Mammoth's shit\n" + LogPrint (eLogError, "Reseed: mammoth's shit\n" " *_____*\n" " *_*****_*\n" " *_(O)_(O)_*\n" @@ -480,7 +448,7 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); + RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); const BIGNUM * n, * e, * d; RSA_get0_key(key, &n, &e, &d); PublicKey value; @@ -493,14 +461,13 @@ namespace data 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); } void Reseeder::LoadCertificates () { - std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; - + std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); std::vector files; int numCertificates = 0; @@ -511,7 +478,7 @@ namespace data for (const std::string & file : files) { if (file.compare(file.size() - 4, 4, ".crt") != 0) { - LogPrint(eLogWarning, "Reseed: Ignoring file ", file); + LogPrint(eLogWarning, "Reseed: ignoring file ", file); continue; } LoadCertificate (file); @@ -535,24 +502,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); @@ -562,10 +529,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(); @@ -576,11 +544,9 @@ namespace data proxyReq.method = "CONNECT"; proxyReq.version = "HTTP/1.1"; proxyReq.uri = url.host + ":" + std::to_string(url.port); - auto auth = i2p::http::CreateBasicAuthorizationString (proxyUrl.user, proxyUrl.pass); - if (!auth.empty ()) - proxyReq.AddHeader("Proxy-Authorization", auth); boost::asio::streambuf writebuf, readbuf; + std::ostream out(&writebuf); out << proxyReq.to_string(); @@ -598,7 +564,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"); @@ -615,21 +581,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 ""; + } } } } @@ -637,39 +644,10 @@ namespace data else { // direct connection - auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); - if (!ecode) - { - bool connected = false; - for (const auto& it: endpoints) - { - boost::asio::ip::tcp::endpoint ep = it; - bool supported = false; - if (!ep.address ().is_unspecified ()) - { - if (ep.address ().is_v4 ()) - supported = i2p::context.SupportsV4 (); - else if (ep.address ().is_v6 ()) - supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ? - i2p::context.SupportsMesh () : i2p::context.SupportsV6 (); - } - if (supported) - { - s.lowest_layer().connect (ep, ecode); - if (!ecode) - { - LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); - connected = true; - break; - } - } - } - if (!connected) - { - LogPrint(eLogError, "Reseed: Failed to connect to ", url.host); - return ""; - } - } + 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) + s.lowest_layer().connect (*it, ecode); } if (!ecode) { @@ -678,7 +656,42 @@ namespace data if (!ecode) { LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); - return ReseedRequest (s, url.to_string()); + i2p::http::HTTPReq req; + req.uri = url.to_string(); + req.AddHeader("User-Agent", "Wget/1.11.4"); + req.AddHeader("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); + // read response + std::stringstream rs; + char recv_buf[1024]; size_t l = 0; + do { + l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); + if (l) rs.write (recv_buf, l); + } while (!ecode && l); + // process response + std::string data = rs.str(); + i2p::http::HTTPRes res; + int len = res.parse(data); + if (len <= 0) { + LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host); + return ""; + } + if (res.code != 200) { + LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code); + return ""; + } + data.erase(0, len); /* drop http headers from response */ + LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host); + if (res.is_chunked()) { + std::stringstream in(data), out; + if (!i2p::http::MergeChunkedResponse(in, out)) { + LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host); + return ""; + } + LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host); + data = out.str(); + } + return data; } else LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); @@ -687,101 +700,6 @@ namespace data LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); return ""; } - - template - std::string Reseeder::ReseedRequest (Stream& s, const std::string& uri) - { - boost::system::error_code ecode; - i2p::http::HTTPReq req; - req.uri = uri; - req.AddHeader("User-Agent", "Wget/1.11.4"); - req.AddHeader("Connection", "close"); - s.write_some (boost::asio::buffer (req.to_string())); - // read response - std::stringstream rs; - char recv_buf[1024]; size_t l = 0; - do { - l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); - if (l) rs.write (recv_buf, l); - } while (!ecode && l); - // process response - std::string data = rs.str(); - i2p::http::HTTPRes res; - int len = res.parse(data); - if (len <= 0) { - LogPrint(eLogWarning, "Reseed: Incomplete/broken response from ", uri); - return ""; - } - if (res.code != 200) { - LogPrint(eLogError, "Reseed: Failed to reseed from ", uri, ", http code ", res.code); - return ""; - } - data.erase(0, len); /* drop http headers from response */ - LogPrint(eLogDebug, "Reseed: Got ", data.length(), " bytes of data from ", uri); - if (res.is_chunked()) { - std::stringstream in(data), out; - if (!i2p::http::MergeChunkedResponse(in, out)) { - LogPrint(eLogWarning, "Reseed: Failed to merge chunked response from ", uri); - return ""; - } - LogPrint(eLogDebug, "Reseed: Got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri); - data = out.str(); - } - return data; - } - - std::string Reseeder::YggdrasilRequest (const std::string& address) - { - i2p::http::URL url; - if (!url.parse(address)) - { - LogPrint(eLogError, "Reseed: Failed to parse url: ", address); - return ""; - } - url.schema = "http"; - if (!url.port) url.port = 80; - - boost::system::error_code ecode; - boost::asio::io_context 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 (!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); - return ReseedRequest (s, url.to_string()); - } - else - LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); - - return ""; - } } } + diff --git a/libi2pd/Reseed.h b/libi2pd/Reseed.h index a6de6fa4..a69969bf 100644 --- a/libi2pd/Reseed.h +++ b/libi2pd/Reseed.h @@ -1,11 +1,3 @@ -/* -* 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 RESEED_H #define RESEED_H @@ -29,8 +21,9 @@ namespace data Reseeder(); ~Reseeder(); - void Bootstrap (); + void Bootstrap (); int ReseedFromServers (); + int ReseedFromSU3Url (const std::string& url); int ProcessSU3File (const char * filename); int ProcessZIPFile (const char * filename); @@ -38,7 +31,6 @@ namespace data private: - int ReseedFromSU3Url (const std::string& url, bool isHttps = true); void LoadCertificate (const std::string& filename); int ProcessSU3Stream (std::istream& s); @@ -47,9 +39,6 @@ namespace data bool FindZipDataDescriptor (std::istream& s); std::string HttpsRequest (const std::string& address); - std::string YggdrasilRequest (const std::string& address); - template - std::string ReseedRequest (Stream& s, const std::string& uri); private: diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 33fb5487..4a229546 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include "Config.h" @@ -19,10 +11,6 @@ #include "version.h" #include "Log.h" #include "Family.h" -#include "ECIESX25519AEADRatchetSession.h" -#include "Transports.h" -#include "Tunnel.h" -#include "CryptoKey.h" #include "RouterContext.h" namespace i2p @@ -31,352 +19,135 @@ 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_StartupTime (0), m_ShareRatio (100), m_Status (eRouterStatusOK), + 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 = i2p::util::GetSecondsSinceEpoch (); if (!Load ()) CreateNewRouter (); m_Decryptor = m_Keys.CreateDecryptor (nullptr); - m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); - i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); - 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, - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); SaveKeys (); NewRouterInfo (); } void RouterContext::NewRouterInfo () { - i2p::data::LocalRouterInfo routerInfo; + i2p::data::RouterInfo routerInfo; routerInfo.SetRouterIdentity (GetIdentity ()); uint16_t port; i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - bool nat; i2p::config::GetOption("nat", nat); - - if ((ntcp2 || ygg) && !m_NTCP2Keys) - NewNTCP2Keys (); - if (ssu2 && !m_SSU2Keys) - NewSSU2Keys (); - bool ntcp2Published = false; - if (ntcp2) + if (!port) { - i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published) - { - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - if (!ntcp2proxy.empty ()) ntcp2Published = false; - } + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser } - bool ssu2Published = false; - if (ssu2) - i2p::config::GetOption("ssu2.published", ssu2Published); - uint8_t caps = 0; + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ssu; i2p::config::GetOption("ssu", ssu); + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool nat; i2p::config::GetOption("nat", nat); + std::string ifname; i2p::config::GetOption("ifname", ifname); + std::string ifname4; i2p::config::GetOption("ifname4", ifname4); + std::string ifname6; i2p::config::GetOption("ifname6", ifname6); if (ipv4) { - std::string host; - 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 host = "127.0.0.1"; + if (!i2p::config::IsDefault("host")) + i2p::config::GetOption("host", host); + else if (!nat && !ifname.empty()) + /* bind to interface, we have no NAT so set external address too */ + host = i2p::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4 - if (ntcp2) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - if (ntcp2Published && ntcp2Port) - { - boost::asio::ip::address addr; - if (!host.empty ()) - addr = boost::asio::ip::make_address (host); - if (!addr.is_v4()) - addr = boost::asio::ip::address_v4 (); - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - } - else - { - // add non-published NTCP2 address - uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; - if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, addressCaps); - } - } - if (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); - } - 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); - } - } + if(ifname4.size()) + host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string(); + + if (ssu) + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + if (ntcp) + routerInfo.AddNTCPAddress (host.c_str(), port); } 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 if (!ifname.empty()) + host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6 - if (ntcp2) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - if (ntcp2Published && ntcp2Port) - { - std::string ntcp2Host; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); - else - ntcp2Host = host; - boost::asio::ip::address addr; - if (!ntcp2Host.empty ()) - addr = boost::asio::ip::make_address (ntcp2Host); - if (!addr.is_v6()) - addr = boost::asio::ip::address_v6 (); - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - } - else - { - if (!ipv4) // no other ntcp2 addresses yet - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::AddressCaps::eV6); - } - } - 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); - } - else - { - if (!ipv4) // no other ssu2 addresses yet - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::AddressCaps::eV6); - } - } - } - if (ygg) - { - auto yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (!yggaddr.is_unspecified ()) - routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); + if(ifname6.size()) + host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string(); + + if (ssu) + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + if (ntcp) + routerInfo.AddNTCPAddress (host.c_str(), port); } - routerInfo.UpdateCaps (caps); // caps + L + routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | + i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC 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 + if (ntcp2) // we don't store iv in the address if non published so we must update it from keys { - port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (!m_NTCP2Keys) NewNTCP2Keys (); + UpdateNTCP2Address (true); + if (!ntcp) // NTCP2 should replace NTCP + { + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) + PublishNTCP2Address (port, true); + } } - 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 (); } void RouterContext::NewNTCP2Keys () { - m_NTCP2StaticKeys.reset (new i2p::crypto::X25519Keys ()); - m_NTCP2StaticKeys->GenerateKeys (); + m_StaticKeys.reset (new i2p::crypto::X25519Keys ()); + m_StaticKeys->GenerateKeys (); m_NTCP2Keys.reset (new NTCP2PrivateKeys ()); - m_NTCP2StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey); - memcpy (m_NTCP2Keys->staticPublicKey, m_NTCP2StaticKeys->GetPublicKey (), 32); + m_StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey); + memcpy (m_NTCP2Keys->staticPublicKey, m_StaticKeys->GetPublicKey (), 32); RAND_bytes (m_NTCP2Keys->iv, 16); // save std::ofstream fk (i2p::fs::DataDirPath (NTCP2_KEYS), std::ofstream::binary | std::ofstream::out); fk.write ((char *)m_NTCP2Keys.get (), sizeof (NTCP2PrivateKeys)); } - void RouterContext::NewSSU2Keys () - { - m_SSU2StaticKeys.reset (new i2p::crypto::X25519Keys ()); - m_SSU2StaticKeys->GenerateKeys (); - m_SSU2Keys.reset (new SSU2PrivateKeys ()); - m_SSU2StaticKeys->GetPrivateKey (m_SSU2Keys->staticPrivateKey); - memcpy (m_SSU2Keys->staticPublicKey, m_SSU2StaticKeys->GetPublicKey (), 32); - RAND_bytes (m_SSU2Keys->intro, 32); - // save - std::ofstream fk (i2p::fs::DataDirPath (SSU2_KEYS), std::ofstream::binary | std::ofstream::out); - 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: - SetReachable (true, false); // ipv4 + SetReachable (); break; case eRouterStatusFirewalled: - SetUnreachable (true, false); // ipv4 - break; - case eRouterStatusMesh: - m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable); - break; - case eRouterStatusProxy: - m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable); - break; - default: - ; - } - } - } - - void RouterContext::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) - { - case eRouterStatusOK: - SetReachable (false, true); // ipv6 - break; - case eRouterStatusFirewalled: - SetUnreachable (false, true); // ipv6 + SetUnreachable (); break; default: ; @@ -386,12 +157,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->port != port) { address->port = port; updated = true; @@ -401,98 +170,23 @@ 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) + void RouterContext::PublishNTCP2Address (int port, bool publish) { if (!m_NTCP2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - bool updated = false; - if (v4) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; - if (addr && (addr->port != port || addr->published != publish)) - { - PublishNTCP2Address (addr, port, publish); - updated = true; - } - } - if (v6) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && (addr->port != port || addr->published != publish)) - { - PublishNTCP2Address (addr, port, publish); - updated = true; - } - } - if (ygg) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; - if (addr && (addr->port != port || addr->published != publish)) - { - PublishNTCP2Address (addr, port, publish); - updated = true; - } - } - - if (updated) - UpdateRouterInfo (); - } - - void RouterContext::UpdateNTCP2Keys () - { - if (!m_NTCP2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& it: *addresses) - { - if (it && it->IsNTCP2 ()) - { - it->s = m_NTCP2Keys->staticPublicKey; - memcpy (it->i, m_NTCP2Keys->iv, 16); - } - } - } - - 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 (); + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser } 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) && - ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) + if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish)) { - if (port) address->port = port; - else if (!address->port) address->port = newPort; - address->published = publish; - if (publish) - address->caps |= (i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); - else - address->caps &= ~(i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); + address->port = port; + address->cost = publish ? 3 : 14; + address->ntcp2->isPublished = publish; + address->ntcp2->iv = m_NTCP2Keys->iv; updated = true; } } @@ -500,116 +194,70 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::UpdateSSU2Keys () + void RouterContext::UpdateNTCP2Address (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)->IsNTCP2 ()) { - it->s = m_SSU2Keys->staticPublicKey; - it->i = m_SSU2Keys->intro; + 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::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)) { - addr->host = host; - updated = true; - } - addr = (*addresses)[i2p::data::RouterInfo::eSSU2V4Idx]; - if (addr && addr->host != host) - { - addr->host = host; + address->host = host; updated = true; } } - else if (host.is_v6 ()) - { - auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && addr->host != host) - { - addr->host = host; - updated = true; - } - addr = (*addresses)[i2p::data::RouterInfo::eSSU2V6Idx]; - if (addr && (addr->host != host || !addr->ssu->mtu)) - { - addr->host = host; - if (m_StatusV6 != eRouterStatusProxy) - { - // update MTU - auto mtu = i2p::util::net::GetMTU (host); - if (mtu) - { - LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); - if (mtu > maxMTU) - { - mtu = maxMTU; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); - } - addr->ssu->mtu = mtu; - } - } - updated = true; - } - } - auto ts = i2p::util::GetSecondsSinceEpoch (); if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) UpdateRouterInfo (); } - bool RouterContext::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.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); else { - m_RouterInfo.UpdateFloodfillProperty (false); + m_RouterInfo.SetCaps (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 +294,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 (); @@ -664,26 +312,23 @@ namespace i2p { case low : /* not set */; break; case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P' - case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; - [[fallthrough]]; - // no break here, extra + high means 'X' - case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; + case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; // no break here, extra + high means 'X' + case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } - m_RouterInfo.UpdateCaps (caps); + m_RouterInfo.SetCaps (caps); UpdateRouterInfo (); m_BandwidthLimit = limit; } 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'); } - m_BandwidthLimit = limit; // set precise limit } void RouterContext::SetShareRatio (int percents) @@ -698,183 +343,98 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } - void RouterContext::SetUnreachable (bool v4, bool v6) + void RouterContext::PublishNTCPAddress (bool publish, bool v4only) { - if (v4 || (v6 && !SupportsV4 ())) + auto& addresses = m_RouterInfo.GetAddresses (); + if (publish) { - // set caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eReachable; - caps |= i2p::data::RouterInfo::eUnreachable; - if (v6 || !SupportsV6 ()) - caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill - m_RouterInfo.UpdateCaps (caps); - } - 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 ()))) + for (const auto& addr : addresses) // v4 + { + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && + addr->host.is_v4 ()) { - addr->published = false; - addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer - addr->ssu->introducers.clear (); - port = addr->port; + // insert NTCP address with host/port from SSU + m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); + break; } + } + if (!v4only) + { + for (const auto& addr : addresses) // v6 + { + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && + addr->host.is_v6 ()) + { + // insert NTCP address with host/port from SSU + m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); + break; + } + } + } } - // unpublish NTCP2 addreeses - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - PublishNTCP2Address (port, false, v4, v6, false); + else + { + 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 () + { + // set caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eReachable; + caps |= i2p::data::RouterInfo::eUnreachable; + caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill + caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer + m_RouterInfo.SetCaps (caps); + // remove NTCP v4 address + PublishNTCPAddress (false); + // delete previous introducers + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr : addresses) + if (addr->ssu) + addr->ssu->introducers.clear (); // update - m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } - void RouterContext::SetReachable (bool v4, bool v6) + void RouterContext::SetReachable () { - if (v4 || (v6 && !SupportsV4 ())) - { - // update caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eUnreachable; - caps |= i2p::data::RouterInfo::eReachable; - if (m_IsFloodfill) - caps |= i2p::data::RouterInfo::eFloodfill; - m_RouterInfo.UpdateCaps (caps); - } - uint16_t port = 0; + // update caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eUnreachable; + caps |= i2p::data::RouterInfo::eReachable; + caps |= i2p::data::RouterInfo::eSSUIntroducer; + if (m_IsFloodfill) + caps |= i2p::data::RouterInfo::eFloodfill; + m_RouterInfo.SetCaps (caps); + // insert NTCP back + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + if (ntcp) + PublishNTCPAddress (true); // 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; - } - } - // publish NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - bool published; i2p::config::GetOption ("ntcp2.published", published); - if (published) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - PublishNTCP2Address (ntcp2Port, true, v4, v6, false); - } - } + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr : addresses) + if (addr->ssu) + addr->ssu->introducers.clear (); // update - m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } void RouterContext::SetSupportsV6 (bool supportsV6) { if (supportsV6) - { - // insert v6 addresses if necessary - bool foundNTCP2 = false, foundSSU2 = false; - uint16_t port = 0; - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { - for (auto& addr: *addresses) - { - if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) - { - switch (addr->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; - } - } - if (addr) port = addr->port; - } - } - if (!port) - { - i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); - } - // NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - if (!foundNTCP2) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - bool added = false; - bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published) - { - 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; - } - } - } - if (!added) - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV6); - } - } - else - m_RouterInfo.RemoveNTCP2Address (false); - // SSU2 - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) - { - if (!foundSSU2) - { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - bool added = false; - bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - if (ssu2Published && ssu2Port) - { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v6 ()) - { - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - added = true; - } - } - } - if (!added) - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV6); - } - } - else - m_RouterInfo.RemoveSSU2Address (false); - if (ntcp2 || ssu2) - m_RouterInfo.EnableV6 (); - } + m_RouterInfo.EnableV6 (); else m_RouterInfo.DisableV6 (); UpdateRouterInfo (); @@ -883,161 +443,83 @@ namespace i2p void RouterContext::SetSupportsV4 (bool supportsV4) { if (supportsV4) - { - bool foundNTCP2 = false, foundSSU2 = false; - uint16_t port = 0; - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { - for (auto& addr: *addresses) - { - if (addr && addr->IsV4 ()) - { - switch (addr->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - foundNTCP2 = true; - break; - case i2p::data::RouterInfo::eTransportSSU2: - foundSSU2 = true; - break; - default: ; - } - } - if (addr && addr->port) port = addr->port; - } - } - if (!port) - { - i2p::config::GetOption("port", port); - if (!port) port = SelectRandomPort (); - } - // NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - if (!foundNTCP2) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - bool added = false; - bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published && ntcp2Port) - { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v4 ()) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); - added = true; - } - } - } - if (!added) - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV4); - } - } - else - m_RouterInfo.RemoveNTCP2Address (true); - // SSU2 - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) - { - if (!foundSSU2) - { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (!ssu2Port) ssu2Port = port; - bool added = false; - bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); - std::string host; i2p::config::GetOption("host", host); - if (ssu2Published && ssu2Port) - { - std::string host; i2p::config::GetOption("host", host); - if (!host.empty ()) - { - auto addr = boost::asio::ip::make_address (host); - if (addr.is_v4 ()) - { - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); - added = true; - } - } - } - if (!added) - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV4); - } - } - else - m_RouterInfo.RemoveSSU2Address (true); - if (ntcp2 || ssu2) - m_RouterInfo.EnableV4 (); - } + m_RouterInfo.EnableV4 (); else m_RouterInfo.DisableV4 (); UpdateRouterInfo (); } - void RouterContext::SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host) + + void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host) { - if (supportsmesh) + bool updated = false, found = false; + int port = 0; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - 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) + if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { - for (auto& addr: *addresses) + if (addr->host != host) { - if (addr && addr->port) - { - port = addr->port; - break; - } + addr->host = host; + updated = true; + } + found = true; + } + else + port = addr->port; + } + if (!found) + { + // create new address + m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port); + auto mtu = i2p::util::net::GetMTU (host); + if (mtu) + { + LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); + if (mtu > 1472) { // TODO: magic constant + mtu = 1472; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); } } - if (!port) port = SelectRandomPort (); - 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); - } + m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO + updated = true; } + if (updated) + UpdateRouterInfo (); } 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 updated = false, found = false; + int port = 0; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) { - addr->host = host; - UpdateRouterInfo (); + if (addr->IsPublishedNTCP2 ()) + { + if (addr->host.is_v6 ()) + { + if (addr->host != host) + { + addr->host = host; + updated = true; + } + found = true; + break; + } + else + port = addr->port; // NTCP2 v4 + } } + + if (!found && port) // we have found NTCP2 v4 but not v6 + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); + updated = true; + } + if (updated) + UpdateRouterInfo (); } void RouterContext::UpdateStats () @@ -1059,44 +541,31 @@ namespace i2p bool RouterContext::Load () { - { - std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); - if (!fk.is_open ()) return false; - fk.seekg (0, std::ios::end); - size_t len = fk.tellg(); - fk.seekg (0, std::ios::beg); + std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); + if (!fk.is_open ()) return false; + fk.seekg (0, std::ios::end); + size_t len = fk.tellg(); + fk.seekg (0, std::ios::beg); - if (len == sizeof (i2p::data::Keys)) // old keys file format - { - i2p::data::Keys keys; - fk.read ((char *)&keys, sizeof (keys)); - m_Keys = keys; - } - else // new keys file format - { - uint8_t * buf = new uint8_t[len]; - fk.read ((char *)buf, len); - m_Keys.FromBuffer (buf, len); - delete[] buf; - } - } - std::shared_ptr oldIdentity; - if (m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1 || - m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) + if (len == sizeof (i2p::data::Keys)) // old keys file format { - // update keys - LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); - oldIdentity = m_Keys.GetPublic (); - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); - SaveKeys (); + i2p::data::Keys keys; + fk.read ((char *)&keys, sizeof (keys)); + m_Keys = keys; + } + else // new keys file format + { + uint8_t * buf = new uint8_t[len]; + fk.read ((char *)buf, len); + m_Keys.FromBuffer (buf, len); + delete[] buf; } // read NTCP2 keys if available std::ifstream n2k (i2p::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary); if (n2k) { n2k.seekg (0, std::ios::end); - size_t len = n2k.tellg(); + len = n2k.tellg(); n2k.seekg (0, std::ios::beg); if (len == sizeof (NTCP2PrivateKeys)) { @@ -1105,30 +574,18 @@ 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 ()); + m_RouterInfo.SetRouterIdentity (GetIdentity ()); i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); if (!routerInfo.IsUnreachable ()) // router.info looks good { m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - if (oldIdentity) - m_RouterInfo.SetRouterIdentity (GetIdentity ()); // from new keys + m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); m_RouterInfo.SetProperty ("router.version", I2P_VERSION); - m_RouterInfo.DeleteProperty ("coreVersion"); // TODO: remove later + + // Migration to 0.9.24. TODO: remove later + m_RouterInfo.DeleteProperty ("coreVersion"); + m_RouterInfo.DeleteProperty ("stat_uptime"); } else { @@ -1137,30 +594,17 @@ namespace i2p } if (IsUnreachable ()) - SetReachable (true, true); // we assume reachable until we discover firewall through peer tests + SetReachable (); // we assume reachable until we discover firewall through peer tests - bool updated = false; - // create new NTCP2 keys if required - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - if ((ntcp2 || ygg) && !m_NTCP2Keys) + // read NTCP2 + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - NewNTCP2Keys (); - UpdateNTCP2Keys (); - updated = true; + if (!m_NTCP2Keys) NewNTCP2Keys (); + UpdateNTCP2Address (true); // enable NTCP2 } - // create new SSU2 keys if required - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2 && !m_SSU2Keys) - { - NewSSU2Keys (); - UpdateSSU2Keys (); - updated = true; - } - if (m_RouterInfo.UpdateCongestion (i2p::data::RouterInfo::eLowCongestion)) - updated = true; - if (updated) - UpdateRouterInfo (); + else + UpdateNTCP2Address (false); // disable NTCP2 return true; } @@ -1181,378 +625,55 @@ namespace i2p return i2p::tunnel::tunnels.GetExploratoryPool (); } - int RouterContext::GetCongestionLevel (bool longTerm) const + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { - 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) - { - 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; + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); } 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"); + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); } - void RouterContext::PostGarlicMessage (std::shared_ptr msg) - { - uint8_t * buf = msg->GetPayload (); - uint32_t len = bufbe32toh (buf); - if (len > msg->GetLength ()) - { - LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); - return; - } - buf += 4; - if (!HandleECIESx25519TagMessage (buf, len)) // try tag first - { - // then Noise_N one-time decryption - if (m_ECIESSession) - m_ECIESSession->HandleNextMessage (buf, len); - 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)); - else - LogPrint (eLogError, "Router: service is NULL"); + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); } - void RouterContext::PostDeliveryStatusMessage (std::shared_ptr msg) + void RouterContext::CleanupDestination () { - if (m_PublishReplyToken == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) - { - LogPrint (eLogInfo, "Router: Publishing confirmed. reply token=", m_PublishReplyToken); - m_PublishExcluded.clear (); - m_PublishReplyToken = 0; - SchedulePublish (); - } - else - i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::CleanupExpiredTags (); } - void RouterContext::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) - { - if (m_Service) - { - struct - { - uint8_t k[32]; - uint64_t t; - } data; - memcpy (data.k, key, 32); - data.t = tag; - boost::asio::post (m_Service->GetService (), [this,data](void) - { - AddECIESx25519Key (data.k, data.t); - }); - } - else - LogPrint (eLogError, "Router: service is NULL"); - } - uint32_t RouterContext::GetUptime () const { - return i2p::util::GetMonotonicSeconds () - m_StartupTime; + return i2p::util::GetSecondsSinceEpoch () - m_StartupTime; } - bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const + bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data) : false; + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; } - bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) + bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const { - return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, false) : false; } - bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) + i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () { - // m_InitialNoiseState is h = SHA256(h || hepk) - m_CurrentNoiseState = m_InitialNoiseState; - m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) - uint8_t sharedSecret[32]; - if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret)) - { - LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); - return false; - } - m_CurrentNoiseState.MixKey (sharedSecret); - encrypted += 32; - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState.m_H, 32, - m_CurrentNoiseState.m_CK + 32, nonce, data, clearTextSize, false)) // decrypt - { - LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); - return false; - } - m_CurrentNoiseState.MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) - return true; - } - - bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) - { - return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); - } - - i2p::crypto::X25519Keys& RouterContext::GetNTCP2StaticKeys () - { - if (!m_NTCP2StaticKeys) + if (!m_StaticKeys) { if (!m_NTCP2Keys) NewNTCP2Keys (); auto x = new i2p::crypto::X25519Keys (m_NTCP2Keys->staticPrivateKey, m_NTCP2Keys->staticPublicKey); - if (!m_NTCP2StaticKeys) - m_NTCP2StaticKeys.reset (x); + if (!m_StaticKeys) + m_StaticKeys.reset (x); else delete x; } - return *m_NTCP2StaticKeys; + return *m_StaticKeys; } - - i2p::crypto::X25519Keys& RouterContext::GetSSU2StaticKeys () - { - if (!m_SSU2StaticKeys) - { - if (!m_SSU2Keys) NewSSU2Keys (); - auto x = new i2p::crypto::X25519Keys (m_SSU2Keys->staticPrivateKey, m_SSU2Keys->staticPublicKey); - if (!m_SSU2StaticKeys) - m_SSU2StaticKeys.reset (x); - else - delete x; - } - 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..19fe08b2 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,165 +1,92 @@ -/* -* 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 ROUTER_CONTEXT_H__ #define ROUTER_CONTEXT_H__ #include #include #include -#include -#include +#include #include #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" -#include "util.h" namespace i2p { -namespace garlic -{ - class RouterIncomingRatchetSession; -} - const char ROUTER_INFO[] = "router.info"; 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 char NTCP2_KEYS[] = "ntcp2.keys"; + 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 }; enum RouterError { eRouterErrorNone = 0, - eRouterErrorClockSkew = 1, - eRouterErrorOffline = 2, - eRouterErrorSymmetricNAT = 3, - eRouterErrorFullConeNAT = 4, - eRouterErrorNoDescriptors = 5 + eRouterErrorClockSkew = 1 }; class RouterContext: public i2p::garlic::GarlicDestination { private: - struct NTCP2PrivateKeys + struct NTCP2PrivateKeys { uint8_t staticPublicKey[32]; uint8_t staticPrivateKey[32]; uint8_t iv[16]; - }; + }; - struct SSU2PrivateKeys - { - uint8_t staticPublicKey[32]; - uint8_t staticPrivateKey[32]; - 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 () + i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; + std::shared_ptr GetSharedRouterInfo () const { - return std::shared_ptr (&m_RouterInfo, - [](i2p::data::RouterInfo *) {}); + return std::shared_ptr (&m_RouterInfo, + [](const i2p::data::RouterInfo *) {}); } std::shared_ptr GetSharedDestination () { 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; }; - i2p::crypto::X25519Keys& GetNTCP2StaticKeys (); + i2p::crypto::X25519Keys& GetStaticKeys (); - const uint8_t * GetSSU2StaticPublicKey () const { return m_SSU2Keys ? m_SSU2Keys->staticPublicKey : nullptr; }; - const uint8_t * GetSSU2StaticPrivateKey () const { return m_SSU2Keys ? m_SSU2Keys->staticPrivateKey : nullptr; }; - const uint8_t * GetSSU2IntroKey () const { return m_SSU2Keys ? m_SSU2Keys->intro : nullptr; }; - i2p::crypto::X25519Keys& GetSSU2StaticKeys (); - - uint32_t GetUptime () const; // in seconds + uint32_t GetUptime () const; + uint32_t GetStartupTime () const { return m_StartupTime; }; uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; }; uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; }; - bool GetTesting () const { return m_Testing; }; - void SetTesting (bool testing); RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); RouterError GetError () const { return m_Error; }; - void SetError (RouterError error) { m_Error = error; }; - bool GetTestingV6 () const { return m_TestingV6; }; - void SetTestingV6 (bool testing); - RouterStatus GetStatusV6 () const { return m_StatusV6; }; - void SetStatusV6 (RouterStatus status); - RouterError GetErrorV6 () const { return m_ErrorV6; }; - void SetErrorV6 (RouterError error) { m_ErrorV6 = error; }; + void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = 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); + bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; - void UpdatePort (int port); // called from Daemon - void UpdateAddress (const boost::asio::ip::address& host); // called from SSU2 or Daemon - void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); - void 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 UpdatePort (int port); // called from Daemon + void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon + void PublishNTCP2Address (int port, bool publish = true); + void UpdateNTCP2Address (bool enable); + void PublishNTCPAddress (bool publish, bool v4only = true); + 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); + void SetUnreachable (); + void SetReachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); @@ -169,43 +96,31 @@ 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 UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session + void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from NTCP2 session 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, BN_CTX * ctx) 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; + void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); // 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; - - 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 ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatusMessage (std::shared_ptr msg); private: @@ -213,63 +128,25 @@ namespace garlic void NewRouterInfo (); 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; + i2p::data::RouterInfo m_RouterInfo; i2p::data::PrivateKeys m_Keys; - std::shared_ptr m_Decryptor, m_TunnelDecryptor; - std::shared_ptr m_ECIESSession; + std::shared_ptr m_Decryptor; uint64_t m_LastUpdateTime; // in seconds bool m_AcceptsTunnels, m_IsFloodfill; - uint64_t m_StartupTime; // monotonic seconds + uint64_t m_StartupTime; // in seconds since epoch uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; - RouterStatus m_Status, m_StatusV6; - RouterError m_Error, m_ErrorV6; - bool m_Testing, m_TestingV6; + RouterStatus m_Status; + 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; + std::unique_ptr m_StaticKeys; }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 4af32f57..6b520943 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,96 +1,56 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include "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" -#include "util.h" #include "Crypto.h" #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 { namespace data { - RouterInfo::Buffer::Buffer (const uint8_t * buf, size_t len) - { - 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_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), + m_SupportedTransports (0), m_Caps (0) { - m_Addresses = AddressesPtr(new Addresses ()); // create empty list - m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's - ReadFromFile (fullPath); + m_Addresses = boost::make_shared(); // create empty list + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + ReadFromFile (); } - 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) - { - if (len <= MAX_RI_BUFFER_SIZE) - { - m_Addresses = AddressesPtr(new Addresses ()); // create empty list - m_Buffer = buf; - if (m_Buffer) m_Buffer->SetBufferLen (len); - ReadFromBuffer (true); - } - else - { - LogPrint (eLogError, "RouterInfo: Buffer is too long ", len, ". Ignored"); - m_Buffer = nullptr; - m_IsUnreachable = true; - } - } - - RouterInfo::RouterInfo (const uint8_t * buf, size_t len): - RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len) + RouterInfo::RouterInfo (const uint8_t * buf, int len): + m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { + m_Addresses = boost::make_shared(); // create empty list + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + memcpy (m_Buffer, buf, len); + m_BufferLen = len; + ReadFromBuffer (true); } RouterInfo::~RouterInfo () { + delete[] m_Buffer; } - bool RouterInfo::Update (const uint8_t * buf, size_t len) + void RouterInfo::Update (const uint8_t * buf, int len) { - if (len > MAX_RI_BUFFER_SIZE) - { - LogPrint (eLogWarning, "RouterInfo: Updated buffer is too long ", len, ". Not changed"); - return false; - } // verify signature since we have identity already int l = len - m_RouterIdentity->GetSignatureLen (); if (m_RouterIdentity->Verify (buf, l, buf + l)) @@ -99,25 +59,26 @@ namespace data m_IsUpdated = true; 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 (); + m_Properties.clear (); + // copy buffer + if (!m_Buffer) + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + memcpy (m_Buffer, buf, len); + m_BufferLen = 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 + 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) @@ -126,35 +87,34 @@ namespace data m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } - bool RouterInfo::LoadFile (const std::string& fullPath) + bool RouterInfo::LoadFile () { - std::ifstream s(fullPath, std::ifstream::binary); + std::ifstream s(m_FullPath, std::ifstream::binary); 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", m_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); + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + s.read((char *)m_Buffer, m_BufferLen); } else { - LogPrint (eLogError, "RouterInfo: Can't open file ", fullPath); + LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath); return false; } return true; } - void RouterInfo::ReadFromFile (const std::string& fullPath) + void RouterInfo::ReadFromFile () { - if (LoadFile (fullPath)) + if (LoadFile ()) ReadFromBuffer (false); else m_IsUnreachable = true; @@ -162,17 +122,11 @@ namespace data void RouterInfo::ReadFromBuffer (bool verifySignature) { - if (!m_Buffer) - { - m_IsUnreachable = true; - return; - } - size_t bufferLen = m_Buffer->GetBufferLen (); - m_RouterIdentity = NewIdentity (m_Buffer->data (), bufferLen); + m_RouterIdentity = std::make_shared(m_Buffer, 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,346 +140,237 @@ namespace data return; } // verify signature - int l = bufferLen - m_RouterIdentity->GetSignatureLen (); - if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer->data (), l, (uint8_t *)m_Buffer->data () + l)) + int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); + if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) { - LogPrint (eLogError, "RouterInfo: Signature verification failed"); + 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 + identityLen, m_BufferLen - identityLen); + ReadFromStream (str); + if (!str) { - LogPrint (eLogError, "RouterInfo: Malformed message"); + 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 + 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)); if (!s) return; + bool introducers = false; 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

(); + s.read ((char *)&address->cost, sizeof (address->cost)); + s.read ((char *)&address->date, sizeof (address->date)); + bool isNTCP2Only = false; + char transportStyle[6]; + auto transportStyleLen = ReadString (transportStyle, 6, s) - 1; + if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 { - address->transportStyle = eTransportSSU2; + address->transportStyle = eTransportNTCP; + if (transportStyleLen > 4 && transportStyle[4] == '2') isNTCP2Only= true; + } + else if (!strcmp (transportStyle, "SSU")) + { + address->transportStyle = eTransportSSU; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } else 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; - if (address->transportStyle == eTransportUnknown) - { - // skip unknown address - offset += size; - continue; - } - 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; - 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 ()) + address->host = boost::asio::ip::address::from_string (value, ecode); + if (!ecode) { - 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; +#if BOOST_VERSION >= 104900 + if (!address->host.is_unspecified ()) // check if address is valid +#else + address->host.to_string (ecode); + if (!ecode) +#endif + { + // add supported protocol + if (address->host.is_v4 ()) + supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; + else + supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; + } } } - 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) + Base64ToByteStream (value, strlen (value), address->ssu->key, 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")) + ExtractCaps (value); + else if (!strcmp (key, "s")) // ntcp2 static key { - 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 - } - else if (address->IsSSU2 ()) - { - if (Base64ToByteStream (value, address->i, 32) == 32) - isIntroKey = true; - else - address->transportStyle = eTransportUnknown; // invalid address - } - } - else if (key == "v") + if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); + supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; + Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); + } + else if (!strcmp (key, "i")) // ntcp2 iv { - if (value == "2") - isV2 = true; - else - { - LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v"); - address->transportStyle = eTransportUnknown; // invalid address - } - } + if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); + supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; + Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); + address->ntcp2->isPublished = true; // presence if "i" means "published" + } else if (key[0] == 'i') { // introducers - if (!address->ssu) - { - LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); - continue; - } - unsigned char index = key[key.length () - 1] - '0'; // TODO: + introducers = true; + 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 ()) - { - if (address->ssu->introducers.empty ()) // first time - address->ssu->introducers.reserve (3); 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 ()); - } - } - } - if (address->transportStyle == eTransportNTCP2) - { - if (isStaticKey) - { - if (isHost && address->port) - { - if (address->host.is_v6 ()) - supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); - else - supportedTransports |= eNTCP2V4; - m_PublishedTransports |= supportedTransports; - } - else - { - address->published = false; - if (address->caps) - { - if (address->caps & AddressCaps::eV4) supportedTransports |= eNTCP2V4; - if (address->caps & AddressCaps::eV6) supportedTransports |= eNTCP2V6; - } - else - supportedTransports |= eNTCP2V4; // most likely, since we don't have host + 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")) + Base64ToByteStream (value, strlen (value), introducer.iKey, 32); + else if (!strcmp (key, "iexp")) + introducer.iExp = boost::lexical_cast(value); } + if (!s) return; } - else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey) + if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented + if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true; + if (supportedTransports) { - if (address->IsV4 ()) supportedTransports |= eSSU2V4; - if (address->IsV6 ()) supportedTransports |= eSSU2V6; - if (isHost && 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 (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); 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; - SetProperty (key, value); + 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; + m_Properties[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) - { - m_Version = 0; - for (auto ch: value) - { - if (ch >= '0' && ch <= '9') - { - m_Version *= 10; - m_Version += (ch - '0'); - } - } - 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) && atoi (value) != i2p::context.GetNetID ()) { - 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 ()) - { - LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); - m_IsUnreachable = true; - } + 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); + m_Family = value; + boost::to_lower (m_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 - m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); - else - { - LogPrint (eLogWarning, "RouterInfo: Family ", family, " signature verification failed"); - SetUnreachable (true); - } + if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) + { + LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); + m_Family.clear (); + } } + + if (!s) return; } - if (!m_SupportedTransports || !isNetId || !m_Version) + if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers)) SetUnreachable (true); - - return true; - } - - bool RouterInfo::IsFamily (FamilyID famid) const - { - return m_FamilyID == famid; } - void RouterInfo::ExtractCaps (std::string_view value) + bool RouterInfo::IsFamily(const std::string & fam) const { + return m_Family == fam; + } + + 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,866 +381,104 @@ namespace data case CAPS_FLAG_UNREACHABLE: m_Caps |= Caps::eUnreachable; break; - case CAPS_FLAG_MEDIUM_CONGESTION: - m_Congestion = eMediumCongestion; + case CAPS_FLAG_SSU_TESTING: + m_Caps |= Caps::eSSUTesting; break; - case CAPS_FLAG_HIGH_CONGESTION: - m_Congestion = eHighCongestion; - break; - case CAPS_FLAG_REJECT_ALL_CONGESTION: - m_Congestion = eRejectAll; - break; - default: ; - } - } - } - - uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const - { - uint8_t caps = 0; - for (auto cap: value) - { - switch (cap) - { - case CAPS_FLAG_V4: - caps |= AddressCaps::eV4; - break; - case CAPS_FLAG_V6: - caps |= AddressCaps::eV6; - break; - case CAPS_FLAG_SSU2_TESTING: - caps |= AddressCaps::eSSUTesting; - break; - case CAPS_FLAG_SSU2_INTRODUCER: - caps |= AddressCaps::eSSUIntroducer; + case CAPS_FLAG_SSU_INTRODUCER: + m_Caps |= Caps::eSSUIntroducer; break; default: ; } - } - 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; - size_t size = m_RouterIdentity->GetFullLen (); - if (size + 8 > len) return false; - return bufbe64toh (buf + size) > m_Timestamp; - } - - const uint8_t * RouterInfo::LoadBuffer (const std::string& fullPath) - { - if (!m_Buffer) - { - 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"); - return false; - } - return SaveToFile (fullPath, m_Buffer); - } - - std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const - { - uint8_t l = buf[0]; - if (l > len) - { - LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); - l = len; - } - return { (const char *)(buf + 1), l }; - } - - std::tuple RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const - { - auto key = ExtractString (buf, len); - size_t offset = key.length () + 1; - if (offset >= len) return { std::string_view(), std::string_view(), len }; - if (buf[offset] != '=') - { - LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key); - key = std::string_view(); - } - offset++; - if (offset >= len) return { key, std::string_view(), len }; - auto value = ExtractString (buf + offset, len - offset); - offset += value.length () + 1; - if (offset >= len) return { key, std::string_view(), len }; - if (buf[offset] != ';') - { - LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value); - value = std::string_view(); - } - offset++; - return { key, value, offset }; - } - - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) - { - auto addr = std::make_shared
(); - addr->port = port; - addr->transportStyle = eTransportNTCP2; - addr->caps = caps; - 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; + cap++; } } - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port) - { - auto addr = std::make_shared
(); - addr->host = host; - addr->port = port; - addr->transportStyle = eTransportNTCP2; - addr->date = 0; - addr->published = true; - memcpy (addr->s, staticKey, 32); - memcpy (addr->i, iv, 16); - addr->caps = 0; - if (host.is_unspecified ()) - { - if (host.is_v4 ()) addr->caps |= eV4; - if (host.is_v6 ()) addr->caps |= eV6; - } - auto addresses = GetAddresses (); - if (addr->IsV4 ()) - { - m_SupportedTransports |= eNTCP2V4; - m_ReachableTransports |= eNTCP2V4; - (*addresses)[eNTCP2V4Idx] = addr; - } - if (addr->IsV6 ()) - { - if (i2p::util::net::IsYggdrasilAddress (addr->host)) - { - m_SupportedTransports |= eNTCP2V6Mesh; - m_ReachableTransports |= eNTCP2V6Mesh; - (*addresses)[eNTCP2V6MeshIdx] = addr; - } - else - { - m_SupportedTransports |= eNTCP2V6; - m_ReachableTransports |= eNTCP2V6; - (*addresses)[eNTCP2V6Idx] = addr; - } - } - } - - void RouterInfo::RemoveNTCP2Address (bool v4) - { - auto addresses = GetAddresses (); - if (v4) - { - if ((*addresses)[eNTCP2V6Idx]) - (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eNTCP2V4Idx].reset (); - } - else - { - if ((*addresses)[eNTCP2V4Idx]) - (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eNTCP2V6Idx].reset (); - } - UpdateSupportedTransports (); - } - - void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps) - { - auto addr = std::make_shared
(); - addr->transportStyle = eTransportSSU2; - addr->port = 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; - } - } - - void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, - const boost::asio::ip::address& host, int port) - { - auto addr = std::make_shared
(); - addr->transportStyle = eTransportSSU2; - addr->host = host; - addr->port = port; - addr->published = true; - 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; - } - } - - void RouterInfo::RemoveSSU2Address (bool v4) - { - auto addresses = GetAddresses (); - if (v4) - { - if ((*addresses)[eSSU2V6Idx]) - (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eSSU2V4Idx].reset (); - } - else - { - if ((*addresses)[eSSU2V4Idx]) - (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eSSU2V6Idx].reset (); - } - UpdateSupportedTransports (); - } - - bool RouterInfo::IsNTCP2 (bool v4only) const - { - if (v4only) - return m_SupportedTransports & eNTCP2V4; - else - return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6); - } - - - void RouterInfo::EnableV6 () - { - if (!IsV6 ()) - { - uint8_t addressCaps = AddressCaps::eV6; - if (IsV4 ()) addressCaps |= AddressCaps::eV4; - SetUnreachableAddressesTransportCaps (addressCaps); - UpdateSupportedTransports (); - } - } - - void RouterInfo::EnableV4 () - { - if (!IsV4 ()) - { - uint8_t addressCaps = AddressCaps::eV4; - if (IsV6 ()) addressCaps |= AddressCaps::eV6; - SetUnreachableAddressesTransportCaps (addressCaps); - UpdateSupportedTransports (); - } - } - - - void RouterInfo::DisableV6 () - { - if (IsV6 ()) - { - auto addresses = GetAddresses (); - if ((*addresses)[eNTCP2V6Idx]) - { - if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx]) - (*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eNTCP2V6Idx].reset (); - } - if ((*addresses)[eSSU2V6Idx]) - { - if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx]) - (*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; - (*addresses)[eSSU2V6Idx].reset (); - } - UpdateSupportedTransports (); - } - } - - void RouterInfo::DisableV4 () - { - if (IsV4 ()) - { - auto addresses = GetAddresses (); - if ((*addresses)[eNTCP2V4Idx]) - { - if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx]) - (*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eNTCP2V4Idx].reset (); - } - if ((*addresses)[eSSU2V4Idx]) - { - if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx]) - (*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; - (*addresses)[eSSU2V4Idx].reset (); - } - UpdateSupportedTransports (); - } - } - - void RouterInfo::EnableMesh () - { - if (!IsMesh ()) - { - m_SupportedTransports |= eNTCP2V6Mesh; - m_ReachableTransports |= eNTCP2V6Mesh; - } - } - - void RouterInfo::DisableMesh () - { - if (IsMesh ()) - { - m_SupportedTransports &= ~eNTCP2V6Mesh; - m_ReachableTransports &= ~eNTCP2V6Mesh; - (*GetAddresses ())[eNTCP2V6MeshIdx].reset (); - } - } - - std::shared_ptr RouterInfo::GetSSU2V4Address () const - { - return (*GetAddresses ())[eSSU2V4Idx]; - } - - 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 - } - - 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 - auto addresses = boost::atomic_load (&m_Addresses); -#endif - for (const auto& address : *addresses) - if (address && filter (address)) return address; - - return nullptr; - } - - std::shared_ptr RouterInfo::GetNTCP2V4Address () const - { - return (*GetAddresses ())[eNTCP2V4Idx]; - } - - std::shared_ptr RouterInfo::GetNTCP2V6Address () const - { - return (*GetAddresses ())[eNTCP2V6Idx]; - } - - std::shared_ptr RouterInfo::GetPublishedNTCP2V4Address () const - { - auto addr = (*GetAddresses ())[eNTCP2V4Idx]; - if (addr && addr->IsPublishedNTCP2 ()) return addr; - return nullptr; - } - - std::shared_ptr RouterInfo::GetPublishedNTCP2V6Address () const - { - auto addr = (*GetAddresses ())[eNTCP2V6Idx]; - if (addr && addr->IsPublishedNTCP2 ()) return addr; - return nullptr; - } - - std::shared_ptr RouterInfo::GetYggdrasilAddress () const - { - return (*GetAddresses ())[eNTCP2V6MeshIdx]; - } - - std::shared_ptr RouterInfo::GetProfile () const - { - auto profile = m_Profile; - if (!profile) - { - profile = GetRouterProfile (GetIdentHash ()); - m_Profile = profile; - } - return profile; - } - - void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted) const - { - auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); - if (encryptor) - encryptor->Encrypt (data, encrypted); - } - - 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))) && - GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; - } - - bool RouterInfo::IsPublished (bool v4) const - { - if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addresses are not published - return IsPublishedOn (v4 ? (eNTCP2V4 | eSSU2V4) : (eNTCP2V6 | eSSU2V6)); - } - - bool RouterInfo::IsPublishedOn (CompatibleTransports transports) const - { - return m_PublishedTransports & transports; - } - - bool RouterInfo::IsNAT2NATOnly (const RouterInfo& other) const - { - return !(m_PublishedTransports & other.m_SupportedTransports) && - !(other.m_PublishedTransports & m_SupportedTransports); - } - - bool RouterInfo::IsSSU2PeerTesting (bool v4) const - { - if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; - auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - return addr && addr->IsPeerTesting () && addr->IsReachableSSU (); - } - - bool RouterInfo::IsSSU2Introducer (bool v4) const - { - if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; - auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - return addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port; - } - - void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) - { - for (auto& addr: *GetAddresses ()) - { - if (addr && !addr->published) - { - addr->caps &= ~(eV4 | eV6); - addr->caps |= transports; - } - } - } - - void RouterInfo::UpdateSupportedTransports () - { - m_SupportedTransports = 0; - m_ReachableTransports = 0; - for (const auto& addr: *GetAddresses ()) - { - if (!addr) continue; - uint8_t transports = 0; - switch (addr->transportStyle) - { - 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: ; - } - 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); - } - - 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 (); - std::stringstream s; - uint8_t ident[1024]; - auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); - auto signatureLen = privateKeys.GetPublic ()->GetSignatureLen (); - s.write ((char *)ident, identLen); - WriteToStream (s); - size_t len = s.str ().size (); - if (len + signatureLen < MAX_RI_BUFFER_SIZE) - { - UpdateBuffer ((const uint8_t *)s.str ().c_str (), len); - // signature - privateKeys.Sign (GetBuffer (), len, GetBufferPointer (len)); - SetBufferLen (len + signatureLen); - } - else - LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", len + signatureLen); - } - - void LocalRouterInfo::UpdateCaps (uint8_t caps) - { - SetCaps (caps); - UpdateCapsProperty (); - } - - void LocalRouterInfo::UpdateCapsProperty () + void RouterInfo::UpdateCapsProperty () { std::string caps; - uint8_t c = GetCaps (); - if (c & eFloodfill) + if (m_Caps & eFloodfill) { - if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? + if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? 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 { - if (c & eExtraBandwidth) - caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ + if (m_Caps & eExtraBandwidth) + caps += (m_Caps & 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 += (m_Caps & 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 + if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden + if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable + if (m_Caps & 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) + void RouterInfo::WriteToStream (std::ostream& s) const { - 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 ()); + uint64_t ts = htobe64 (m_Timestamp); 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++; - } + uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (size_t idx = 0; idx < addresses->size(); idx++) + for (const auto& addr_ptr : *m_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) - cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; - 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.cost, sizeof (address.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) + if (address.transportStyle == eTransportNTCP) + WriteString (address.IsNTCP2 () ? "NTCP2" : "NTCP", s); + else if (address.transportStyle == eTransportSSU) { - WriteString ("NTCP2", s); - // caps - if (!isPublished) - { - WriteString ("caps", properties); - properties << '='; - std::string caps; - if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 () || address.host.is_v6 ()) caps += CAPS_FLAG_V6; // we set 6 for unspecified ipv6 - if (caps.empty ()) caps += CAPS_FLAG_V4; - WriteString (caps, properties); - properties << ';'; - } - } - else if (address.transportStyle == eTransportSSU2) - { - WriteString ("SSU2", s); + WriteString ("SSU", s); // caps + WriteString ("caps", properties); + properties << '='; std::string caps; - if (isPublished) - { - if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU2_TESTING; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU2_INTRODUCER; - } - else - { - if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 () || address.host.is_v6 ()) caps += CAPS_FLAG_V6; // we set 6 for unspecified ipv6 - if (caps.empty ()) caps += CAPS_FLAG_V4; - } - if (!caps.empty ()) - { - WriteString ("caps", properties); - properties << '='; - WriteString (caps, properties); - properties << ';'; - } + if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; + if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + WriteString (caps, properties); + properties << ';'; } else WriteString ("", s); - if (isPublished && !address.host.is_unspecified ()) + if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) { WriteString ("host", properties); properties << '='; WriteString (address.host.to_string (), properties); properties << ';'; } - if ((address.IsNTCP2 () && isPublished) || address.IsSSU2 ()) - { - // publish i for NTCP2 or SSU2 - WriteString ("i", properties); properties << '='; - size_t len = address.IsSSU2 () ? 32 : 16; - WriteString (address.i.ToBase64 (len), properties); properties << ';'; - } - if (address.transportStyle == eTransportSSU2) + if (address.transportStyle == eTransportSSU) { // write introducers if any - if (address.ssu && !address.ssu->introducers.empty()) + if (address.ssu->introducers.size () > 0) { 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); - properties << '='; - WriteString (std::to_string(introducer.iExp), properties); - properties << ';'; - } + 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 ("ih" + std::to_string(i), properties); + WriteString ("ikey" + boost::lexical_cast(i), properties); properties << '='; - auto value = ByteStreamToBase64 (introducer.iH, 32); + char value[64]; + size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); + value[l] = 0; WriteString (value, properties); properties << ';'; i++; @@ -1403,42 +486,74 @@ namespace data i = 0; for (const auto& introducer: address.ssu->introducers) { - if (!introducer.iTag) continue; - WriteString ("itag" + std::to_string(i), properties); + WriteString ("iport" + boost::lexical_cast(i), properties); properties << '='; - WriteString (std::to_string(introducer.iTag), 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++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + if (introducer.iExp) // expiration is specified + { + WriteString ("iexp" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iExp), properties); + properties << ';'; + } + i++; + } } - } - - if (address.transportStyle == eTransportSSU2) - { + // write intro key + WriteString ("key", properties); + properties << '='; + char value[64]; + size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); + value[l] = 0; + WriteString (value, properties); + properties << ';'; // write mtu - if (address.ssu && address.ssu->mtu) + if (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 (address.IsPublishedNTCP2 ()) + { + // publish i for NTCP2 + WriteString ("i", properties); properties << '='; + WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; + } + + if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) { WriteString ("port", properties); properties << '='; - WriteString (std::to_string(address.port), properties); + WriteString (boost::lexical_cast(address.port), properties); properties << ';'; - } - if (address.IsNTCP2 () || address.IsSSU2 ()) + } + if (address.IsNTCP2 ()) { - // publish s and v for NTCP2 or SSU2 + // publish s and v for NTCP2 WriteString ("s", properties); properties << '='; - WriteString (address.s.ToBase64 (), properties); properties << ';'; + WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; WriteString ("v", properties); properties << '='; WriteString ("2", properties); properties << ';'; - } + } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); @@ -1463,19 +578,189 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } - void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) + bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { - auto [it, inserted] = m_Properties.emplace (key, value); - if (!inserted) - it->second = value; + if (!m_RouterIdentity) return false; + size_t size = m_RouterIdentity->GetFullLen (); + if (size + 8 > len) return false; + return bufbe64toh (buf + size) > m_Timestamp; } - void LocalRouterInfo::DeleteProperty (const std::string& key) + const uint8_t * RouterInfo::LoadBuffer () + { + if (!m_Buffer) + { + if (LoadFile ()) + LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); + } + return m_Buffer; + } + + void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys) + { + m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp + std::stringstream s; + uint8_t ident[1024]; + auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); + s.write ((char *)ident, identLen); + WriteToStream (s); + m_BufferLen = s.str ().size (); + if (!m_Buffer) + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); + // signature + privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); + m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); + } + + bool RouterInfo::SaveToFile (const std::string& fullPath) + { + m_FullPath = fullPath; + if (!m_Buffer) { + LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); + return false; + } + std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) { + LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); + return false; + } + f.write ((char *)m_Buffer, m_BufferLen); + return true; + } + + size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const + { + uint8_t l; + s.read ((char *)&l, 1); + if (l < len) + { + s.read (str, l); + if (!s) l = 0; // failed, return empty string + str[l] = 0; + } + else + { + LogPrint (eLogWarning, "RouterInfo: string length ", (int)l, " exceeds buffer size ", len); + s.seekg (l, std::ios::cur); // skip + str[0] = 0; + } + return l+1; + } + + void RouterInfo::WriteString (const std::string& str, std::ostream& s) const + { + uint8_t len = str.size (); + s.write ((char *)&len, 1); + s.write (str.c_str (), len); + } + + void RouterInfo::AddNTCPAddress (const char * host, int port) + { + auto addr = std::make_shared
(); + addr->host = boost::asio::ip::address::from_string (host); + addr->port = port; + addr->transportStyle = eTransportNTCP; + addr->cost = 6; + addr->date = 0; + for (const auto& it: *m_Addresses) // don't insert same address twice + if (*it == *addr) return; + m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; + m_Addresses->push_front(std::move(addr)); // always make NTCP first + } + + 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 = eTransportSSU; + addr->cost = 10; // NTCP should have priority over SSU + addr->date = 0; + addr->ssu.reset (new SSUExt ()); + addr->ssu->mtu = mtu; + memcpy (addr->ssu->key, key, 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_Addresses->push_back(std::move(addr)); + + m_Caps |= eSSUTesting; + m_Caps |= eSSUIntroducer; + } + + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port) + { + auto addr = std::make_shared
(); + addr->host = host; + addr->port = port; + addr->transportStyle = eTransportNTCP; + addr->cost = port ? 3 : 14; // override from RouterContext::PublishNTCP2Address + addr->date = 0; + addr->ntcp2.reset (new NTCP2Ext ()); + addr->ntcp2->isNTCP2Only = true; // NTCP2 only address + if (port) addr->ntcp2->isPublished = true; + memcpy (addr->ntcp2->staticKey, staticKey, 32); + memcpy (addr->ntcp2->iv, iv, 16); + m_Addresses->push_back(std::move(addr)); + } + + bool RouterInfo::AddIntroducer (const Introducer& introducer) + { + for (auto& addr : *m_Addresses) + { + if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) + { + for (auto& intro: addr->ssu->introducers) + if (intro.iTag == introducer.iTag) return false; // already presented + addr->ssu->introducers.push_back (introducer); + return true; + } + } + return false; + } + + bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) + { + for (auto& addr: *m_Addresses) + { + if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) + { + 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); + return true; + } + } + } + return false; + } + + void RouterInfo::SetCaps (uint8_t caps) + { + m_Caps = caps; + UpdateCapsProperty (); + } + + void RouterInfo::SetCaps (const char * caps) + { + SetProperty ("caps", caps); + m_Caps = 0; + ExtractCaps (caps); + } + + void RouterInfo::SetProperty (const std::string& key, const std::string& value) + { + m_Properties[key] = value; + } + + void RouterInfo::DeleteProperty (const std::string& key) { m_Properties.erase (key); } - std::string LocalRouterInfo::GetProperty (const std::string& key) const + std::string RouterInfo::GetProperty (const std::string& key) const { auto it = m_Properties.find (key); if (it != m_Properties.end ()) @@ -1483,98 +768,154 @@ namespace data return ""; } - void LocalRouterInfo::UpdateFloodfillProperty (bool floodfill) + bool RouterInfo::IsNTCP (bool v4only) const { - if (floodfill) - { - UpdateCaps (GetCaps () | i2p::data::RouterInfo::eFloodfill); - SetFloodfill (); - } + if (v4only) + return m_SupportedTransports & eNTCPV4; else - { - UpdateCaps (GetCaps () & ~i2p::data::RouterInfo::eFloodfill); - ResetFloodfill (); - } - } + return m_SupportedTransports & (eNTCPV4 | eNTCPV6); + } + + bool RouterInfo::IsSSU (bool v4only) const + { + if (v4only) + return m_SupportedTransports & eSSUV4; + else + return m_SupportedTransports & (eSSUV4 | eSSUV6); + } + + bool RouterInfo::IsNTCP2 (bool v4only) const + { + if (v4only) + return m_SupportedTransports & eNTCP2V4; + else + return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6); + } + + bool RouterInfo::IsV6 () const + { + return m_SupportedTransports & (eNTCPV6 | eSSUV6); + } + + bool RouterInfo::IsV4 () const + { + return m_SupportedTransports & (eNTCPV4 | eSSUV4 | eNTCP2V4); + } + + void RouterInfo::EnableV6 () + { + if (!IsV6 ()) + m_SupportedTransports |= eNTCPV6 | eSSUV6 | eNTCP2V6; + } + + void RouterInfo::EnableV4 () + { + if (!IsV4 ()) + m_SupportedTransports |= eNTCPV4 | eSSUV4 | eNTCP2V4; + } + + + void RouterInfo::DisableV6 () + { + if (IsV6 ()) + { + m_SupportedTransports &= ~(eNTCPV6 | eSSUV6 | eNTCP2V6); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + { + auto addr = *it; + if (addr->host.is_v6 ()) + it = m_Addresses->erase (it); + else + ++it; + } + } + } + + void RouterInfo::DisableV4 () + { + if (IsV4 ()) + { + m_SupportedTransports &= ~(eNTCPV4 | eSSUV4 | eNTCP2V4); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + { + auto addr = *it; + if (addr->host.is_v4 ()) + it = m_Addresses->erase (it); + else + ++it; + } + } + } + + + bool RouterInfo::UsesIntroducer () const + { + return m_Caps & Caps::eUnreachable; // non-reachable + } + + std::shared_ptr RouterInfo::GetNTCPAddress (bool v4only) const + { + return GetAddress ( + [v4only](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportNTCP) && !address->IsNTCP2Only () && (!v4only || address->host.is_v4 ()); + }); + } + + std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const + { + return GetAddress ( + [v4only](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && (!v4only || address->host.is_v4 ()); + }); + } + + std::shared_ptr RouterInfo::GetSSUV6Address () const + { + return GetAddress ( + [](std::shared_ptr address)->bool + { + return (address->transportStyle == eTransportSSU) && address->host.is_v6 (); + }); + } + + template + std::shared_ptr RouterInfo::GetAddress (Filter filter) const + { + // TODO: make it more generic using comparator +#if (BOOST_VERSION >= 105300) + auto addresses = boost::atomic_load (&m_Addresses); +#else + auto addresses = m_Addresses; +#endif + for (const auto& address : *addresses) + if (filter (address)) return address; - void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const - { - uint8_t len = str.size (); - s.write ((char *)&len, 1); - s.write (str.c_str (), len); + return nullptr; } - std::shared_ptr LocalRouterInfo::NewBuffer () const + std::shared_ptr RouterInfo::GetNTCP2Address (bool publishedOnly, bool v4only) const { - return std::make_shared (); + return GetAddress ( + [publishedOnly, v4only](std::shared_ptr address)->bool + { + return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()) && (!v4only || address->host.is_v4 ()); + }); } - std::shared_ptr LocalRouterInfo::NewAddress () const + std::shared_ptr RouterInfo::GetProfile () const { - return std::make_shared
(); + if (!m_Profile) + m_Profile = GetRouterProfile (GetIdentHash ()); + return m_Profile; } - RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const + void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { - return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); + auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); + if (encryptor) + encryptor->Encrypt (data, encrypted, ctx, true); } - - 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..084ad8a7 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,30 +1,16 @@ -/* -* 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 ROUTER_INFO_H__ #define ROUTER_INFO_H__ #include #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" namespace i2p { @@ -33,7 +19,6 @@ namespace data const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; - const char ROUTER_INFO_PROPERTY_VERSION[] = "router.version"; const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; @@ -44,98 +29,56 @@ 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_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_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 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_SSU2_NON_PUBLISHED = 15; + const char CAPS_FLAG_SSU_TESTING = 'B'; + const char CAPS_FLAG_SSU_INTRODUCER = 'C'; - 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 - + const int MAX_RI_BUFFER_SIZE = 2048; class RouterInfo: public RoutingDestination { public: - enum SupportedTransportsIdx + enum SupportedTranports { - eNTCP2V4Idx = 0, - eNTCP2V6Idx, - eSSU2V4Idx, - eSSU2V6Idx, - eNTCP2V6MeshIdx, - eNumTransports + eNTCPV4 = 0x01, + eNTCPV6 = 0x02, + eSSUV4 = 0x04, + eSSUV6 = 0x08, + eNTCP2V4 = 0x10, + eNTCP2V6 = 0x20 }; -#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 - eAllTransports = 0xFF - }; - typedef uint8_t CompatibleTransports; - enum Caps { eFloodfill = 0x01, eHighBandwidth = 0x02, eExtraBandwidth = 0x04, eReachable = 0x08, - eHidden = 0x10, - eUnreachable = 0x20 - }; - - enum Congestion - { - eLowCongestion = 0, - eMediumCongestion, - eHighCongestion, - eRejectAll - }; - - enum AddressCaps - { - eV4 = 0x01, - eV6 = 0x02, - eSSUTesting = 0x04, - eSSUIntroducer = 0x08 + eSSUTesting = 0x10, + eSSUIntroducer = 0x20, + eHidden = 0x40, + eUnreachable = 0x80 }; enum TransportStyle { eTransportUnknown = 0, - eTransportNTCP2, - eTransportSSU2 + eTransportNTCP, + eTransportSSU }; + typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { - Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; - IdentHash iH; + Introducer (): iExp (0) {}; + boost::asio::ip::address iHost; + int iPort; + IntroKey iKey; uint32_t iTag; uint32_t iExp; }; @@ -143,29 +86,37 @@ namespace data struct SSUExt { int mtu; + IntroKey key; // intro key for SSU std::vector introducers; }; + struct NTCP2Ext + { + Tag<32> staticKey; + Tag<16> iv; + bool isPublished = false; + bool isNTCP2Only = false; + }; + struct Address { TransportStyle transportStyle; boost::asio::ip::address host; - Tag<32> s, i; // keys, i is first 16 bytes for NTCP2 and 32 bytes intro key for SSU int port; uint64_t date; - uint8_t caps; - bool published = false; + uint8_t cost; std::unique_ptr ssu; // not null for SSU + std::unique_ptr ntcp2; // not null for NTCP2 bool IsCompatible (const boost::asio::ip::address& other) const { - return (IsV4 () && other.is_v4 ()) || - (IsV6 () && other.is_v6 ()); + return (host.is_v4 () && other.is_v4 ()) || + (host.is_v6 () && other.is_v6 ()); } bool operator==(const Address& other) const { - return transportStyle == other.transportStyle && + return transportStyle == other.transportStyle && IsNTCP2 () == other.IsNTCP2 () && host == other.host && port == other.port; } @@ -174,237 +125,116 @@ namespace data return !(*this == other); } - bool IsNTCP2 () const { return transportStyle == eTransportNTCP2; }; - bool IsSSU2 () const { return transportStyle == eTransportSSU2; }; - bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; - bool IsReachableSSU () const { return (bool)ssu && (published || UsesIntroducer ()); }; - bool UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); }; - - bool IsIntroducer () const { return caps & eSSUIntroducer; }; - bool IsPeerTesting () const { return caps & eSSUTesting; }; - - bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); }; - bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); }; + bool IsNTCP2 () const { return (bool)ntcp2; }; + bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; }; + bool IsNTCP2Only () const { return ntcp2 && ntcp2->isNTCP2Only; }; }; + typedef std::list > Addresses; - class Buffer: public std::array - { - public: - - Buffer () = default; - Buffer (const uint8_t * buf, size_t len); - Buffer (const Buffer& other): Buffer (other.data (), other.m_BufferLen) {}; - - size_t GetBufferLen () const { return m_BufferLen; }; - void SetBufferLen (size_t len) { m_BufferLen = len; }; - - private: - - size_t m_BufferLen = 0; - }; - - typedef std::array, eNumTransports> Addresses; -#ifdef __cpp_lib_atomic_shared_ptr - typedef std::shared_ptr AddressesPtr; -#else - typedef boost::shared_ptr AddressesPtr; -#endif + RouterInfo (); RouterInfo (const std::string& fullPath); - RouterInfo (const RouterInfo& ) = delete; - RouterInfo& operator=(const RouterInfo& ) = delete; - RouterInfo (std::shared_ptr&& buf, size_t len); - RouterInfo (const uint8_t * buf, size_t len); - virtual ~RouterInfo (); + RouterInfo (const RouterInfo& ) = default; + RouterInfo& operator=(const RouterInfo& ) = default; + RouterInfo (const uint8_t * buf, int len); + ~RouterInfo (); std::shared_ptr GetRouterIdentity () const { return m_RouterIdentity; }; void SetRouterIdentity (std::shared_ptr identity); 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 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; - std::shared_ptr GetPublishedNTCP2V4Address () const; - std::shared_ptr GetPublishedNTCP2V6Address () const; - std::shared_ptr GetYggdrasilAddress () const; - std::shared_ptr GetSSU2V4Address () const; - std::shared_ptr GetSSU2V6Address () const; - std::shared_ptr GetSSU2Address (bool v4) const; + Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr + std::shared_ptr GetNTCPAddress (bool v4only = true) const; + std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; + std::shared_ptr GetSSUAddress (bool v4only = true) const; + std::shared_ptr GetSSUV6Address () const; - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps); // non published - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, - const boost::asio::ip::address& host, int port); // published - void RemoveNTCP2Address (bool v4); - void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, int port, uint8_t caps); // non published - void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, - const boost::asio::ip::address& host, int port); // published - void RemoveSSU2Address (bool v4); - void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps - void UpdateSupportedTransports (); - void UpdateIntroducers (uint64_t ts); // ts in seconds - bool IsFloodfill () const { return m_IsFloodfill; }; - void SetFloodfill () { m_IsFloodfill = true; }; - void ResetFloodfill () { m_IsFloodfill = false; }; - bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; + void AddNTCPAddress (const char * host, int port); + 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 = boost::asio::ip::address(), int port = 0); + bool AddIntroducer (const Introducer& introducer); + bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); + void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only + void DeleteProperty (const std::string& key); // called from RouterContext only + std::string GetProperty (const std::string& key) const; // called from RouterContext only + void ClearProperties () { m_Properties.clear (); }; + bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; + bool IsReachable () const { return m_Caps & Caps::eReachable; }; + bool IsNTCP (bool v4only = true) const; + bool IsSSU (bool v4only = true) const; 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 IsMesh () const { return m_SupportedTransports & eNTCP2V6Mesh; }; + bool IsV6 () const; + bool IsV4 () const; void EnableV6 (); void DisableV6 (); void EnableV4 (); void DisableV4 (); - void EnableMesh (); - void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; - 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 UsesIntroducer () const; + bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; + bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; - bool IsEligibleFloodfill () const; - bool IsDeclaredFloodfill () const { return m_Caps & RouterInfo::eFloodfill; }; - bool IsPublished (bool v4) const; - bool IsPublishedOn (CompatibleTransports transports) const; - bool IsNAT2NATOnly (const RouterInfo& other) const; // only NAT-to-NAT connection is possible - bool IsSSU2PeerTesting (bool v4) const; - bool IsSSU2Introducer (bool v4) const; - bool IsHighCongestion (bool highBandwidth) const; uint8_t GetCaps () const { return m_Caps; }; - char GetBandwidthCap() const { return m_BandwidthCap; }; - void SetCaps (uint8_t caps) { m_Caps = caps; }; + void SetCaps (uint8_t caps); + void SetCaps (const char * caps); - Congestion GetCongestion () const { return m_Congestion; }; - void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; - void ExcludeReachableTransports (CompatibleTransports transports) { m_ReachableTransports &= ~transports; }; - const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; }; - const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary - size_t GetBufferLen () const { return m_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; }; + const uint8_t * GetBuffer () const { return m_Buffer; }; + const uint8_t * LoadBuffer (); // load if necessary + int GetBufferLen () const { return m_BufferLen; }; + void CreateBuffer (const PrivateKeys& privateKeys); 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, int len); + void DeleteBuffer () { delete[] m_Buffer; 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 */ - bool IsFamily (FamilyID famid) const; + /** return true if we are in a router family and the signature is valid */ + bool IsFamily(const std::string & fam) const; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; bool IsDestination () const { return false; }; - protected: - - 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 RefreshTimestamp (); - CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; - void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; - void SetCongestion (Congestion c) { m_Congestion = c; }; - private: - bool LoadFile (const std::string& fullPath); - void ReadFromFile (const std::string& fullPath); - bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed + bool LoadFile (); + void ReadFromFile (); + 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); + void WriteToStream (std::ostream& s) const; + size_t ReadString (char* str, size_t len, std::istream& s) const; + void WriteString (const std::string& str, std::ostream& s) const; + void ExtractCaps (const char * value); 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; - 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 - { - public: - - 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 DeleteProperty (const std::string& key); - std::string GetProperty (const std::string& key) const; - void ClearProperties () override { m_Properties.clear (); }; - void UpdateFloodfillProperty (bool floodfill); - - bool AddSSU2Introducer (const Introducer& introducer, bool v4); - bool RemoveSSU2Introducer (const IdentHash& h, bool v4); - bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); - - private: - - void WriteToStream (std::ostream& s) const; void UpdateCapsProperty (); - void WriteString (const std::string& str, std::ostream& s) const; - std::shared_ptr NewBuffer () const override; - std::shared_ptr
NewAddress () const override; - RouterInfo::AddressesPtr NewAddresses () const override; - std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: + std::string m_FullPath, m_Family; + std::shared_ptr m_RouterIdentity; + uint8_t * m_Buffer; + size_t m_BufferLen; + uint64_t m_Timestamp; + boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; + bool m_IsUpdated, m_IsUnreachable; + uint8_t m_SupportedTransports, m_Caps; + mutable std::shared_ptr m_Profile; }; } } diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp new file mode 100644 index 00000000..daf7e1e9 --- /dev/null +++ b/libi2pd/SSU.cpp @@ -0,0 +1,798 @@ +#include +#include +#include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "NetDb.hpp" +#include "SSU.h" + +namespace i2p +{ +namespace transport +{ + + SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): + m_OnlyV6(true), m_IsRunning(false), + m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), + m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), + m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), + m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), + m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), + m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), + m_TerminationTimerV6 (m_ServiceV6) + { + OpenSocketV6 (); + } + + SSUServer::SSUServer (int port): + m_OnlyV6(false), m_IsRunning(false), + m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), + m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), + 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_PeerTestsCleanupTimer (m_Service), + m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) + { + OpenSocket (); + if (context.SupportsV6 ()) + OpenSocketV6 (); + } + + SSUServer::~SSUServer () + { + } + + void SSUServer::OpenSocket () + { + 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); + } + + void SSUServer::OpenSocketV6 () + { + 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)); + m_SocketV6.bind (m_EndpointV6); + } + + void SSUServer::Start () + { + m_IsRunning = true; + if (!m_OnlyV6) + { + m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); + m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); + m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); + ScheduleTermination (); + } + if (context.SupportsV6 ()) + { + m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); + m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); + m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); + ScheduleTerminationV6 (); + } + SchedulePeerTestsCleanupTimer (); + ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers + } + + void SSUServer::Stop () + { + DeleteAllSessions (); + m_IsRunning = false; + m_TerminationTimer.cancel (); + m_TerminationTimerV6.cancel (); + m_Service.stop (); + m_Socket.close (); + m_ServiceV6.stop (); + m_SocketV6.close (); + m_ReceiversService.stop (); + m_ReceiversServiceV6.stop (); + if (m_ReceiversThread) + { + m_ReceiversThread->join (); + delete m_ReceiversThread; + m_ReceiversThread = nullptr; + } + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + if (m_ReceiversThreadV6) + { + m_ReceiversThreadV6->join (); + delete m_ReceiversThreadV6; + m_ReceiversThreadV6 = nullptr; + } + if (m_ThreadV6) + { + m_ThreadV6->join (); + delete m_ThreadV6; + m_ThreadV6 = nullptr; + } + } + + void SSUServer::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ()); + } + } + } + + void SSUServer::RunV6 () + { + while (m_IsRunning) + { + try + { + m_ServiceV6.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ()); + } + } + } + + void SSUServer::RunReceivers () + { + while (m_IsRunning) + { + try + { + m_ReceiversService.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); + } + } + } + + void SSUServer::RunReceiversV6 () + { + while (m_IsRunning) + { + try + { + m_ReceiversServiceV6.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); + } + } + } + + void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) + { + m_Relays[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) + { + if (to.protocol () == boost::asio::ip::udp::v4()) + m_Socket.send_to (boost::asio::buffer (buf, len), to); + else + m_SocketV6.send_to (boost::asio::buffer (buf, len), to); + } + + void SSUServer::Receive () + { + SSUPacket * packet = new SSUPacket (); + 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 = new SSUPacket (); + 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) + { + 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 = new SSUPacket (); + 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: ", ec.message ()); + delete packet; + break; + } + } + } + + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); + Receive (); + } + else + { + delete packet; + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogError, "SSU: receive error: ", 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) + { + 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 = new SSUPacket (); + 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: ", ec.message ()); + delete packet; + break; + } + } + } + + m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); + ReceiveV6 (); + } + else + { + delete packet; + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ()); + m_SocketV6.close (); + OpenSocketV6 (); + ReceiveV6 (); + } + } + } + + void SSUServer::HandleReceivedPackets (std::vector packets, + std::map > * sessions) + { + 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) + { + 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"); + } + } + session->ProcessNextMessage (packet->buf, packet->len, packet->from); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); + if (session) session->FlushData (); + session = nullptr; + } + delete packet; + } + if (session) session->FlushData (); + } + + std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const + { + if (!router) return nullptr; + auto address = router->GetSSUAddress (true); // v4 only + if (!address) return nullptr; + auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); + if (session || !context.SupportsV6 ()) + return session; + // try v6 + address = router->GetSSUV6Address (); + if (!address) return nullptr; + return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); + } + + 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; + } + + void SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) + { + auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); + if (address) + CreateSession (router, address->host, address->port, peerTest); + else + LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + } + + void SSUServer::CreateSession (std::shared_ptr router, + const boost::asio::ip::address& addr, int port, bool peerTest) + { + if (router) + { + if (router->UsesIntroducer ()) + m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread + else + { + boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); + auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service; + s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); + } + } + } + + 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, bool peerTest) + { + if (router && router->UsesIntroducer ()) + { + auto address = router->GetSSUAddress (true); // v4 only for now + if (address) + { + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto it = m_Sessions.find (remoteEndpoint); + // check if session is presented already + if (it != m_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 + for (int i = 0; i < numIntroducers; i++) + { + auto intr = &(address->ssu->introducers[i]); + 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 ()) // ipv4 only + { + if (!introducer) introducer = intr; // we pick first one for now + it = m_Sessions.find (ep); + if (it != m_Sessions.end ()) + { + introducerSession = it->second; + break; + } + } + } + if (!introducer) + { + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 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); + m_Sessions[introducerEndpoint] = introducerSession; + } +#if BOOST_VERSION >= 104900 + if (!address->host.is_unspecified () && address->port) +#endif + { + // create session + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + m_Sessions[remoteEndpoint] = session; + + // introduce + LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), + "] through introducer ", introducer->iHost, ":", introducer->iPort); + session->WaitForIntroduction (); + if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable + { + 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"); + } + else + LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + } + } + + 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::set SSUServer::FindIntroducers (int maxNumIntroducers) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::set ret; + for (int i = 0; i < maxNumIntroducers; i++) + { + auto session = GetRandomV4Session ( + [&ret, ts](std::shared_ptr session)->bool + { + return session->GetRelayTag () && !ret.count (session.get ()) && + session->GetState () == eSessionStateEstablished && + ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION; + } + ); + if (session) + { + ret.insert (session.get ()); + break; + } + } + return ret; + } + + 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)); + } + + void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + // timeout expired + if (i2p::context.GetStatus () == eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimer (); + return; + } + if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore + // we are firewalled + if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); + std::list newList; + size_t numIntroducers = 0; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (const auto& it : m_Introducers) + { + auto session = FindSession (it); + if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) + { + session->SendKeepAlive (); + newList.push_back (it); + numIntroducers++; + } + else + i2p::context.RemoveIntroducer (it); + } + + if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) + { + // create new + auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); + for (const auto& it1: introducers) + { + 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 (); + if (i2p::context.AddIntroducer (introducer)) + { + newList.push_back (ep); + if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; + } + } + } + m_Introducers = newList; + if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) + { + auto introducer = i2p::data::netdb.GetRandomIntroducer (); + if (introducer) + CreateSession (introducer); + } + ScheduleIntroducersUpdateTimer (); + } + } + + 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"); + SchedulePeerTestsCleanupTimer (); + } + } + + void SSUServer::ScheduleTermination () + { + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_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 (); + }); + } + ScheduleTermination (); + } + } + + void SSUServer::ScheduleTerminationV6 () + { + m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_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_ServiceV6.post ([session] + { + LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); + session->Failed (); + }); + } + ScheduleTerminationV6 (); + } + } +} +} + diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h new file mode 100644 index 00000000..10e5ec06 --- /dev/null +++ b/libi2pd/SSU.h @@ -0,0 +1,138 @@ +#ifndef SSU_H__ +#define SSU_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Crypto.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_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 (const boost::asio::ip::address & addr, int port); // ipv6 only constructor + ~SSUServer (); + void Start (); + void Stop (); + void CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); + void CreateSession (std::shared_ptr router, + const boost::asio::ip::address& addr, int port, bool peerTest = false); + void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); + std::shared_ptr FindSession (std::shared_ptr router) const; + 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; }; + boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; }; + const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; + 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 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 RunV6 (); + 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, bool peerTest = false); + template + std::shared_ptr GetRandomV4Session (Filter filter); + template + std::shared_ptr GetRandomV6Session (Filter filter); + + std::set FindIntroducers (int maxNumIntroducers); + void ScheduleIntroducersUpdateTimer (); + void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); + + 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 + }; + + bool m_OnlyV6; + bool m_IsRunning; + std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread, * m_ReceiversThreadV6; + boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService, m_ReceiversServiceV6; + boost::asio::io_service::work m_Work, m_WorkV6, 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_PeerTestsCleanupTimer, + m_TerminationTimer, m_TerminationTimerV6; + std::list m_Introducers; // 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 + + 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 deleted file mode 100644 index fc2355a5..00000000 --- a/libi2pd/SSU2.cpp +++ /dev/null @@ -1,1802 +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 "Log.h" -#include "RouterContext.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "Config.h" -#include "SSU2.h" - -namespace i2p -{ -namespace transport -{ - SSU2Server::SSU2Server (): - RunnableServiceWithWork ("SSU2"), m_ReceiveService ("SSU2r"), - m_SocketV4 (m_ReceiveService.GetService ()), m_SocketV6 (m_ReceiveService.GetService ()), - m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()), - m_TerminationTimer (GetService ()), m_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_IsThroughProxy (false) - { - } - - void SSU2Server::Start () - { - 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) - { - 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) - { - uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); - if (ssu2Port) port = ssu2Port; - else - { - uint16_t p; i2p::config::GetOption ("port", p); - if (p) port = p; - } - } - if (port) - { - if (address->IsV4 ()) - { - found = true; - LogPrint (eLogDebug, "SSU2: Opening IPv4 socket at Start"); - OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV4, port)); - boost::asio::post (m_ReceiveService.GetService (), - [this]() - { - Receive (m_SocketV4); - }); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers - } - if (address->IsV6 ()) - { - found = true; - LogPrint (eLogDebug, "SSU2: Opening IPv6 socket at Start"); - OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV6, port)); - boost::asio::post (m_ReceiveService.GetService (), - [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"); - } - } - if (found) - { - if (m_IsThroughProxy) - ConnectToProxy (); - m_ReceiveService.Start (); - } - ScheduleTermination (); - ScheduleCleanup (); - ScheduleResend (false); - } - } - - void SSU2Server::Stop () - { - if (IsRunning ()) - { - m_TerminationTimer.cancel (); - m_CleanupTimer.cancel (); - m_ResendTimer.cancel (); - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimerV6.cancel (); - } - - auto sessions = m_Sessions; - for (auto& it: sessions) - { - it.second->RequestTermination (eSSU2TerminationReasonRouterShutdown); - it.second->Done (); - } - - if (context.SupportsV4 () || context.SupportsV6 ()) - m_ReceiveService.Stop (); - m_SocketV4.close (); - m_SocketV6.close (); - - if (m_UDPAssociateSocket) - { - m_UDPAssociateSocket->close (); - m_UDPAssociateSocket.reset (nullptr); - } - - StopIOService (); - - m_Sessions.clear (); - m_SessionsByRouterHash.clear (); - m_PendingOutgoingSessions.clear (); - m_Relays.clear (); - m_PeerTests.clear (); - m_Introducers.clear (); - m_IntroducersV6.clear (); - m_ConnectedRecently.clear (); - m_RequestedPeerTests.clear (); - - m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue); - m_ReceivedPacketsQueue.clear (); - } - - void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) - { - if (localAddress.is_unspecified ()) return; - if (localAddress.is_v4 ()) - { - m_AddressV4 = localAddress; - 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.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 - } - return socket; - } - - 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, - 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 - { - 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; - packets.push_back (packet); - while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH) - { - packet = m_PacketsPool.AcquireMt (); - packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); - if (!ec) - { - i2p::transport::transports.UpdateReceivedBytes (packet->len); - if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE) - packets.push_back (packet); - else // drop too short packets - m_PacketsPool.ReleaseMt (packet); - moreBytes = socket.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU2: receive_from error: code ", ec.value(), ": ", ec.message ()); - m_PacketsPool.ReleaseMt (packet); - break; - } - } - InsertToReceivedPacketsQueue (packets); - } - else - InsertToReceivedPacketsQueue (packet); - Receive (socket); - } - else - { - m_PacketsPool.ReleaseMt (packet); - 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); - } - } - } - } - - void SSU2Server::HandleReceivedPackets (std::list&& packets) - { - 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 (); - } - - void SSU2Server::InsertToReceivedPacketsQueue (Packet * packet) - { - if (!packet) return; - bool empty = false; - { - std::lock_guard l(m_ReceivedPacketsQueueMutex); - empty = m_ReceivedPacketsQueue.empty (); - m_ReceivedPacketsQueue.push_back (packet); - } - if (empty) - boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); - } - - void SSU2Server::InsertToReceivedPacketsQueue (std::list& packets) - { - if (packets.empty ()) return; - size_t queueSize = 0; - { - std::lock_guard l(m_ReceivedPacketsQueueMutex); - queueSize = m_ReceivedPacketsQueue.size (); - if (queueSize < SSU2_MAX_RECEIVED_QUEUE_SIZE) - m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets); - else - { - LogPrint (eLogError, "SSU2: Received queue size ", queueSize, " exceeds max size", SSU2_MAX_RECEIVED_QUEUE_SIZE); - m_PacketsPool.ReleaseMt (packets); - queueSize = 0; // invoke processing just in case - } - } - if (!queueSize) - boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); - } - - void SSU2Server::HandleReceivedPacketsQueue () - { - std::list receivedPackets; - { - std::lock_guard l(m_ReceivedPacketsQueueMutex); - m_ReceivedPacketsQueue.swap (receivedPackets); - } - HandleReceivedPackets (std::move (receivedPackets)); - } - - bool SSU2Server::AddSession (std::shared_ptr session) - { - if (session) - { - if (m_Sessions.emplace (session->GetConnID (), session).second) - { - if (session->GetState () != eSSU2SessionStatePeerTest) - AddSessionByRouterHash (session); - return true; - } - } - return false; - } - - void SSU2Server::RemoveSession (uint64_t connID) - { - 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; - 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) - { - auto ident = session->GetRemoteIdentity (); - if (ident) - { - std::shared_ptr oldSession; - { - std::lock_guard l(m_SessionsByRouterHashMutex); - auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); - if (!ret.second) - { - oldSession = ret.first->second.lock (); - // update session - ret.first->second = session; - } - } - if (oldSession && oldSession != session) - { - // session already exists - LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); - // move unsent msgs to new session - oldSession->MoveSendQueue (session); - // terminate existing - boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession)); - } - } - } - } - - bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) - { - if (!session) return false; - std::lock_guard l(m_PendingOutgoingSessionsMutex); - return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; - } - - std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) - { - 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; - } - - void SSU2Server::AddRelay (uint32_t tag, std::shared_ptr relay) - { - m_Relays.emplace (tag, relay); - } - - void SSU2Server::RemoveRelay (uint32_t tag) - { - m_Relays.erase (tag); - } - - std::shared_ptr SSU2Server::FindRelaySession (uint32_t tag) - { - 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); - } - 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; - uint64_t connID; - memcpy (&connID, buf, 8); - connID ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); - if (!m_LastSession || m_LastSession->GetConnID () != connID) - { - if (m_LastSession) m_LastSession->FlushData (); - auto it = m_Sessions.find (connID); - if (it != m_Sessions.end ()) - m_LastSession = it->second; - else - m_LastSession = nullptr; - } - if (m_LastSession) - { - switch (m_LastSession->GetState ()) - { - case eSSU2SessionStateEstablished: - case eSSU2SessionStateSessionConfirmedSent: - m_LastSession->ProcessData (buf, len, senderEndpoint); - break; - case eSSU2SessionStateSessionCreatedSent: - if (!m_LastSession->ProcessSessionConfirmed (buf, len)) - { - m_LastSession->Done (); - m_LastSession = nullptr; - } - break; - case eSSU2SessionStateIntroduced: - if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) - 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; - } - 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 ()); - } - } - else - { - // check pending sessions if it's SessionCreated or Retry - 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); - 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 ()) - { - // 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); - } - } - - 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); - } - } - - bool SSU2Server::CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest) - { - auto s = FindPendingOutgoingSession (ep); - if (s) - { - if (peerTest) - { - // if peer test requested add it to the list for pending session - auto onEstablished = s->GetOnEstablished (); - if (onEstablished) - s->SetOnEstablished ([s, onEstablished]() - { - onEstablished (); - s->SendPeerTest (); - }); - else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); - } - return true; - } - return false; - } - - bool SSU2Server::CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest) - { - if (router && address) - { - // check if no session - auto 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)); - else - return false; - } - else - return false; - return true; - } - - void SSU2Server::ConnectThroughIntroducer (std::shared_ptr session) - { - if (!session) return; - auto address = session->GetAddress (); - if (!address) return; - session->WaitForIntroduction (); - auto ts = i2p::util::GetSecondsSinceEpoch (); - std::vector indices; int i = 0; - // try to find existing session first - for (auto& it: address->ssu->introducers) - { - if (it.iTag && ts < it.iExp) - { - auto s = FindSession (it.iH); - if (s) - { - auto addr = s->GetAddress (); - if (addr && addr->IsIntroducer ()) - { - s->Introduce (session, it.iTag); - return; - } - } - else - indices.push_back(i); - } - i++; - } - // we have to start a new session to an introducer - std::vector newRouters; - std::shared_ptr r; - std::shared_ptr addr; - uint32_t relayTag = 0; - if (!indices.empty ()) - { - if (indices.size () > 1) - std::shuffle (indices.begin(), indices.end(), m_Rng); - - for (auto ind: indices) - { - const auto& introducer = address->ssu->introducers[ind]; - // introducer is not expired, because in indices - r = i2p::data::netdb.FindRouter (introducer.iH); - if (r) - { - if (r->IsPublishedOn (i2p::context.GetRouterInfo ().GetCompatibleTransports (false) & // outgoing - (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))) - { - relayTag = introducer.iTag; - addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); - if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port && - !i2p::transport::transports.IsInReservedRange(addr->host)) - break; - else - { - // address is invalid or not intrudcer, try another SSU2 address if exists - if (address->IsV4 ()) - { - if (i2p::context.SupportsV6 ()) - addr = r->GetSSU2V6Address (); - } - else - { - if (i2p::context.SupportsV4 ()) - addr = r->GetSSU2V4Address (); - } - if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port && - !i2p::transport::transports.IsInReservedRange(addr->host)) - break; - else - { - // all addresses are invalid, try next introducer - relayTag = 0; - addr = nullptr; - r = nullptr; - } - } - } - else - r = nullptr; - } - else if (!i2p::data::IsRouterBanned (introducer.iH)) - newRouters.push_back (introducer.iH); - } - } - if (r) - { - if (relayTag && addr) - { - // introducer and tag found connect to it through SSU2 - auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); - if (!s) - { - s = std::make_shared (*this, r, addr); - s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); }); - s->Connect (); - } - else - { - auto onEstablished = s->GetOnEstablished (); - if (onEstablished) - s->SetOnEstablished ([session, s, relayTag, onEstablished]() - { - onEstablished (); - s->Introduce (session, relayTag); - }); - else - s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); }); - } - } - else - session->Done (); - } - else - { - // introducers not found, try to request them - for (auto& it: newRouters) - i2p::data::netdb.RequestDestination (it); - session->Done (); // don't wait for connect timeout - } - } - - 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.async_wait (std::bind (&SSU2Server::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void SSU2Server::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - - { - std::lock_guard l(m_PendingOutgoingSessionsMutex); - for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) - { - 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); - } - else - it++; - } - - for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) - { - if (ts > it->second.second) - it = m_IncomingTokens.erase (it); - else - it++; - } - - for (auto it = m_OutgoingTokens.begin (); it != m_OutgoingTokens.end (); ) - { - if (ts > it->second.second) - it = m_OutgoingTokens.erase (it); - else - it++; - } - - for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); ) - { - if (ts > it->second + SSU2_MAX_HOLE_PUNCH_EXPIRATION) - it = m_ConnectedRecently.erase (it); - else - it++; - } - - for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();) - { - if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) - it = m_RequestedPeerTests.erase (it); - else - it++; - } - - { - std::lock_guard l(m_SessionsByRouterHashMutex); - for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) - { - if (it->second.expired ()) - it = m_SessionsByRouterHash.erase (it); - else - it++; - } - } - - m_PacketsPool.CleanUpMt (); - m_SentPacketsPool.CleanUp (); - m_IncompleteMessagesPool.CleanUp (); - m_FragmentsPool.CleanUp (); - ScheduleCleanup (); - } - } - - void SSU2Server::ScheduleResend (bool more) - { - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? - (SSU2_RESEND_CHECK_MORE_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE): - (SSU2_RESEND_CHECK_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE))); - m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer, - this, std::placeholders::_1)); - } - - void SSU2Server::HandleResendTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - size_t resentPacketsNum = 0; - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - 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); - } - } - - void SSU2Server::UpdateOutgoingToken (const boost::asio::ip::udp::endpoint& ep, uint64_t token, uint32_t exp) - { - m_OutgoingTokens[ep] = {token, exp}; - } - - uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) - { - auto it = m_OutgoingTokens.find (ep); - if (it != m_OutgoingTokens.end ()) - { - if (i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_THRESHOLD > it->second.second) - { - // 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); - } - 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)); - 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 deleted file mode 100644 index a8598ce3..00000000 --- a/libi2pd/SSU2.h +++ /dev/null @@ -1,228 +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_H__ -#define SSU2_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "util.h" -#include "SSU2Session.h" -#include "SSU2OutOfSession.h" -#include "Socks5.h" - -namespace i2p -{ -namespace transport -{ - const int SSU2_TERMINATION_CHECK_TIMEOUT = 23; // in seconds - const int SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // in seconds - const int SSU2_CLEANUP_INTERVAL = 72; // in seconds - const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds - const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds - const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 4; // in milliseconds - const int SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE = 9; // in milliseconds - const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time - const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024; - const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024; - const size_t SSU2_MAX_NUM_INTRODUCERS = 3; - const size_t SSU2_MIN_RECEIVED_PACKET_SIZE = 40; // 16 byte short header + 8 byte minimum payload + 16 byte MAC - const size_t SSU2_MAX_RECEIVED_QUEUE_SIZE = 2500; // in packets - const int SSU2_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour - const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes - const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds - const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds - const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds - const int SSU2_MIN_HOLE_PUNCH_EXPIRATION = 30; // in seconds - const int SSU2_MAX_HOLE_PUNCH_EXPIRATION = 160; // in seconds - const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 64; - - class SSU2Server: private i2p::util::RunnableServiceWithWork - { - struct Packet - { - uint8_t buf[SSU2_MAX_PACKET_SIZE]; - 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 (); }; - void Start () { StartIOService (); }; - void Stop () { StopIOService (); }; - }; - - public: - - SSU2Server (); - ~SSU2Server () {}; - - 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 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); - - bool AddSession (std::shared_ptr session); - void RemoveSession (uint64_t connID); - void RequestRemoveSession (uint64_t connID); - void AddSessionByRouterHash (std::shared_ptr session); - bool AddPendingOutgoingSession (std::shared_ptr session); - void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep); - std::shared_ptr FindSession (const i2p::data::IdentHash& ident); - 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 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); - - 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 GetIncomingToken (const boost::asio::ip::udp::endpoint& ep); - std::pair NewIncomingToken (const boost::asio::ip::udp::endpoint& ep); - - void RescheduleIntroducersUpdateTimer (); - void RescheduleIntroducersUpdateTimerV6 (); - - i2p::util::MemoryPool& GetSentPacketsPool () { return m_SentPacketsPool; }; - i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; - i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; - - private: - - boost::asio::ip::udp::socket& OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint); - void Receive (boost::asio::ip::udp::socket& socket); - void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred, - Packet * packet, boost::asio::ip::udp::socket& socket); - void HandleReceivedPackets (std::list&& packets); - void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void InsertToReceivedPacketsQueue (Packet * packet); - void InsertToReceivedPacketsQueue (std::list& packets); - void HandleReceivedPacketsQueue (); - - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - - void ScheduleCleanup (); - void HandleCleanupTimer (const boost::system::error_code& ecode); - - void ScheduleResend (bool more); - void HandleResendTimer (const boost::system::error_code& ecode); - - bool CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest); - void ConnectThroughIntroducer (std::shared_ptr session); - std::vector > FindIntroducers (int maxNumIntroducers, - bool v4, const std::unordered_set& excluded); - void UpdateIntroducers (bool v4); - void ScheduleIntroducersUpdateTimer (); - void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); - void ScheduleIntroducersUpdateTimerV6 (); - - void SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, - const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); - void ProcessNextPacketFromProxy (uint8_t * buf, size_t len); - void ConnectToProxy (); - void ReconnectToProxy (); - void HandshakeWithProxy (); - void ReadHandshakeWithProxyReply (); - void SendUDPAssociateRequest (); - void ReadUDPAssociateReply (); - void ReadUDPAssociateSocket (); // handle if closed by peer - - private: - - ReceiveService m_ReceiveService; - boost::asio::ip::udp::socket m_SocketV4, m_SocketV6; - boost::asio::ip::address m_AddressV4, m_AddressV6; - std::unordered_map > m_Sessions; - std::unordered_map > m_SessionsByRouterHash; - mutable std::mutex m_SessionsByRouterHashMutex; - std::map > m_PendingOutgoingSessions; - mutable std::mutex m_PendingOutgoingSessionsMutex; - std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) - std::unordered_map > m_Relays; // we are introducer, relay tag -> session - std::unordered_map, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob - std::list > m_Introducers, m_IntroducersV6; // introducers we are connected to - i2p::util::MemoryPoolMt m_PacketsPool; - i2p::util::MemoryPool m_SentPacketsPool; - i2p::util::MemoryPool m_IncompleteMessagesPool; - i2p::util::MemoryPool m_FragmentsPool; - boost::asio::deadline_timer m_TerminationTimer, m_CleanupTimer, m_ResendTimer, - m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6; - std::shared_ptr m_LastSession; - bool m_IsPublished; // if we maintain introducers - bool m_IsSyncClockFromPeers; - int64_t m_PendingTimeOffset; // during peer test - std::shared_ptr m_PendingTimeOffsetFrom; - std::mt19937 m_Rng; - std::map m_ConnectedRecently; // endpoint -> last activity time in seconds - mutable std::mutex m_ConnectedRecentlyMutex; - std::unordered_map, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) - std::list m_ReceivedPacketsQueue; - mutable std::mutex m_ReceivedPacketsQueueMutex; - i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; - i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; - i2p::crypto::ChaCha20Context m_ChaCha20; - - // proxy - bool m_IsThroughProxy; - uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE]; - std::unique_ptr m_ProxyEndpoint; - std::unique_ptr m_UDPAssociateSocket; - std::unique_ptr m_ProxyRelayEndpoint; - std::unique_ptr m_ProxyConnectRetryTimer; - - public: - - // for HTTP/I2PControl - const decltype(m_Sessions)& GetSSU2Sessions () const { return m_Sessions; }; - }; -} -} - -#endif diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp deleted file mode 100644 index 3760e329..00000000 --- a/libi2pd/SSU2OutOfSession.cpp +++ /dev/null @@ -1,345 +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 - 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 cb10f848..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() + m_MsgLocalExpirationTimeout < mts) - { - // drop null or expired message - if (msg) msg->Drop (); - m_SendQueue.pop_front (); - continue; - } - size_t len = msg->GetNTCP2Length () + 3; - if (len > m_MaxPayloadSize) // message too long - { - 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..3ad5e769 --- /dev/null +++ b/libi2pd/SSUData.cpp @@ -0,0 +1,513 @@ +#include +#include +#include "Log.h" +#include "Timestamp.h" +#include "NetDb.hpp" +#include "SSU.h" +#include "SSUData.h" +#ifdef WITH_EVENTS +#include "Event.h" +#endif + +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_IncompleteMessagesCleanupTimer (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 () + { + ScheduleIncompleteMessagesCleanup (); + } + + void SSUData::Stop () + { + m_ResendTimer.cancel (); + m_IncompleteMessagesCleanupTimer.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].reset (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, + std::unique_ptr(new IncompleteMessage (msg)))).first; + } + std::unique_ptr& incompleteMessage = it->second; + + // 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 = new Fragment (fragmentNum, buf, fragmentSize, isLast); + if (incompleteMessage->savedFragments.insert (std::unique_ptr(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_ReceivedMessages.insert (msgID); + m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); + if (!msg->IsExpired ()) + { +#ifdef WITH_EVENTS + QueueIntEvent("transport.recvmsg", m_Session.GetIdentHashBase64(), 1); +#endif + m_Handler.PutNextMessage (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, fragmentNum); + 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.count (msgID) > 0) + { + LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); + return; + } + if (m_SentMessages.empty ()) // schedule resend at first message only + ScheduleResend (); + + auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); + std::unique_ptr& 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) + { + Fragment * fragment = new Fragment; + fragment->fragmentNum = fragmentNum; + uint8_t * buf = fragment->buf; + uint8_t * payload = 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 - buf; + if (size & 0x0F) // make sure 16 bytes boundary + size = ((size >> 4) + 1) << 4; // (/16 + 1)*16 + fragment->len = size; + fragments.push_back (std::unique_ptr (fragment)); + + // encrypt message with session key + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size); + 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, int fragmentNum) + { + if (fragmentNum > 64) + { + LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); + 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; + div_t d = div (fragmentNum, 7); + memset (payload, 0x80, d.quot); // 0x80 means non-last + payload += d.quot; + *payload = 0x01 << d.rem; // set corresponding bit + payload++; + *payload = 0; // number of fragments + + size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1) + // 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) + { + 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.Send (f->buf, f->len); // resend + numResent++; + } + catch (boost::system::system_error& ec) + { + LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ()); + } + } + + it->second->numResends++; + it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; + ++it; + } + else + { + LogPrint (eLogInfo, "SSU: message 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::ScheduleIncompleteMessagesCleanup () + { + m_IncompleteMessagesCleanupTimer.cancel (); + m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); + auto s = m_Session.shared_from_this(); + m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) + { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); + } + + void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + 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; + } + // decay + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || + i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) + m_ReceivedMessages.clear (); + + ScheduleIncompleteMessagesCleanup (); + } + } +} +} + diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h new file mode 100644 index 00000000..fbd167bf --- /dev/null +++ b/libi2pd/SSUData.h @@ -0,0 +1,131 @@ +#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; + #ifdef MESHNET + const size_t SSU_MTU_V6 = 1286; + #else + const size_t SSU_MTU_V6 = 1488; + #endif + 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 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::unique_ptr& f1, const std::unique_ptr& f2) const + { + return f1->fragmentNum < f2->fragmentNum; + }; + }; + + struct IncompleteMessage + { + std::shared_ptr msg; + int nextFragmentNum; + uint32_t lastFragmentInsertTime; // in seconds + std::set, FragmentCmp> savedFragments; + + IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (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 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, int fragmentNum); + 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); + + void ScheduleIncompleteMessagesCleanup (); + void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); + + + private: + + SSUSession& m_Session; + std::map > m_IncompleteMessages; + std::map > m_SentMessages; + std::unordered_set m_ReceivedMessages; + boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; + 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..a7497fd1 --- /dev/null +++ b/libi2pd/SSUSession.cpp @@ -0,0 +1,1205 @@ +#include +#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 = router->GetSSUAddress (false); + if (address) m_IntroKey = address->ssu->key; + m_Data.AdjustPacketSize (router); // mtu + } + else + { + // we are server + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); + if (address) m_IntroKey = address->ssu->key; + } + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + } + + SSUSession::~SSUSession () + { + } + + boost::asio::io_service& SSUSession::GetService () + { + return IsV6 () ? m_Server.GetServiceV6 () : 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 = i2p::context.GetRouterInfo ().GetSSUAddress (false); + if (!address) + { + LogPrint (eLogInfo, "SSU is not supported"); + return; + } + if (Validate (buf, len, address->ssu->key)) + Decrypt (buf, len, address->ssu->key); + 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 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 >= 3) // options are presented + { + uint16_t flags = bufbe16toh (buf + headerSize); + sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; + } + headerSize += extendedOptionsLen; + } + if (headerSize >= len) + { + LogPrint (eLogError, "Session request header size ", headerSize, " exceeds packet length ", len); + return; + } + if (!m_DHKeysPair) + m_DHKeysPair = transports.GetNextDHKeysPair (); + 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, "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; + uint8_t addressSize = *payload; + payload += 1; // size + uint8_t * ourAddress = payload; + boost::asio::ip::address ourIP; + if (addressSize == 4) // v4 + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), ourAddress, 4); + ourIP = boost::asio::ip::address_v4 (bytes); + } + else // v6 + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), ourAddress, 16); + ourIP = boost::asio::ip::address_v6 (bytes); + } + s.Insert (ourAddress, addressSize); // our IP + payload += addressSize; // address + uint16_t ourPort = bufbe16toh (payload); + s.Insert (payload, 2); // our port + payload += 2; // 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 + if (i2p::context.GetStatus () == eRouterStatusTesting) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t signedOnTime = bufbe32toh(payload); + if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) + { + LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); + i2p::context.SetError (eRouterErrorClockSkew); + } + } + 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)) + { + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + SendSessionConfirmed (y, ourAddress, addressSize + 2); + } + 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"); + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "SSU: Session confirmed header size ", len, " exceeds packet length ", len); + return; + } + const uint8_t * payload = buf + headerSize; + payload++; // identity fragment info + uint16_t identitySize = bufbe16toh (payload); + 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 paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); + paddingSize &= 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + payload += paddingSize; + // 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 + if (i2p::context.GetStatus () == 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 + bool isV4 = m_RemoteEndpoint.address ().is_v4 (); + 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 = i2p::context.GetRouterInfo ().GetSSUAddress (false); + if (!address) + { + LogPrint (eLogInfo, "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->ssu->key, 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); + } + + 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 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) + { + // Charlie's address always v4 + if (!to.address ().is_v4 ()) + { + LogPrint (eLogWarning, "SSU: Charlie's IP must be v4"); + return; + } + uint8_t buf[80 + 18] = {0}; // 64 Alice's ipv4 and 80 Alice's ipv6 + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 4; + payload++; // size + htobe32buf (payload, to.address ().to_v4 ().to_ulong ()); // Charlie's IP + payload += 4; // address + htobe16buf (payload, to.port ()); // Charlie's port + payload += 2; // port + // Alice + bool isV4 = from.address ().is_v4 (); // Alice's + 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; + // Alice's address always v4 + if (!from.address ().is_v4 ()) + { + LogPrint (eLogWarning, "SSU: Alice's IP must be v4"); + return; + } + uint8_t buf[48 + 18] = {0}; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = 4; + payload++; // size + htobe32buf (payload, from.address ().to_v4 ().to_ulong ()); // Alice's IP + payload += 4; // 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, 48, session->m_SessionKey, iv, session->m_MacKey); + m_Server.Send (buf, 48, 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"); + uint8_t remoteSize = *buf; + buf++; // remote size + boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); + buf += remoteSize; // remote address + uint16_t remotePort = bufbe16toh (buf); + buf += 2; // remote port + uint8_t ourSize = *buf; + buf++; // our size + boost::asio::ip::address ourIP; + if (ourSize == 4) + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), buf, 4); + ourIP = boost::asio::ip::address_v4 (bytes); + } + else + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), buf, 16); + ourIP = boost::asio::ip::address_v6 (bytes); + } + buf += ourSize; // our address + uint16_t ourPort = bufbe16toh (buf); + buf += 2; // our port + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + 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 (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable + m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch + m_Server.CreateDirectSession (it->second, remoteEndpoint, false); + } + // delete request + m_RelayRequests.erase (it); + } + else + LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); + } + + void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) + { + uint8_t size = *buf; + if (size == 4) + { + buf++; // size + boost::asio::ip::address_v4 address (bufbe32toh (buf)); + buf += 4; // address + uint16_t port = bufbe16toh (buf); + // send hole punch of 0 bytes + m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); + } + else + LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); + } + + 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); + htobe16buf (buf + len + 16, encryptedLen); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) + { + if (len < sizeof (SSUHeader)) + { + LogPrint (eLogError, "SSU: Unexpected packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + RAND_bytes (header->iv, 16); // random iv + m_SessionKeyEncryption.SetIV (header->iv); + header->flag = payloadType << 4; // MSB is 0 + htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, header->iv, 16); + htobe16buf (buf + len + 16, encryptedLen); + 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); + htobe16buf (buf + len + 16, encryptedLen); + 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) + { + // set connect timer + ScheduleConnectTimer (); + m_DHKeysPair = transports.GetNextDHKeysPair (); + 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); + m_RelayRequests[nonce] = to; + 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::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + uint32_t nonce = bufbe32toh (buf); // 4 bytes + uint8_t size = buf[4]; // 1 byte + const uint8_t * address = buf + 5; // big endian, size bytes + uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes + const uint8_t * introKey = buf + size + 7; + if (port && (size != 4) && (size != 16)) + { + LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); + return; + } + 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 (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK + i2p::context.SetStatus (eRouterStatusFirewalled); + } + 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"); + 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"); + 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) + session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // 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"); + m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); + Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob + boost::asio::ip::address addr; // Alice's address + if (size == 4) // v4 + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), address, 4); + addr = boost::asio::ip::address_v4 (bytes); + } + else // v6 + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), address, 16); + addr = boost::asio::ip::address_v6 (bytes); + } + SendPeerTest (nonce, addr, be16toh (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 = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (addr) + memcpy (payload, addr->ssu->key, 32); // intro key + else + LogPrint (eLogInfo, "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 = i2p::context.GetRouterInfo ().GetSSUAddress (i2p::context.SupportsV4 ()); + if (!address) + { + LogPrint (eLogInfo, "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->ssu->key, 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); + } +} +} + diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h new file mode 100644 index 00000000..8f81838a --- /dev/null +++ b/libi2pd/SSUSession.h @@ -0,0 +1,167 @@ +#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 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; }; + + 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 FlushData (); + + 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 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 (); + + 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 > m_RelayRequests; // nonce->Charlie + }; + + +} +} + +#endif + diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 3e4b451b..21d08f30 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,16 +1,4 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include -#include -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#endif #include "Log.h" #include "Signature.h" @@ -18,203 +6,38 @@ namespace i2p { namespace crypto { -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - DSAVerifier::DSAVerifier (): - m_PublicKey (nullptr) - { - } - - DSAVerifier::~DSAVerifier () - { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); - } - - void DSAVerifier::SetPublicKey (const uint8_t * signingKey) - { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); - BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); - m_PublicKey = CreateDSA (pub); - BN_free (pub); - } - - bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // calculate SHA1 digest - uint8_t digest[20], sign[48]; - SHA1 (buf, len, digest); - // signature - DSA_SIG * sig = DSA_SIG_new(); - DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); - // to DER format - uint8_t * s = sign; - auto l = i2d_DSA_SIG (sig, &s); - DSA_SIG_free(sig); - // verify - auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL); - EVP_PKEY_verify_init(ctx); - EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); - bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20); - EVP_PKEY_CTX_free(ctx); - return ret; - } - - DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) - { - BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL); - m_PrivateKey = CreateDSA (nullptr, priv); - BN_free (priv); - } - - DSASigner::~DSASigner () - { - if (m_PrivateKey) - EVP_PKEY_free (m_PrivateKey); - } - - void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - uint8_t digest[20], sign[48]; - SHA1 (buf, len, digest); - auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL); - EVP_PKEY_sign_init(ctx); - EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); - size_t l = 48; - EVP_PKEY_sign(ctx, sign, &l, digest, 20); - const uint8_t * s1 = sign; - DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l); - const BIGNUM * r, * s; - DSA_SIG_get0 (sig, &r, &s); - bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); - bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); - DSA_SIG_free(sig); - EVP_PKEY_CTX_free(ctx); - } - - void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - EVP_PKEY * paramskey = CreateDSA(); - EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL); - EVP_PKEY_keygen_init(ctx); - EVP_PKEY * pkey = nullptr; - EVP_PKEY_keygen(ctx, &pkey); - BIGNUM * pub = NULL, * priv = NULL; - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub); - bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv); - bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - BN_free (pub); BN_free (priv); - EVP_PKEY_free (pkey); - EVP_PKEY_free (paramskey); - EVP_PKEY_CTX_free (ctx); - } -#else - - DSAVerifier::DSAVerifier () - { - m_PublicKey = CreateDSA (); - } - - DSAVerifier::~DSAVerifier () - { - DSA_free (m_PublicKey); - } - - void DSAVerifier::SetPublicKey (const uint8_t * signingKey) - { - DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); - } - - bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // calculate SHA1 digest - uint8_t digest[20]; - SHA1 (buf, len, digest); - // signature - DSA_SIG * sig = DSA_SIG_new(); - DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); - // DSA verification - int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); - DSA_SIG_free(sig); - return ret; - } - - DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) - { - m_PrivateKey = CreateDSA (); - DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); - } - - DSASigner::~DSASigner () - { - DSA_free (m_PrivateKey); - } - - void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - uint8_t digest[20]; - SHA1 (buf, len, digest); - DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); - const BIGNUM * r, * s; - DSA_SIG_get0 (sig, &r, &s); - bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); - bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); - DSA_SIG_free(sig); - } - - void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - DSA * dsa = CreateDSA (); - DSA_generate_key (dsa); - const BIGNUM * pub_key, * priv_key; - DSA_get0_key(dsa, &pub_key, &priv_key); - bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); - DSA_free (dsa); - } -#endif - -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA EDDSA25519Verifier::EDDSA25519Verifier (): m_Pkey (nullptr) { + m_MDCtx = EVP_MD_CTX_create (); } EDDSA25519Verifier::~EDDSA25519Verifier () { - EVP_PKEY_free (m_Pkey); + EVP_MD_CTX_destroy (m_MDCtx); + if (m_Pkey) 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); - } - + EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + } + 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); - EVP_MD_CTX_destroy (ctx); - return ret; - } - else - LogPrint (eLogError, "EdDSA verification key is not set"); - return false; + return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); } - -#else + +#else EDDSA25519Verifier::EDDSA25519Verifier () { } EDDSA25519Verifier::~EDDSA25519Verifier () { - } + } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { @@ -222,8 +45,8 @@ namespace crypto 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 { uint8_t digest[64]; @@ -260,228 +83,57 @@ namespace crypto EDDSA25519SignerCompat::~EDDSA25519SignerCompat () { - } - + } + void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } - -#if OPENSSL_EDDSA + +#if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_Pkey (nullptr), m_Fallback (nullptr) - { + m_Fallback (nullptr) + { m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); - uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; + uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); - m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); EVP_PKEY_free (m_Pkey); - m_Pkey = nullptr; + m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } + else + { + m_MDCtx = EVP_MD_CTX_create (); + EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + } } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; - if (m_Pkey) EVP_PKEY_free (m_Pkey); + else + { + EVP_MD_CTX_destroy (m_MDCtx); + EVP_PKEY_free (m_Pkey); + } } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - if (m_Fallback) - return m_Fallback->Sign (buf, len, signature); - else if (m_Pkey) - { - - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - size_t l = 64; - uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 - EVP_DigestSignInit (ctx, NULL, NULL, NULL, m_Pkey); - if (!EVP_DigestSign (ctx, sig, &l, buf, len)) - LogPrint (eLogError, "EdDSA signing failed"); - memcpy (signature, sig, 64); - EVP_MD_CTX_destroy (ctx); - } + if (m_Fallback) return m_Fallback->Sign (buf, len, signature); else - LogPrint (eLogError, "EdDSA signing key is not set"); - } -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) - static const OSSL_PARAM EDDSA25519phParams[] = - { - OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9), - OSSL_PARAM_END - }; - - bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - auto pkey = GetPkey (); - if (pkey) { - uint8_t digest[64]; - SHA512 (buf, len, digest); - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); - auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64); - EVP_MD_CTX_destroy (ctx); - return ret; + size_t l = 64; + uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 + EVP_DigestSign (m_MDCtx, sig, &l, buf, len); + memcpy (signature, sig, 64); } - else - LogPrint (eLogError, "EdDSA verification key is not set"); - return false; - } - - EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): - EDDSA25519Signer (signingPrivateKey) - { - } - - void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - auto pkey = GetPkey (); - if (pkey) - { - uint8_t digest[64]; - SHA512 (buf, len, digest); - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - size_t l = 64; - uint8_t sig[64]; - EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); - if (!EVP_DigestSign (ctx, sig, &l, digest, 64)) - LogPrint (eLogError, "EdDSA signing failed"); - memcpy (signature, sig, 64); - EVP_MD_CTX_destroy (ctx); - } - else - LogPrint (eLogError, "EdDSA signing key is not set"); } #endif - -#if OPENSSL_PQ - - MLDSA44Verifier::MLDSA44Verifier (): - m_Pkey (nullptr) - { - } - - MLDSA44Verifier::~MLDSA44Verifier () - { - EVP_PKEY_free (m_Pkey); - } - - void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey) - { - if (m_Pkey) - { - EVP_PKEY_free (m_Pkey); - m_Pkey = nullptr; - } - OSSL_PARAM params[] = - { - OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()), - OSSL_PARAM_END - }; - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); - if (ctx) - { - EVP_PKEY_fromdata_init (ctx); - EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params); - EVP_PKEY_CTX_free (ctx); - } - else - LogPrint (eLogError, "MLDSA44 can't create PKEY context"); - } - - bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - bool ret = false; - if (m_Pkey) - { - EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); - if (vctx) - { - EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); - if (sig) - { - int encode = 1; - OSSL_PARAM params[] = - { - OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), - OSSL_PARAM_END - }; - EVP_PKEY_verify_message_init (vctx, sig, params); - ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len); - EVP_SIGNATURE_free (sig); - } - EVP_PKEY_CTX_free (vctx); - } - else - LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); - } - else - LogPrint (eLogError, "MLDSA44 verification key is not set"); - return ret; - } - - MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey): - m_Pkey (nullptr) - { - OSSL_PARAM params[] = - { - OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH), - OSSL_PARAM_END - }; - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); - if (ctx) - { - EVP_PKEY_fromdata_init (ctx); - EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params); - EVP_PKEY_CTX_free (ctx); - } - else - LogPrint (eLogError, "MLDSA44 can't create PKEY context"); - } - - MLDSA44Signer::~MLDSA44Signer () - { - if (m_Pkey) EVP_PKEY_free (m_Pkey); - } - - void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - if (m_Pkey) - { - EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); - if (sctx) - { - EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); - if (sig) - { - int encode = 1; - OSSL_PARAM params[] = - { - OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), - OSSL_PARAM_END - }; - EVP_PKEY_sign_message_init (sctx, sig, params); - size_t siglen = MLDSA44_SIGNATURE_LENGTH; - EVP_PKEY_sign (sctx, signature, &siglen, buf, len); - EVP_SIGNATURE_free (sig); - } - EVP_PKEY_CTX_free (sctx); - } - else - LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); - } - else - LogPrint (eLogError, "MLDSA44 signing key is not set"); - } - -#endif } } + + diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 20c7e11b..0c5f27d6 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,11 +1,3 @@ -/* -* 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 SIGNATURE_H__ #define SIGNATURE_H__ @@ -43,55 +35,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); + inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + DSA * dsa = CreateDSA (); + DSA_generate_key (dsa); + const BIGNUM * pub_key, * priv_key; + DSA_get0_key(dsa, &pub_key, &priv_key); + bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); + bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); + DSA_free (dsa); + } - // ECDSA struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) @@ -122,6 +153,7 @@ namespace crypto enum { hashLen = 64 }; }; + // EcDSA template class ECDSAVerifier: public Verifier { @@ -131,9 +163,9 @@ namespace crypto { m_PublicKey = EC_KEY_new_by_curve_name (curve); } - + void SetPublicKey (const uint8_t * signingKey) - { + { BIGNUM * x = BN_bin2bn (signingKey, keyLen/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL); EC_KEY_set_public_key_affine_coordinates (m_PublicKey, x, y); @@ -255,7 +287,7 @@ namespace crypto EDDSA25519Verifier (); void SetPublicKey (const uint8_t * signingKey); ~EDDSA25519Verifier (); - + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; @@ -263,28 +295,15 @@ namespace crypto private: -#if OPENSSL_EDDSA - +#if OPENSSL_EDDSA EVP_PKEY * m_Pkey; - - protected: - - EVP_PKEY * GetPkey () const { return m_Pkey; }; -#else + EVP_MD_CTX * m_MDCtx; +#else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; -#endif +#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: @@ -292,17 +311,17 @@ namespace crypto EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519SignerCompat (); - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: - + uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA class EDDSA25519Signer: public Signer { public: @@ -310,39 +329,23 @@ namespace crypto EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519Signer (); - + 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 typedef EDDSA25519SignerCompat EDDSA25519Signer; - -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - class EDDSA25519phSigner: public EDDSA25519Signer - { - public: - - EDDSA25519phSigner (const uint8_t * signingPrivateKey); - - void Sign (const uint8_t * buf, int len, uint8_t * signature) const; - }; #endif inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { -#if OPENSSL_EDDSA +#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); @@ -352,12 +355,12 @@ namespace crypto EVP_PKEY_get_raw_public_key (pkey, signingPublicKey, &len); len = EDDSA25519_PRIVATE_KEY_LENGTH; EVP_PKEY_get_raw_private_key (pkey, signingPrivateKey, &len); - EVP_PKEY_free (pkey); -#else + EVP_PKEY_free (pkey); +#else RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); EDDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); -#endif +#endif } @@ -396,18 +399,18 @@ namespace crypto GOSTR3410Verifier (GOSTR3410ParamSet paramSet): m_ParamSet (paramSet), m_PublicKey (nullptr) { - } + } - void SetPublicKey (const uint8_t * signingKey) + void SetPublicKey (const uint8_t * signingKey) { BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL); m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y); BN_free (x); BN_free (y); } - ~GOSTR3410Verifier () - { - if (m_PublicKey) EC_POINT_free (m_PublicKey); + ~GOSTR3410Verifier () + { + if (m_PublicKey) EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const @@ -498,18 +501,18 @@ namespace crypto auto publicKey = GetEd25519 ()->GeneratePublicKey (m_PrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); - } + } ~RedDSA25519Signer () {}; - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { - GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature); + GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature); } - + const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: - + uint8_t m_PrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; @@ -518,60 +521,10 @@ namespace crypto { GetEd25519 ()->CreateRedDSAPrivateKey (signingPrivateKey); RedDSA25519Signer signer (signingPrivateKey); - memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); + 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 } } #endif + diff --git a/libi2pd/Siphash.h b/libi2pd/Siphash.h index 78b22b90..70822466 100644 --- a/libi2pd/Siphash.h +++ b/libi2pd/Siphash.h @@ -1,9 +1,9 @@ /** - * This code is licensed under the MCGSI Public License - * Copyright 2018 Jeff Becker - * - * Kovri go write your own code - * + This code is licensed under the MCGSI Public License + Copyright 2018 Jeff Becker + + Kovri go write your own code + */ #ifndef SIPHASH_H #define SIPHASH_H @@ -14,140 +14,140 @@ #if !OPENSSL_SIPHASH namespace i2p { -namespace crypto +namespace crypto { - namespace siphash - { - constexpr int crounds = 2; - constexpr int drounds = 4; + namespace siphash + { + constexpr int crounds = 2; + constexpr int drounds = 4; - inline uint64_t rotl(const uint64_t & x, int b) - { - uint64_t ret = x << b; - ret |= x >> (64 - b); - return ret; - } + inline uint64_t rotl(const uint64_t & x, int b) + { + uint64_t ret = x << b; + ret |= x >> (64 - b); + return ret; + } - inline void u32to8le(const uint32_t & v, uint8_t * p) - { - p[0] = (uint8_t) v; - p[1] = (uint8_t) (v >> 8); - p[2] = (uint8_t) (v >> 16); - p[3] = (uint8_t) (v >> 24); - } + inline void u32to8le(const uint32_t & v, uint8_t * p) + { + p[0] = (uint8_t) v; + p[1] = (uint8_t) (v >> 8); + p[2] = (uint8_t) (v >> 16); + p[3] = (uint8_t) (v >> 24); + } - inline void u64to8le(const uint64_t & v, uint8_t * p) - { - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; - p[4] = (v >> 32) & 0xff; - p[5] = (v >> 40) & 0xff; - p[6] = (v >> 48) & 0xff; - p[7] = (v >> 56) & 0xff; - } + inline void u64to8le(const uint64_t & v, uint8_t * p) + { + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; + p[4] = (v >> 32) & 0xff; + p[5] = (v >> 40) & 0xff; + p[6] = (v >> 48) & 0xff; + p[7] = (v >> 56) & 0xff; + } - inline uint64_t u8to64le(const uint8_t * p) - { - uint64_t i = 0; - int idx = 0; - while(idx < 8) - { - i |= ((uint64_t) p[idx]) << (idx * 8); - ++idx; - } - return i; - } + inline uint64_t u8to64le(const uint8_t * p) + { + uint64_t i = 0; + int idx = 0; + while(idx < 8) + { + i |= ((uint64_t) p[idx]) << (idx * 8); + ++idx; + } + return i; + } + + inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3) + { + _v0 += _v1; + _v1 = rotl(_v1, 13); + _v1 ^= _v0; + _v0 = rotl(_v0, 32); + _v2 += _v3; + _v3 = rotl(_v3, 16); + _v3 ^= _v2; + _v0 += _v3; + _v3 = rotl(_v3, 21); + _v3 ^= _v0; + _v2 += _v1; + _v1 = rotl(_v1, 17); + _v1 ^= _v2; + _v2 = rotl(_v2, 32); + } + } + + /** hashsz must be 8 or 16 */ + template + inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key) + { + uint64_t v0 = 0x736f6d6570736575ULL; + uint64_t v1 = 0x646f72616e646f6dULL; + uint64_t v2 = 0x6c7967656e657261ULL; + uint64_t v3 = 0x7465646279746573ULL; + const uint64_t k0 = siphash::u8to64le(key); + const uint64_t k1 = siphash::u8to64le(key + 8); + uint64_t msg; + int i; + const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t)); + auto left = bufsz & 7; + uint64_t b = ((uint64_t)bufsz) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; - inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3) - { - _v0 += _v1; - _v1 = rotl(_v1, 13); - _v1 ^= _v0; - _v0 = rotl(_v0, 32); - _v2 += _v3; - _v3 = rotl(_v3, 16); - _v3 ^= _v2; - _v0 += _v3; - _v3 = rotl(_v3, 21); - _v3 ^= _v0; - _v2 += _v1; - _v1 = rotl(_v1, 17); - _v1 ^= _v2; - _v2 = rotl(_v2, 32); - } - } + if(hashsz == 16) v1 ^= 0xee; - /** hashsz must be 8 or 16 */ - template - inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key) - { - uint64_t v0 = 0x736f6d6570736575ULL; - uint64_t v1 = 0x646f72616e646f6dULL; - uint64_t v2 = 0x6c7967656e657261ULL; - uint64_t v3 = 0x7465646279746573ULL; - const uint64_t k0 = siphash::u8to64le(key); - const uint64_t k1 = siphash::u8to64le(key + 8); - uint64_t msg; - int i; - const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t)); - auto left = bufsz & 7; - uint64_t b = ((uint64_t)bufsz) << 56; - v3 ^= k1; - v2 ^= k0; - v1 ^= k1; - v0 ^= k0; + while(buf != end) + { + msg = siphash::u8to64le(buf); + v3 ^= msg; + for(i = 0; i < siphash::crounds; ++i) + siphash::round(v0, v1, v2, v3); + + v0 ^= msg; + buf += 8; + } - if(hashsz == 16) v1 ^= 0xee; + while(left) + { + --left; + b |= ((uint64_t)(buf[left])) << (left * 8); + } - while(buf != end) - { - msg = siphash::u8to64le(buf); - v3 ^= msg; - for(i = 0; i < siphash::crounds; ++i) - siphash::round(v0, v1, v2, v3); + v3 ^= b; - v0 ^= msg; - buf += 8; - } + for(i = 0; i < siphash::crounds; ++i) + siphash::round(v0, v1, v2, v3); - while(left) - { - --left; - b |= ((uint64_t)(buf[left])) << (left * 8); - } - - v3 ^= b; - - for(i = 0; i < siphash::crounds; ++i) - siphash::round(v0, v1, v2, v3); - - v0 ^= b; + v0 ^= b; - if(hashsz == 16) - v2 ^= 0xee; - else - v2 ^= 0xff; + if(hashsz == 16) + v2 ^= 0xee; + else + v2 ^= 0xff; - for(i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); + for(i = 0; i < siphash::drounds; ++i) + siphash::round(v0, v1, v2, v3); - b = v0 ^ v1 ^ v2 ^ v3; + b = v0 ^ v1 ^ v2 ^ v3; - siphash::u64to8le(b, h); + siphash::u64to8le(b, h); - if(hashsz == 8) return; + if(hashsz == 8) return; - v1 ^= 0xdd; + v1 ^= 0xdd; - for (i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); + for (i = 0; i < siphash::drounds; ++i) + siphash::round(v0, v1, v2, v3); - b = v0 ^ v1 ^ v2 ^ v3; - siphash::u64to8le(b, h + 8); - } + b = v0 ^ v1 ^ v2 ^ v3; + siphash::u64to8le(b, h + 8); + } } } #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 99da5fd2..7b1f187b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,11 +1,3 @@ -/* -* 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 "Crypto.h" #include "Log.h" #include "RouterInfo.h" @@ -19,55 +11,36 @@ namespace i2p { namespace stream { - void SendBufferQueue::Add (std::shared_ptr&& buf) + void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) { - if (buf) - { - m_Size += buf->len; - m_Buffers.push_back (std::move (buf)); - } + m_Buffers.push_back (std::make_shared(buf, len, handler)); + m_Size += len; } 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,66 +48,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 (0), - m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), - m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed - m_Status (eStreamStatusNew), m_IsIncoming (false), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), - m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), - m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), - m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), - m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), - m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), - m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), - m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), - m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed - m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) + 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) { 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 (0), - m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), - m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed - m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), - m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsClientChoked (false), - m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_DoubleWinIncCounter (false), m_LocalDestination (local), - m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT), m_SlowRTT (INITIAL_RTT), m_SlowRTT2 (INITIAL_RTT), - m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), - m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), - m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), - m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed - m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) + 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) { 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 () @@ -143,29 +86,27 @@ namespace stream LogPrint (eLogDebug, "Streaming: Stream deleted"); } - void Stream::Terminate (bool deleteFromDestination) // should be called from StreamingDestination::Stop only + void Stream::Terminate () { - m_Status = eStreamStatusTerminated; 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 ()); + m_LocalDestination.DeleteStream (shared_from_this ()); } void Stream::CleanUp () { - m_SendBuffer.CleanUp (); + { + std::unique_lock l(m_SendBufferMutex); + 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); @@ -178,51 +119,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->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; } @@ -232,8 +141,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 ();) { @@ -243,7 +151,6 @@ namespace stream m_SavedPackets.erase (it++); ProcessPacket (savedPacket); - if (m_Status == eStreamStatusTerminated) return; } else break; @@ -254,12 +161,15 @@ namespace stream { if (!m_IsAckSendScheduled) { + m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; - ScheduleAck (ackTimeout); + 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 } @@ -269,21 +179,8 @@ namespace stream { // we have received duplicate LogPrint (eLogWarning, "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; + 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 { @@ -292,18 +189,22 @@ namespace stream 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)); + } } } } @@ -326,7 +227,7 @@ namespace stream Terminate (); return; } - + packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) { @@ -356,45 +257,9 @@ namespace stream bool Stream::ProcessOptions (uint16_t flags, Packet * packet) { 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; + size_t optionSize = packet->GetOptionSize (); 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 (delayRequested < m_RTT) - { - m_IsAckSendScheduled = true; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); - m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, - shared_from_this (), std::placeholders::_1)); - } - if (delayRequested >= DELAY_CHOKING) - { - if (!m_IsClientChoked) - { - LogPrint (eLogDebug, "Streaming: Client choked, set min. window size"); - m_WindowDropTargetSize = MIN_WINDOW_SIZE; - m_LastWindowDropSize = 0; - m_WindowIncCounter = 0; - m_IsClientChoked = true; - m_IsWinDropped = false; - m_DropWindowDelaySequenceNumber = m_SequenceNumber; - UpdatePacingTime (); - } - } - } optionData += 2; - } if (flags & PACKET_FLAG_FROM_INCLUDED) { @@ -403,7 +268,7 @@ namespace stream m_RemoteIdentity = std::make_shared(optionData, optionSize); if (m_RemoteIdentity->IsRSA ()) { - LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); + LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); return false; } optionData += m_RemoteIdentity->GetFullLen (); @@ -440,78 +305,41 @@ namespace stream size_t offset = 0; m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); optionData += offset; - if (!m_TransientVerifier) + if (!m_TransientVerifier) { LogPrint (eLogError, "Streaming: offline signature failed"); return false; - } + } } } if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - bool verified = false; - auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); - if (signatureLen > packet->GetLength ()) + uint8_t signature[256]; + auto signatureLen = m_RemoteIdentity->GetSignatureLen (); + if(signatureLen <= sizeof(signature)) + { + memcpy (signature, optionData, signatureLen); + memset (const_cast(optionData), 0, signatureLen); + bool verified = m_TransientVerifier ? + m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : + m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); + if (!verified) + { + LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + Close (); + flags |= PACKET_FLAG_CLOSE; + } + memcpy (const_cast(optionData), signature, signatureLen); + optionData += signatureLen; + } + else { LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); return false; - } - if(signatureLen <= 256) - { - // standard - uint8_t signature[256]; - memcpy (signature, optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - verified = m_TransientVerifier ? - m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : - m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); - if (verified) - memcpy (const_cast(optionData), signature, signatureLen); } - else - { - // post quantum - std::vector signature(signatureLen); - memcpy (signature.data (), optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - verified = m_TransientVerifier ? - m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()) : - m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()); - } - if (verified) - optionData += signatureLen; - else - { - LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); - return false; - } } - if (immediateAckRequested) - SendQuickAck (); - return true; - } - - void Stream::HandlePing (Packet * packet) - { - uint16_t flags = packet->GetFlags (); - if (ProcessOptions (flags, packet) && m_RemoteIdentity) - { - // send pong - Packet p; - 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); - if (payloadLen > 0) - memcpy (p.buf + 22, packet->GetPayload (), payloadLen); - else - payloadLen = 0; - p.len = payloadLen + 22; - SendPackets (std::vector { &p }); - LogPrint (eLogDebug, "Streaming: Pong of ", p.len, " bytes sent"); - } - m_LocalDestination.DeletePacket (packet); + return true; } void Stream::ProcessAck (Packet * packet) @@ -519,16 +347,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; - m_IsNAcked = false; - m_IsResendNeeded = false; int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) { @@ -541,8 +364,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; } @@ -554,134 +375,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; - if (m_WindowIncCounter < MAX_WINDOW_SIZE && !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_SlowRTT = rttSample; - m_SlowRTT2 = rttSample; - m_PrevRTTSample = rttSample; - m_Jitter = rttSample / 10; // 10% - m_Jitter += 15; // for low-latency connections - m_IsFirstRttSample = false; - } - else - m_RTT = (m_PrevRTTSample + rttSample) / 2; - if (!m_IsWinDropped) - { - m_SlowRTT = SLOWRTT_EWMA_ALPHA * m_RTT + (1.0 - SLOWRTT_EWMA_ALPHA) * m_SlowRTT; - m_SlowRTT2 = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT2; - // calculate jitter - double jitter = 0; - if (rttSample > m_PrevRTTSample) - jitter = rttSample - m_PrevRTTSample; - else if (rttSample < m_PrevRTTSample) - jitter = m_PrevRTTSample - rttSample; - else - jitter = rttSample / 10; // 10% - jitter += 15; // for low-latency connections - m_Jitter = (0.05 * jitter) + (1.0 - 0.05) * m_Jitter; - } - if (rttSample > m_SlowRTT) - { - incCounter = 0; - m_DoubleWinIncCounter = 1; - } - else if (rttSample < m_SlowRTT) - { - if (m_DoubleWinIncCounter) - { - incCounter = incCounter * 2; - m_DoubleWinIncCounter = 0; - } - } - m_WindowIncCounter = m_WindowIncCounter + incCounter; - // - // delay-based CC - if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast, late detection - { - LogPrint (eLogDebug, "Streaming: Congestion detected, reduce window size"); - ProcessWindowDrop (); - } - UpdatePacingTime (); - m_PrevRTTSample = rttSample; - - bool wasInitial = m_RTO == INITIAL_RTO; - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter + m_ACKRecieveInterval)); // TODO: implement it better - - if (wasInitial) - ScheduleResend (); - } - if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber) - m_IsClientChoked = false; - if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) - { - m_IsFirstRttSample = true; - m_IsWinDropped = false; - } - if (m_WindowDropTargetSize && int(m_SentPackets.size ()) <= m_WindowDropTargetSize) - { - m_WindowSize = m_WindowDropTargetSize; - m_WindowDropTargetSize = 0; - } - if (acknowledged || m_IsNAcked) - { - ScheduleResend (); - } - if (m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss - { - m_IsResendNeeded = true; - m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter + m_ACKRecieveInterval)); // to prevent spurious retransmit - } - if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) - { + 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) { m_NumResendAttempts = 0; - m_IsTimeOutResend = false; SendBuffer (); } if (m_Status == eStreamStatusClosed) @@ -690,167 +420,106 @@ 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) { + size_t sent = len; + while(len > MAX_PACKET_SIZE) + { + AsyncSend (buf, MAX_PACKET_SIZE, nullptr); + buf += MAX_PACKET_SIZE; + len -= MAX_PACKET_SIZE; + } AsyncSend (buf, len, nullptr); - return len; + return sent; } 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 (); - }); + handler(boost::system::error_code ()); + 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 (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 - packet[size] = 8; - size++; // NACK count - memcpy (packet + size, m_RemoteIdentity->GetIdentHash (), 32); - size += 32; - } - else - { + 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 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; + // initial packet + m_Status = eStreamStatusOpen; + 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, STREAMING_MTU); + size += 2; // max packet size + if (isOfflineSignature) + { + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature + } + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + size += m_SendBuffer.Get (packet + size, STREAMING_MTU - size); // payload + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } - 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) + else { - const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); - memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); - size += offlineSignature.size (); // offline signature + // 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, STREAMING_MTU - size); // payload } - uint8_t * signature = packet + size; // set it later - memset (signature, 0, signatureLen); // zeroes for now - size += signatureLen; // signature - htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size - size += m_SendBuffer.Get (packet + size, m_MTU); // payload - m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + p->len = size; + packets.push_back (p); + numMsgs--; } - else - { - // follow on packet - htobuf16 (packet + size, 0); - size += 2; // flags - htobuf16 (packet + size, 0); // no options - size += 2; // options size - size += m_SendBuffer.Get(packet + size, m_MTU); // payload - } - p->len = size; - packets.push_back (p); - numMsgs--; } if (packets.size () > 0) { @@ -860,15 +529,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_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) SendClose (); if (isEmpty) @@ -879,53 +546,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) { @@ -945,7 +569,6 @@ namespace stream htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; - bool choking = false; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs @@ -954,16 +577,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++) @@ -984,72 +601,17 @@ namespace stream packet[size] = 0; 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 - 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 LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } - void Stream::SendPing () - { - Packet p; - uint8_t * packet = p.GetBuffer (); - size_t size = 0; - htobe32buf (packet, m_RecvStreamID); - size += 4; // sendStreamID - memset (packet + size, 0, 14); - size += 14; // all zeroes - uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_FROM_INCLUDED; - bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); - if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; - htobe16buf (packet + size, flags); - size += 2; // flags - size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); - size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); - uint8_t * optionsSize = packet + size; // set options size later - size += 2; // options size - m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); - size += identityLen; // from - if (isOfflineSignature) - { - const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); - memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); - size += offlineSignature.size (); // offline signature - } - uint8_t * signature = packet + size; // set it later - memset (signature, 0, signatureLen); // zeroes for now - size += signatureLen; // signature - htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size - m_LocalDestination.GetOwner ()->Sign (packet, size, signature); - p.len = size; - SendPackets (std::vector { &p }); - LogPrint (eLogDebug, "Streaming: Ping of ", p.len, " bytes sent"); - } - void Stream::Close () { LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); @@ -1077,7 +639,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); }; } @@ -1092,15 +654,14 @@ namespace stream size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum - htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); + htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); size += 4; // ack Through packet[size] = 0; size++; // NACK count - packet[size] = 0; size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; @@ -1109,7 +670,7 @@ namespace stream m_LocalDestination.GetOwner ()->Sign (packet, size, signature); p->len = size; - boost::asio::post (m_Service, std::bind (&Stream::SendPacket, shared_from_this (), p)); + m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } @@ -1141,7 +702,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); @@ -1157,7 +717,6 @@ namespace stream { if (!m_RemoteLeaseSet) { - CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { @@ -1165,16 +724,8 @@ namespace stream return; } } - 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; - } - } + if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) // expired and detached + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first @@ -1184,65 +735,27 @@ namespace stream m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; + m_RTO = m_RTT*1.5; // TODO: implement it better } } + if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + if (!m_CurrentOutboundTunnel) + { + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); + return; + } 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); - 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) + for (auto it: packets) { - auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( - it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets (), it->IsSYN ())); + auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port)); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -1250,13 +763,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 { @@ -1268,15 +776,15 @@ namespace stream void Stream::SendUpdatedLeaseSet () { - if (m_RoutingSession && !m_RoutingSession->IsTerminated ()) + if (m_RoutingSession) { 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; @@ -1289,255 +797,76 @@ namespace stream SendQuickAck (); } } - else - SendQuickAck (); - } - - void Stream::ScheduleSend () - { - if (m_Status != eStreamStatusTerminated) - { - m_SendTimer.cancel (); - m_SendTimer.expires_from_now (boost::posix_time::microseconds( - SEND_INTERVAL + m_LocalDestination.GetRandom () % SEND_INTERVAL_VARIANCE)); - m_SendTimer.async_wait (std::bind (&Stream::HandleSendTimer, - shared_from_this (), std::placeholders::_1)); - } - } - - void Stream::HandleSendTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (m_LastSendTime && ts*1000 > m_LastSendTime*1000 + m_PacingTime) - { - if (m_PacingTime) - { - auto numPackets = std::lldiv (m_PacingTimeRem + ts*1000 - m_LastSendTime*1000, m_PacingTime); - m_NumPacketsToSend = numPackets.quot; - m_PacingTimeRem = numPackets.rem; - } - else - { - LogPrint (eLogError, "Streaming: pacing time is zero"); - m_NumPacketsToSend = 1; m_PacingTimeRem = 0; - } - m_IsSendTime = true; - if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT) - { - float winSize = m_WindowSize; - if (m_WindowDropTargetSize) - winSize = m_WindowDropTargetSize; - float maxWinSize = MAX_WINDOW_SIZE; - if (m_LastWindowIncTime) - maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize; - for (int i = 0; i < m_NumPacketsToSend; i++) - { - if (m_WindowIncCounter) - { - if (m_WindowDropTargetSize) - { - if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize)) - m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here - else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize)) - m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here - else - m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; - if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE; - m_WindowIncCounter--; - if (m_WindowDropTargetSize >= maxWinSize) - { - m_WindowDropTargetSize = maxWinSize; - break; - } - } - else - { - if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowSize)) - m_WindowSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowSize)); // some magic here - else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize)) - m_WindowSize += (m_WindowSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; // some magic here - else - m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; - if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; - m_WindowIncCounter--; - if (m_WindowSize >= maxWinSize) - { - m_WindowSize = maxWinSize; - break; - } - } - } - else - break; - } - m_LastWindowIncTime = ts; - UpdatePacingTime (); - } - else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) - { - m_WindowSizeTail = m_WindowSizeTail + m_WindowIncCounter; - if (m_WindowSizeTail > MAX_WINDOW_SIZE) m_WindowSizeTail = MAX_WINDOW_SIZE; - } - if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) // resend packets - ResendPacket (); - else if (m_WindowSize > int(m_SentPackets.size ())) // send packets - SendBuffer (); - } - else // pass - ScheduleSend (); - } } void Stream::ScheduleResend () { - if (m_Status != eStreamStatusTerminated) - { - m_ResendTimer.cancel (); - // check for invalid value - if (m_RTO <= 0) m_RTO = INITIAL_RTO; - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); - m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, - shared_from_this (), std::placeholders::_1)); - } + m_ResendTimer.cancel (); + // check for invalid value + if (m_RTO <= 0) m_RTO = INITIAL_RTO; + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); + m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, + shared_from_this (), std::placeholders::_1)); } void Stream::HandleResendTimer (const boost::system::error_code& ecode) { 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) - { - 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*2) - 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*2) - it->resent = true; - else - it->resent = false; it->sendTime = ts; packets.push_back (it); - if ((int)packets.size () >= m_NumPacketsToSend) break; } } - } - - // select tunnels if necessary and send - if (packets.size () > 0 && m_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"); - ProcessWindowDrop (); + case 1: // congesion avoidance + m_WindowSize /= 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 + // 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_IsTimeOutResend = false; - m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change - m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; - m_LastWindowDropSize = 0; - m_WindowIncCounter = 0; - m_IsWinDropped = true; - m_IsFirstRttSample = true; - m_DropWindowDelaySequenceNumber = 0; - m_IsFirstACK = true; - m_LastACKRecieveTime = 0; - m_ACKRecieveInterval = m_AckDelay; - UpdatePacingTime (); - if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); - if (m_NumResendAttempts & 1) - { - // pick another outbound tunnel - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); - LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, - ", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); - } - else - { - CancelRemoteLeaseChange (); - UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, - ", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); - } - } - SendPackets (packets); - m_LastSendTime = ts; - m_IsSendTime = false; - if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) ScheduleSend (); + ScheduleResend (); } - else if (!m_IsClientChoked) - SendBuffer (); - if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); - if (m_IsClientChoked) ScheduleSend (); } - void Stream::ScheduleAck (int timeout) - { - if (m_IsAckSendScheduled) - m_AckSendTimer.cancel (); - m_IsAckSendScheduled = true; - if (timeout < MIN_SEND_ACK_TIMEOUT) timeout = MIN_SEND_ACK_TIMEOUT; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(timeout)); - m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, - shared_from_this (), std::placeholders::_1)); - } - void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) @@ -1553,13 +882,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 (); } @@ -1569,41 +894,20 @@ 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) + m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (!m_RemoteLeaseSet) { - LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); - if (!m_IsIncoming) // outgoing - { - if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) - { - m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( - std::make_shared(m_RemoteIdentity)); - return; // we keep m_RemoteLeaseSet for possible next request - } - else - { - m_RemoteLeaseSet = nullptr; - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt - } - } - else // incoming - { - // just close the socket without sending FIN or RST - m_Status = eStreamStatusClosed; - AsyncClose (); - } + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt } else { // LeaseSet updated - m_RemoteLeaseSet = remoteLeaseSet; m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); - } + } } if (m_RemoteLeaseSet) { @@ -1614,11 +918,11 @@ namespace stream { expired = false; // time to request - if (m_RemoteLeaseSet->IsPublishedEncrypted ()) + if (m_RemoteLeaseSet->GetOrigStoreType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( std::make_shared(m_RemoteIdentity)); else - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) @@ -1636,15 +940,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]; } } @@ -1661,92 +960,12 @@ namespace stream LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; } - if (isLeaseChanged && !m_IsRemoteLeaseChangeInProgress) - { - LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); - ResetWindowSize (); - } - } - - void Stream::ResetRoutingPath () - { - m_CurrentOutboundTunnel = nullptr; - m_CurrentRemoteLease = nullptr; - m_RTT = INITIAL_RTT; - m_RTO = INITIAL_RTO; - if (m_RoutingSession) - m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures - } - - void Stream::UpdatePacingTime () - { - if (m_WindowDropTargetSize) - m_PacingTime = std::round (m_RTT*1000/m_WindowDropTargetSize); - else - m_PacingTime = std::round (m_RTT*1000/m_WindowSize); - if (m_MinPacingTime && m_PacingTime < m_MinPacingTime) - m_PacingTime = m_MinPacingTime; - } - - void Stream::ProcessWindowDrop () - { - if (m_WindowDropTargetSize) - m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue - else - { - if (m_WindowSize < m_LastWindowDropSize) - { - m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize))); - if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE; - } - else - { - m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2)); - if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE; - } - m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue - } - if (m_WindowDropTargetSize < MIN_WINDOW_SIZE) - m_WindowDropTargetSize = MIN_WINDOW_SIZE; - m_WindowIncCounter = 0; // disable window growth - m_DropWindowDelaySequenceNumber = m_SequenceNumber + int(m_WindowDropTargetSize); - m_IsFirstACK = true; // ignore first RTT sample - m_IsWinDropped = true; // don't drop window twice - m_WindowSizeTail = 0; - UpdatePacingTime (); - } - - void Stream::ResetWindowSize () - { - m_RTO = INITIAL_RTO; - if (!m_IsClientChoked) - { - if (m_WindowSize > INITIAL_WINDOW_SIZE) - { - m_WindowDropTargetSize = (float)INITIAL_WINDOW_SIZE; - m_IsWinDropped = true; - } - else - m_WindowSize = INITIAL_WINDOW_SIZE; - } - m_LastWindowDropSize = 0; - m_WindowIncCounter = 0; - m_IsFirstRttSample = true; - m_IsFirstACK = true; - m_WindowSizeTail = 0; - UpdatePacingTime (); - } - - void Stream::CancelRemoteLeaseChange () - { - m_RemoteLeaseChangeTime = 0; - m_IsRemoteLeaseChangeInProgress = false; } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), - m_PendingIncomingTimer (m_Owner->GetService ()), - m_LastCleanupTime (i2p::util::GetSecondsSinceEpoch ()) + m_LastIncomingReceiveStreamID (0), + m_PendingIncomingTimer (m_Owner->GetService ()) { } @@ -1771,11 +990,7 @@ namespace stream m_PendingIncomingStreams.clear (); { std::unique_lock l(m_StreamsMutex); - for (auto it: m_Streams) - it.second->Terminate (false); // we delete here m_Streams.clear (); - m_IncomingStreams.clear (); - m_LastStream = nullptr; } } @@ -1784,23 +999,9 @@ namespace stream uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { - if (!m_LastStream || sendStreamID != m_LastStream->GetRecvStreamID ()) - { - auto it = m_Streams.find (sendStreamID); - if (it != m_Streams.end ()) - m_LastStream = it->second; - else - m_LastStream = nullptr; - } - if (m_LastStream) - m_LastStream->HandleNextPacket (packet); - else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) - { - // ping - LogPrint (eLogInfo, "Streaming: Ping received sSID=", sendStreamID); - auto s = std::make_shared (m_Owner->GetService (), *this); - s->HandlePing (packet); - } + auto it = m_Streams.find (sendStreamID); + if (it != m_Streams.end ()) + it->second->HandleNextPacket (packet); else { LogPrint (eLogInfo, "Streaming: Unknown stream sSID=", sendStreamID); @@ -1809,39 +1010,21 @@ namespace stream } else { - if (packet->IsEcho ()) - { - // pong - LogPrint (eLogInfo, "Streaming: Pong received rSID=", packet->GetReceiveStreamID ()); - DeletePacket (packet); - return; - } if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); - auto it1 = m_IncomingStreams.find (receiveStreamID); - if (it1 != m_IncomingStreams.end ()) + if (receiveStreamID == m_LastIncomingReceiveStreamID) { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); - it1->second->ResetRoutingPath (); // Ack was not delivered, changing path DeletePacket (packet); // drop it, because previous should be connected return; } - if (m_Owner->GetStreamingMaxConcurrentStreams () > 0 && (int)m_Streams.size () > m_Owner->GetStreamingMaxConcurrentStreams ()) - { - LogPrint(eLogWarning, "Streaming: Number of streams exceeds ", m_Owner->GetStreamingMaxConcurrentStreams ()); - DeletePacket (packet); - return; - } - auto incomingStream = CreateNewIncomingStream (receiveStreamID); + auto incomingStream = CreateNewIncomingStream (); 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(); + + m_LastIncomingReceiveStreamID = receiveStreamID; // handle saved packets if any { @@ -1879,13 +1062,13 @@ namespace stream else // follow on packet without SYN { uint32_t receiveStreamID = packet->GetReceiveStreamID (); - auto it1 = m_IncomingStreams.find (receiveStreamID); - if (it1 != m_IncomingStreams.end ()) - { - // found - it1->second->HandleNextPacket (packet); - return; - } + for (auto& it: m_Streams) + if (it.second->GetSendStreamID () == receiveStreamID) + { + // found + it.second->HandleNextPacket (packet); + return; + } // save follow on packet auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) @@ -1918,22 +1101,15 @@ namespace stream { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); - m_Streams.emplace (s->GetRecvStreamID (), s); + m_Streams[s->GetRecvStreamID ()] = s; return s; } - void StreamingDestination::SendPing (std::shared_ptr remote) - { - auto s = std::make_shared (m_Owner->GetService (), *this, remote, 0); - s->SendPing (); - } - - std::shared_ptr StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID) + std::shared_ptr StreamingDestination::CreateNewIncomingStream () { auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); - m_Streams.emplace (s->GetRecvStreamID (), s); - m_IncomingStreams.emplace (receiveStreamID, s); + m_Streams[s->GetRecvStreamID ()] = s; return s; } @@ -1942,40 +1118,17 @@ namespace stream if (stream) { std::unique_lock l(m_StreamsMutex); - m_Streams.erase (stream->GetRecvStreamID ()); - if (stream->IsIncoming ()) - m_IncomingStreams.erase (stream->GetSendStreamID ()); - if (m_LastStream == stream) m_LastStream = nullptr; + auto it = m_Streams.find (stream->GetRecvStreamID ()); + if (it != m_Streams.end ()) + m_Streams.erase (it); } - auto ts = i2p::util::GetSecondsSinceEpoch (); - if (m_Streams.empty () || ts > m_LastCleanupTime + STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL) - { - m_PacketsPool.CleanUp (); - m_I2NPMsgsPool.CleanUp (); - m_LastCleanupTime = ts; - } - } - - bool StreamingDestination::DeleteStream (uint32_t recvStreamID) - { - auto it = m_Streams.find (recvStreamID); - if (it == m_Streams.end ()) - return false; - auto s = it->second; - boost::asio::post (m_Owner->GetService (), [this, s] () - { - s->Close (); // try to send FIN - s->Terminate (false); - DeleteStream (s); - }); - return true; } void StreamingDestination::SetAcceptor (const Acceptor& acceptor) { 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) @@ -1994,10 +1147,10 @@ namespace stream 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 ()) - { + { acceptor (m_PendingIncomingStreams.front ()); m_PendingIncomingStreams.pop_front (); if (m_PendingIncomingStreams.empty ()) @@ -2017,26 +1170,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) @@ -2060,20 +1193,17 @@ namespace stream DeletePacket (uncompressed); } - std::shared_ptr StreamingDestination::CreateDataMessage ( - const uint8_t * payload, size_t len, uint16_t toPort, bool checksum, bool gzip) + std::shared_ptr StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort) { - size_t size; - auto msg = (len <= STREAMING_MTU_RATCHETS) ? m_I2NPMsgsPool.AcquireShared () : NewI2NPMessage (); + auto msg = NewI2NPShortMessage (); + if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) + m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); + else + m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); 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); - else - size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); - + size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length @@ -2081,22 +1211,12 @@ namespace stream htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size; - msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); + msg->FillI2NPMessageHeader (eI2NPData); } else msg = nullptr; 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 570fdd1d..ba52464f 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,17 +1,9 @@ -/* -* 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 STREAMING_H__ #define STREAMING_H__ #include #include -#include +#include #include #include #include @@ -19,7 +11,6 @@ #include #include #include "Base.h" -#include "Gzip.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" @@ -50,48 +41,28 @@ namespace stream const uint16_t PACKET_FLAG_OFFLINE_SIGNATURE = 0x0800; 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 = 16; - const double RTT_EWMA_ALPHA = 0.25; - const double SLOWRTT_EWMA_ALPHA = 0.05; - const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer - const int MIN_RTO = 20; // in milliseconds - const int INITIAL_RTT = 1500; // in milliseconds + const int 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 uint64_t SEND_INTERVAL = 10000; // in microseconds - const uint64_t SEND_INTERVAL_VARIANCE = 2000; // in microseconds - const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds - const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds - const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1 - const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds - + const int MAX_RECEIVE_TIMEOUT = 30; // in seconds + struct Packet { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; uint64_t sendTime; - bool resent; - Packet (): len (0), offset (0), sendTime (0), resent (false) {}; + 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); }; @@ -99,7 +70,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 ()); }; @@ -108,7 +78,6 @@ namespace stream bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; }; bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; }; - bool IsEcho () const { return GetFlags () & PACKET_FLAG_ECHO; }; }; struct PacketCmp @@ -132,11 +101,6 @@ namespace stream buf = new uint8_t[len]; memcpy (buf, b, len); } - SendBuffer (size_t l): // create empty buffer - len(l), offset (0) - { - buf = new uint8_t[len]; - } ~SendBuffer () { delete[] buf; @@ -154,7 +118,7 @@ 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); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; @@ -172,8 +136,7 @@ namespace stream eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, - eStreamStatusClosed, - eStreamStatusTerminated + eStreamStatusClosed }; class StreamingDestination; @@ -181,9 +144,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; }; @@ -192,23 +155,18 @@ 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); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); - void SendPing (); 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 (); @@ -222,7 +180,8 @@ namespace stream int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; - void Terminate (bool deleteFromDestination = true); + /** don't call me */ + void Terminate (); private: @@ -244,70 +203,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; bool m_IsAckSendScheduled; - bool m_IsNAcked; - bool m_IsFirstACK; - bool m_IsResendNeeded; - bool m_IsFirstRttSample; - bool m_IsSendTime; - bool m_IsWinDropped; - bool m_IsClientChoked; - bool m_IsTimeOutResend; - bool m_IsImmediateAckRequested; - bool m_IsRemoteLeaseChangeInProgress; - bool m_DoubleWinIncCounter; StreamingDestination& m_LocalDestination; std::shared_ptr m_RemoteIdentity; std::shared_ptr m_TransientVerifier; // in case of offline key std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; std::shared_ptr m_CurrentRemoteLease; - std::shared_ptr m_NextRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; std::set m_SentPackets; - 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_SlowRTT, m_SlowRTT2; - float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize; - int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail; - double m_Jitter; - uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds - m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime; // milliseconds - uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed - int m_NumResendAttempts, m_NumPacketsToSend; - size_t m_MTU; + int m_WindowSize, m_RTT, m_RTO, m_AckDelay; + uint64_t m_LastWindowSizeIncreaseTime; + int m_NumResendAttempts; }; class StreamingDestination: public std::enable_shared_from_this @@ -316,39 +243,34 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); void Stop (); std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); - void SendPing (std::shared_ptr remote); void DeleteStream (std::shared_ptr stream); - bool DeleteStream (uint32_t recvStreamID); - size_t GetNumStreams () const { return m_Streams.size (); }; void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; void AcceptOnce (const Acceptor& acceptor); - void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); - std::shared_ptr AcceptStream (int timeout = 0); // sync std::shared_ptr GetOwner () const { return m_Owner; }; void SetOwner (std::shared_ptr owner) { m_Owner = owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true, bool gzip = false); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } - uint32_t GetRandom (); - private: + + void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); void HandleNextPacket (Packet * packet); - std::shared_ptr CreateNewIncomingStream (uint32_t receiveStreamID); + std::shared_ptr CreateNewIncomingStream (); void HandlePendingIncomingTimer (const boost::system::error_code& ecode); private: @@ -357,18 +279,15 @@ namespace stream uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; - std::unordered_map > m_Streams; // sendStreamID->stream - std::unordered_map > m_IncomingStreams; // receiveStreamID->stream - std::shared_ptr m_LastStream; + std::map > m_Streams; // sendStreamID->stream Acceptor m_Acceptor; + uint32_t m_LastIncomingReceiveStreamID; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; - std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN + std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; - i2p::util::MemoryPool > m_I2NPMsgsPool; - uint64_t m_LastCleanupTime; // in seconds - + public: i2p::data::GzipInflator m_Inflator; @@ -384,7 +303,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); @@ -393,19 +312,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) @@ -423,7 +343,7 @@ namespace stream handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); else { - // itermediate interrupt + // itermediate iterrupt SendUpdatedLeaseSet (); // send our leaseset if applicable AsyncReceive (buffer, handler, remainingTimeout); } diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index 30b7708d..03e3bc06 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,113 +1,96 @@ +#ifndef TAG_H__ +#define TAG_H__ + /* -* Copyright (c) 2013-2025, The PurpleI2P Project +* Copyright (c) 2013-2017, 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 TAG_H__ -#define TAG_H__ - #include #include #include -#include -#include #include "Base.h" -namespace i2p +namespace i2p { +namespace data { + +template +class Tag { -namespace data -{ - template - class Tag + BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); + +public: + + Tag () = default; + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } + + bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } + bool operator!= (const Tag& other) const { return !(*this == other); } + bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } + + uint8_t * operator()() { return m_Buf; } + const uint8_t * operator()() const { return m_Buf; } + + operator uint8_t * () { return m_Buf; } + operator const uint8_t * () const { return m_Buf; } + + const uint8_t * data() const { return m_Buf; } + const uint64_t * GetLL () const { return ll; } + + bool IsZero () const { - BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); + for (size_t i = 0; i < sz/8; ++i) + if (ll[i]) return false; + return true; + } - public: + void Fill(uint8_t c) + { + memset(m_Buf, c, sz); + } - Tag () = default; - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } + void Randomize() + { + RAND_bytes(m_Buf, sz); + } - bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } - bool operator!= (const Tag& other) const { return !(*this == other); } - bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } + std::string ToBase64 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } - uint8_t * operator()() { return m_Buf; } - const uint8_t * operator()() const { return m_Buf; } + std::string ToBase32 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } - operator uint8_t * () { return m_Buf; } - operator const uint8_t * () const { return m_Buf; } + void FromBase32 (const std::string& s) + { + i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } - const uint8_t * data() const { return m_Buf; } - const uint64_t * GetLL () const { return ll; } + void FromBase64 (const std::string& s) + { + i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } - bool IsZero () const - { - for (size_t i = 0; i < sz/8; ++i) - if (ll[i]) return false; - return true; - } +private: - void Fill(uint8_t c) - { - memset(m_Buf, c, sz); - } - - void Randomize() - { - RAND_bytes(m_Buf, sz); - } - - std::string ToBase64 (size_t len = sz) const - { - return i2p::data::ByteStreamToBase64 (m_Buf, len); - } - - std::string ToBase32 (size_t len = sz) const - { - return i2p::data::ByteStreamToBase32 (m_Buf, len); - } - - size_t FromBase32 (std::string_view s) - { - return i2p::data::Base32ToByteStream (s, m_Buf, sz); - } - - size_t FromBase64 (std::string_view s) - { - return i2p::data::Base64ToByteStream (s, m_Buf, sz); - } - - uint8_t GetBit (int i) const - { - int pos = i >> 3; // /8 - if (pos >= (int)sz) return 0; - return m_Buf[pos] & (0x80 >> (i & 0x07)); - } - - private: - - union // 8 bytes aligned - { - uint8_t m_Buf[sz]; - uint64_t ll[sz/8]; - }; + union // 8 bytes aligned + { + uint8_t m_Buf[sz]; + uint64_t ll[sz/8]; }; +}; + } // data } // i2p -namespace std -{ - // hash for std::unordered_map - template struct hash > - { - size_t operator()(const i2p::data::Tag& s) const - { - return s.GetLL ()[0]; - } - }; -} - #endif /* TAG_H__ */ diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index a22e9bde..acc9cb10 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -16,12 +8,10 @@ #include #include "Config.h" #include "Log.h" -#include "RouterContext.h" #include "I2PEndian.h" #include "Timestamp.h" -#include "util.h" -#ifdef _WIN32 +#ifdef WIN32 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif @@ -34,67 +24,35 @@ namespace util static uint64_t GetLocalMillisecondsSinceEpoch () { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); - } - - static uint64_t GetLocalSecondsSinceEpoch () - { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); - } - - static uint32_t GetLocalMinutesSinceEpoch () - { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); + std::chrono::system_clock::now().time_since_epoch()).count (); } static uint32_t GetLocalHoursSinceEpoch () { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); + std::chrono::system_clock::now().time_since_epoch()).count (); + } + + static uint64_t GetLocalSecondsSinceEpoch () + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); } static int64_t g_TimeOffset = 0; // in seconds static void SyncTimeWithNTP (const std::string& address) { - LogPrint (eLogInfo, "Timestamp: NTP request to ", address); - boost::asio::io_context service; + LogPrint (eLogInfo, "Timestamp: NTP request to ", address); + boost::asio::io_service service; + boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; - auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec); - if (!ec) + auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); + if (!ec && it != boost::asio::ip::udp::resolver::iterator()) { - bool found = false; - boost::asio::ip::udp::endpoint ep; - for (const auto& it: endpoints) - { - ep = it; - if (!ep.address ().is_unspecified ()) - { - if (ep.address ().is_v4 ()) - { - if (i2p::context.SupportsV4 ()) found = true; - } - else if (ep.address ().is_v6 ()) - { - if (i2p::util::net::IsYggdrasilAddress (ep.address ())) - { - if (i2p::context.SupportsMesh ()) found = true; - } - else if (i2p::context.SupportsV6 ()) found = true; - } - } - if (found) break; - } - if (!found) - { - LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); - return; - } - + auto ep = (*it).endpoint (); // take first one boost::asio::ip::udp::socket socket (service); - socket.open (ep.protocol (), ec); + socket.open (boost::asio::ip::udp::v4 (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response @@ -130,7 +88,7 @@ namespace util LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } else - LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); + LogPrint (eLogError, "Timestamp: Couldn't resove address ", address); } NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) @@ -151,7 +109,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 @@ -176,8 +134,6 @@ namespace util void NTPTimeSync::Run () { - i2p::util::SetThreadName("Timesync"); - while (m_IsRunning) { try @@ -192,11 +148,11 @@ namespace util } void NTPTimeSync::Sync () - { + { if (m_NTPServersList.size () > 0) SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]); else - m_IsRunning = false; + m_IsRunning = false; if (m_IsRunning) { @@ -214,49 +170,21 @@ namespace util return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000; } - uint64_t GetSecondsSinceEpoch () - { - return GetLocalSecondsSinceEpoch () + g_TimeOffset; - } - - uint32_t GetMinutesSinceEpoch () - { - return GetLocalMinutesSinceEpoch () + g_TimeOffset/60; - } - uint32_t GetHoursSinceEpoch () { return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; } - uint64_t GetMonotonicMicroseconds() + uint64_t GetSecondsSinceEpoch () { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); + return GetLocalSecondsSinceEpoch () + g_TimeOffset; } - 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; @@ -270,10 +198,6 @@ namespace util sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); #endif } +} +} - void AdjustTimeOffset (int64_t offset) - { - g_TimeOffset += offset; - } -} -} diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 00c60433..31dc86aa 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef TIMESTAMP_H__ #define TIMESTAMP_H__ @@ -20,18 +12,11 @@ namespace i2p namespace util { uint64_t GetMillisecondsSinceEpoch (); - uint64_t GetSecondsSinceEpoch (); - uint32_t GetMinutesSinceEpoch (); uint32_t GetHoursSinceEpoch (); + uint64_t GetSecondsSinceEpoch (); - uint64_t GetMonotonicMicroseconds (); - uint64_t GetMonotonicMilliseconds (); - uint64_t GetMonotonicSeconds (); - - void GetCurrentDate (char * date); // returns UTC date as YYYYMMDD string, 9 bytes - void GetNextDayDate (char * date); // returns next UTC day as YYYYMMDD string, 9 bytes - void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes - void AdjustTimeOffset (int64_t offset); // in seconds from current + 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 class NTPTimeSync { @@ -41,18 +26,18 @@ namespace util ~NTPTimeSync (); void Start (); - void Stop (); + void Stop (); private: - + void Run (); - void Sync (); + void Sync (); private: bool m_IsRunning; - std::unique_ptr m_Thread; - boost::asio::io_context m_Service; + std::unique_ptr m_Thread; + boost::asio::io_service m_Service; boost::asio::deadline_timer m_Timer; int m_SyncInterval; std::vector m_NTPServersList; @@ -61,3 +46,4 @@ namespace util } #endif + diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index b24c8ac5..1adc4178 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,21 +1,8 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "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,51 +12,32 @@ 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 () { } - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - EncryptTunnelMsg (tunnelMsg, tunnelMsg); + auto newMsg = CreateEmptyTunnelDataMsg (); + EncryptTunnelMsg (tunnelMsg, newMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); - tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); - m_TunnelDataMsgs.push_back (tunnelMsg); + htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); + newMsg->FillI2NPMessageHeader (eI2NPTunnelData); + m_TunnelDataMsgs.push_back (newMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -79,103 +47,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 + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - 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); + auto newMsg = CreateEmptyTunnelDataMsg (); 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 +107,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..5b891dc3 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,20 +1,11 @@ -/* -* 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 TRANSIT_TUNNEL_H__ #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 +20,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 +39,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 +60,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 +80,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..49067ce2 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,19 +1,10 @@ -/* -* 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 TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__ #include -#include +#include #include #include -#include #include "Identity.h" #include "Crypto.h" #include "RouterInfo.h" @@ -24,74 +15,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_DHKeysPair (nullptr), 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 () {}; @@ -99,104 +67,29 @@ namespace transport std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; } - std::shared_ptr GetRemoteIdentity () - { - std::lock_guard l(m_RemoteIdentityMutex); - return m_RemoteIdentity; - } - void SetRemoteIdentity (std::shared_ptr ident) - { - std::lock_guard l(m_RemoteIdentityMutex); - m_RemoteIdentity = ident; - } + std::shared_ptr GetRemoteIdentity () { return m_RemoteIdentity; }; + void SetRemoteIdentity (std::shared_ptr ident) { m_RemoteIdentity = ident; }; 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; + std::shared_ptr m_DHKeysPair; // X - for client and Y - for server + 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..c00e5d52 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,12 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include // for boost::to_lower #include "Log.h" #include "Crypto.h" #include "RouterContext.h" @@ -15,7 +6,10 @@ #include "Transports.h" #include "Config.h" #include "HTTP.h" +#ifdef WITH_EVENTS +#include "Event.h" #include "util.h" +#endif using namespace i2p::data; @@ -23,27 +17,23 @@ namespace i2p { namespace transport { - template - EphemeralKeysSupplier::EphemeralKeysSupplier (int size): - m_QueueSize (size), m_IsRunning (false) + DHKeysPairSupplier::DHKeysPairSupplier (int size): + m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) { } - template - EphemeralKeysSupplier::~EphemeralKeysSupplier () + DHKeysPairSupplier::~DHKeysPairSupplier () { Stop (); } - template - void EphemeralKeysSupplier::Start () + void DHKeysPairSupplier::Start () { m_IsRunning = true; - m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier::Run, this))); + m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this)); } - template - void EphemeralKeysSupplier::Stop () + void DHKeysPairSupplier::Stop () { { std::unique_lock l(m_AcquiredMutex); @@ -53,38 +43,28 @@ 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 - void EphemeralKeysSupplier::Run () + void DHKeysPairSupplier::Run () { - i2p::util::SetThreadName("Ephemerals"); - 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); + CreateDHKeysPairs (num); total += num; } - if (total > m_QueueSize) + if (total >= 10) { - LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); + LogPrint (eLogWarning, "Transports: ", total, " DH 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 @@ -92,26 +72,24 @@ namespace transport } } - template - void EphemeralKeysSupplier::CreateEphemeralKeys (int num) + void DHKeysPairSupplier::CreateDHKeysPairs (int num) { if (num > 0) { 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); + std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } - template - std::shared_ptr EphemeralKeysSupplier::Acquire () + std::shared_ptr DHKeysPairSupplier::Acquire () { { - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); @@ -121,47 +99,34 @@ namespace transport } } // queue is empty, create new - auto pair = m_KeysPool.AcquireSharedMt (); + auto pair = std::make_shared (); pair->GenerateKeys (); return pair; } - template - void EphemeralKeysSupplier::Return (std::shared_ptr pair) + void DHKeysPairSupplier::Return (std::shared_ptr pair) { 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_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), + m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), + m_NTCPServer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), + m_DHKeysPairSupplier (5), // 5 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,188 +137,141 @@ 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 enableNTCP, bool enableSSU) { 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_DHKeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); + std::string ntcpproxy; i2p::config::GetOption("ntcpproxy", ntcpproxy); i2p::http::URL proxyurl; - // create NTCP2. TODO: move to acceptor - if (enableNTCP2 || i2p::context.SupportsMesh ()) + uint16_t softLimit, hardLimit, threads; + i2p::config::GetOption("limits.ntcpsoft", softLimit); + i2p::config::GetOption("limits.ntcphard", hardLimit); + i2p::config::GetOption("limits.ntcpthreads", threads); + if(softLimit > 0 && hardLimit > 0 && softLimit >= hardLimit) { - if(!ntcp2proxy.empty() && enableNTCP2) + LogPrint(eLogError, "ntcp soft limit must be less than ntcp hard limit"); + return; + } + if(ntcpproxy.size() && enableNTCP) + { + if(proxyurl.parse(ntcpproxy)) { - if(proxyurl.parse(ntcp2proxy)) + if(proxyurl.schema == "socks" || proxyurl.schema == "http") { - if(proxyurl.schema == "socks" || proxyurl.schema == "http") + m_NTCPServer = new NTCPServer(threads); + m_NTCPServer->SetSessionLimits(softLimit, hardLimit); + NTCPServer::ProxyType proxytype = NTCPServer::eSocksProxy; + + if (proxyurl.schema == "http") + proxytype = NTCPServer::eHTTPProxy; + m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port) ; + m_NTCPServer->Start(); + if(!m_NTCPServer->NetworkIsReady()) { - m_NTCP2Server = new NTCP2Server (); - NTCP2Server::ProxyType proxytype = NTCP2Server::eSocksProxy; - - if (proxyurl.schema == "http") - proxytype = NTCP2Server::eHTTPProxy; - - m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); - i2p::context.SetStatus (eRouterStatusProxy); - if (ipv6) - i2p::context.SetStatusV6 (eRouterStatusProxy); + LogPrint(eLogError, "Transports: NTCP failed to start with proxy"); + m_NTCPServer->Stop(); + delete m_NTCPServer; + m_NTCPServer = nullptr; } - else - LogPrint(eLogCritical, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); } else - LogPrint(eLogCritical, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogError, "Transports: unsupported NTCP proxy URL ", ntcpproxy); } else - m_NTCP2Server = new NTCP2Server (); + LogPrint(eLogError, "Transports: invalid NTCP proxy url ", ntcpproxy); + return; } - - // create SSU2 server - if (enableSSU2) + // create NTCP2. TODO: move to acceptor + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - m_SSU2Server = new SSU2Server (); - std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy); - if (!ssu2proxy.empty()) + m_NTCP2Server = new NTCP2Server (); + m_NTCP2Server->Start (); + } + + // create acceptors + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address : addresses) + { + if (!address) continue; + if (m_NTCPServer == nullptr && enableNTCP) { - if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks") + m_NTCPServer = new NTCPServer (threads); + m_NTCPServer->SetSessionLimits(softLimit, hardLimit); + m_NTCPServer->Start (); + if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) { + /** failed to bind to NTCP */ + LogPrint(eLogError, "Transports: failed to bind to TCP"); + m_NTCPServer->Stop(); + delete m_NTCPServer; + m_NTCPServer = nullptr; + } + } + + if (address->transportStyle == RouterInfo::eTransportSSU) + { + if (m_SSUServer == nullptr && enableSSU) { - if (m_SSU2Server->SetProxy (proxyurl.host, proxyurl.port)) - { - i2p::context.SetStatus (eRouterStatusProxy); - if (ipv6) - i2p::context.SetStatusV6 (eRouterStatusProxy); - } + if (address->host.is_v4()) + m_SSUServer = new SSUServer (address->port); else - LogPrint(eLogCritical, "Transports: Can't set SSU2 proxy ", ssu2proxy); + m_SSUServer = new SSUServer (address->host, address->port); + LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); + try { + m_SSUServer->Start (); + } catch ( std::exception & ex ) { + LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port); + delete m_SSUServer; + m_SSUServer = nullptr; + continue; + } + DetectExternalIP (); } else - LogPrint(eLogCritical, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); + LogPrint (eLogError, "Transports: SSU server already exists"); } } - - // bind to interfaces - 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); - 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 (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); - 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); - } - } - } - - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - if (ygg) - { - std::string address; i2p::config::GetOption("meshnets.yggaddress", address); - if (!address.empty ()) - { - boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (address, ec); - if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr)) - m_NTCP2Server->SetLocalAddress (addr); - } - } - - // 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->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); - for (int i = 0; i < TRAFFIC_SAMPLE_COUNT; i++) - { - m_TrafficSamples[i].Timestamp = ts - (TRAFFIC_SAMPLE_COUNT - i - 1) * 1000; - m_TrafficSamples[i].TotalReceivedBytes = 0; - m_TrafficSamples[i].TotalSentBytes = 0; - m_TrafficSamples[i].TotalTransitTransmittedBytes = 0; - } - m_TrafficSamplePtr = TRAFFIC_SAMPLE_COUNT - 1; - - m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); - m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); - - if (m_IsNAT) - { - m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); - m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); - } + if (m_IsNAT) + { + m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); + } } void Transports::Stop () { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); - - if (m_SSU2Server) + m_Peers.clear (); + if (m_SSUServer) { - m_SSU2Server->Stop (); - delete m_SSU2Server; - m_SSU2Server = nullptr; + m_SSUServer->Stop (); + delete m_SSUServer; + m_SSUServer = nullptr; + } + if (m_NTCPServer) + { + m_NTCPServer->Stop (); + delete m_NTCPServer; + m_NTCPServer = nullptr; } if (m_NTCP2Server) @@ -363,7 +281,7 @@ namespace transport m_NTCP2Server = nullptr; } - m_X25519KeysPairSupplier.Stop (); + m_DHKeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) @@ -372,13 +290,10 @@ namespace transport delete m_Thread; m_Thread = nullptr; } - m_Peers.clear (); } void Transports::Run () { - i2p::util::SetThreadName("Transports"); - while (m_IsRunning && m_Service) { try @@ -387,262 +302,171 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "Transports: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "Transports: runtime exception: ", ex.what ()); } } } - 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; - } - in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta; - out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta; - transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta; - } - - void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - 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)); - } - } - - int Transports::GetCongestionLevel (bool longTerm) 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); - } - - std::future > Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) - { - if (m_IsOnline) - return SendMessages (ident, { msg }); - return {}; // invalid future - } - - std::future > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs) - { - return boost::asio::post (*m_Service, boost::asio::use_future ([this, ident, msgs = std::move(msgs)] () mutable + auto delta = ts - m_LastBandwidthUpdateTime; + if (delta > 0) { - return PostMessages (ident, msgs); - })); - } - - std::shared_ptr Transports::PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs) + 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; + } + } + m_LastBandwidthUpdateTime = ts; + m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; + m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; + m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; + } + + bool Transports::IsBandwidthExceeded () const + { + auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes + auto bw = std::max (m_InBandwidth, m_OutBandwidth); + return bw > limit; + } + + bool Transports::IsTransitBandwidthExceeded () const + { + auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes + return m_TransitBandwidth > limit; + } + + void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) + { + SendMessages (ident, std::vector > {msg }); + } + + void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) + { +#ifdef WITH_EVENTS + QueueIntEvent("transport.send", ident.ToBase64(), msgs.size()); +#endif + 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 ()) { // we send it to ourself for (auto& it: msgs) - m_LoopbackHandler.PutNextMessage (std::move (it)); + m_LoopbackHandler.PutNextMessage (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; + { + 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); + LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES); + 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) // we have RI already { - if (peer->priority.empty ()) - SetPriority (peer); - while (peer->numAttempts < (int)peer->priority.size ()) + if (!peer.numAttempts) // NTCP2 { - auto tr = peer->priority[peer->numAttempts]; - peer->numAttempts++; - switch (tr) + peer.numAttempts++; + if (m_NTCP2Server) // we support NTCP2 { - case i2p::data::RouterInfo::eNTCP2V4: - case i2p::data::RouterInfo::eNTCP2V6: + // NTCP2 have priority over NTCP + auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only + if (address) { - 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) + auto s = std::make_shared (*m_NTCP2Server, peer.router); + m_NTCP2Server->Connect (address->host, address->port, s); + return true; + } + } + } + if (peer.numAttempts == 1) // NTCP1 + { + peer.numAttempts++; + auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); + if (address && m_NTCPServer) + { + if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) + { + if(!m_NTCPServer->ShouldLimit()) { - auto s = std::make_shared (*m_NTCP2Server, peer->router, address); - if( m_NTCP2Server->UsingProxy()) - m_NTCP2Server->ConnectWithProxy(s); + auto s = std::make_shared (*m_NTCPServer, peer.router); + if(m_NTCPServer->UsingProxy()) + { + NTCPServer::RemoteAddressType remote = NTCPServer::eIP4Address; + std::string addr = address->host.to_string(); + + if(address->host.is_v6()) + remote = NTCPServer::eIP6Address; + + m_NTCPServer->ConnectWithProxy(addr, address->port, remote, s); + } else - m_NTCP2Server->Connect (s); + m_NTCPServer->Connect (address->host, address->port, s); return true; } - break; - } - case i2p::data::RouterInfo::eSSU2V4: - case i2p::data::RouterInfo::eSSU2V6: - { - 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 ()) + else { - if (m_SSU2Server->CreateSession (peer->router, address)) - return true; + LogPrint(eLogWarning, "Transports: NTCP Limit hit falling back to SSU"); } - break; } - case i2p::data::RouterInfo::eNTCP2V6Mesh: - { - if (!m_NTCP2Server) continue; - auto address = peer->router->GetYggdrasilAddress (); - if (address) - { - auto s = std::make_shared (*m_NTCP2Server, peer->router, address); - m_NTCP2Server->Connect (s); - return true; - } - break; - } - default: - LogPrint (eLogError, "Transports: Unknown transport ", (int)tr); + } + else + LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); + } + if (peer.numAttempts == 2)// SSU + { + peer.numAttempts++; + if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) + { + auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); + m_SSUServer->CreateSession (peer.router, address->host, address->port); + return true; } } - - 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); + LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); + peer.Done (); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -655,236 +479,121 @@ 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::CloseSession (std::shared_ptr router) + { + if (!router) return; + m_Service->post (std::bind (&Transports::PostCloseSession, this, router)); + } + void Transports::PostCloseSession (std::shared_ptr router) + { + auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr; + if (ssuSession) // try SSU first + { + m_SSUServer->DeleteSession (ssuSession); + LogPrint (eLogDebug, "Transports: SSU session closed"); + } + auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr; + if (ntcpSession) // try deleting ntcp session too + { + ntcpSession->Terminate (); + LogPrint(eLogDebug, "Transports: NTCP session closed"); + } } void Transports::DetectExternalIP () { if (RoutesRestricted()) { - LogPrint(eLogInfo, "Transports: Restricted routes enabled, not detecting IP"); + LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); i2p::context.SetStatus (eRouterStatusOK); return; } - if (m_SSU2Server) - PeerTest (); + if (m_SSUServer) + { + bool isv4 = i2p::context.SupportsV4 (); + if (m_IsNAT && isv4) + i2p::context.SetStatus (eRouterStatusTesting); + for (int i = 0; i < 5; i++) + { + auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4 + if (router) + m_SSUServer->CreateSession (router, true, isv4); // peer test + else + { + // if not peer test capable routers found pick any + router = i2p::data::netdb.GetRandomRouter (); + if (router && router->IsSSU ()) + m_SSUServer->CreateSession (router); // no peer test + } + } + } 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) + void Transports::PeerTest () { - if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return; - if (ipv4 && i2p::context.SupportsV4 ()) + if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return; + if (m_SSUServer) { - LogPrint (eLogInfo, "Transports: Started peer test IPv4"); - std::unordered_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); // v4 only if (router) { - if (!i2p::context.GetTesting ()) - { - i2p::context.SetTesting (true); - // send first peer test immediately - m_SSU2Server->StartPeerTest (router, true); - } - else + if (!statusChanged) { - testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; - if (m_Service) - { - auto delayTimer = std::make_shared(*m_Service); - delayTimer->expires_from_now (boost::posix_time::milliseconds (testDelay)); - delayTimer->async_wait ( - [this, router, delayTimer](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - m_SSU2Server->StartPeerTest (router, true); - }); - } - } - excluded.insert (router->GetIdentHash ()); - } - } - if (excluded.size () <= 1) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); - } - if (ipv6 && i2p::context.SupportsV6 ()) - { - LogPrint (eLogInfo, "Transports: Started peer test IPv6"); - std::unordered_set excluded; - excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - int testDelay = 0; - for (int i = 0; i < 5; i++) - { - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 - if (router) - { - if (!i2p::context.GetTestingV6 ()) - { - i2p::context.SetTestingV6 (true); - // send first peer test immediately - m_SSU2Server->StartPeerTest (router, false); - } - else - { - testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; - if (m_Service) - { - auto delayTimer = std::make_shared(*m_Service); - delayTimer->expires_from_now (boost::posix_time::milliseconds (testDelay)); - delayTimer->async_wait ( - [this, router, delayTimer](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - m_SSU2Server->StartPeerTest (router, false); - }); - } + statusChanged = true; + i2p::context.SetStatus (eRouterStatusTesting); // first time only } - excluded.insert (router->GetIdentHash ()); + m_SSUServer->CreateSession (router, true, true); // peer test v4 } } - if (excluded.size () <= 1) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); + if (!statusChanged) + LogPrint (eLogWarning, "Can't find routers for peer test"); } } - std::shared_ptr Transports::GetNextX25519KeysPair () + std::shared_ptr Transports::GetNextDHKeysPair () { - return m_X25519KeysPairSupplier.Acquire (); + return m_DHKeysPairSupplier.Acquire (); } - void Transports::ReuseX25519KeysPair (std::shared_ptr pair) + void Transports::ReuseDHKeysPair (std::shared_ptr pair) { - m_X25519KeysPairSupplier.Return (pair); + m_DHKeysPairSupplier.Return (pair); } 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 +601,14 @@ 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 +#ifdef WITH_EVENTS + EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "false"}}); +#endif 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,68 +617,50 @@ 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 - LogPrint(eLogWarning, "Transports: Closing untrusted inbound connection from ", ident.ToBase64()); + LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64()); 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); +#ifdef WITH_EVENTS + EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "true"}}); +#endif + 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; auto ident = remoteIdentity->GetIdentHash (); +#ifdef WITH_EVENTS + EmitEvent({{"type" , "transport.disconnected"}, {"ident", ident.ToBase64()}}); +#endif 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 ()) + it->second.sessions.remove (session); + if (it->second.sessions.empty ()) // TODO: why? { - if (peer->delayedMessages.size () > 0) - { - if (wasConnected) // we had an active session before - peer->numAttempts = 0; // start over - ConnectToPeer (ident, peer); - } + if (it->second.delayedMessages.size () > 0) + 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 +669,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 +681,24 @@ 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 - if (ipv4Testing || ipv6Testing) - PeerTest (ipv4Testing, ipv6Testing); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT)); + UpdateBandwidth (); // TODO: use separate timer(s) for it + if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test + 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)); } } @@ -1065,340 +708,91 @@ 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; - 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; - 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++; - } - } - } - } - } - return found ? i2p::data::netdb.FindRouter (ident) : nullptr; + if (m_Peers.empty ()) return nullptr; + std::unique_lock l(m_PeersMutex); + auto it = m_Peers.begin (); + std::advance (it, rand () % m_Peers.size ()); + return it != m_Peers.end () ? it->second.router : nullptr; } - - std::shared_ptr Transports::GetRandomPeer (bool isHighBandwidth) const - { - return GetRandomPeer ( - [isHighBandwidth](std::shared_ptr peer)->bool - { - // connected, not overloaded and not slow - return !peer->router && peer->IsConnected () && peer->isEligible && - peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && - !peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) && - (!isHighBandwidth || peer->isHighBandwidth); - }); - } - - void Transports::RestrictRoutesToFamilies(const std::set& families) + void Transports::RestrictRoutesToFamilies(std::set families) { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); - for (auto fam : families) - { - boost::to_lower (fam); - auto id = i2p::data::netdb.GetFamilies ().GetFamilyID (fam); - if (id) - m_TrustedFamilies.push_back (id); - } + for ( const auto& fam : families ) + m_TrustedFamilies.push_back(fam); } - 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); - i2p::data::FamilyID fam = 0; + std::string fam; auto sz = m_TrustedFamilies.size(); if(sz > 1) { auto it = m_TrustedFamilies.begin (); - std::advance(it, m_Rng() % sz); + std::advance(it, rand() % sz); fam = *it; + boost::to_lower(fam); } else if (sz == 1) { fam = m_TrustedFamilies[0]; } - if (fam) + if (fam.size()) 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; } return false; } - - void Transports::SetOnline (bool online) - { - if (m_IsOnline != online) - { - m_IsOnline = online; - if (online) - PeerTest (); - else - 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..798c90a9 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,131 +1,74 @@ -/* -* 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 TRANSPORTS_H__ #define TRANSPORTS_H__ #include #include -#include #include #include -#include -#include +#include #include #include #include #include #include -#include #include #include "TransportSession.h" -#include "SSU2.h" +#include "NTCPSession.h" +#include "SSU.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" -#include "util.h" namespace i2p { namespace transport { - template - class EphemeralKeysSupplier + class DHKeysPairSupplier { - // called from this file only, so implementation is in Transports.cpp public: - EphemeralKeysSupplier (int size); - ~EphemeralKeysSupplier (); + DHKeysPairSupplier (int size); + ~DHKeysPairSupplier (); void Start (); void Stop (); - std::shared_ptr Acquire (); - void Return (std::shared_ptr pair); + std::shared_ptr Acquire (); + void Return (std::shared_ptr pair); private: void Run (); - void CreateEphemeralKeys (int num); + void CreateDHKeysPairs (int num); private: const int m_QueueSize; - i2p::util::MemoryPoolMt m_KeysPool; - std::queue > m_Queue; + 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 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; }; + const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds + const int PEER_TEST_INTERVAL = 71; // in minutes + const int MAX_NUM_DELAYED_MESSAGES = 50; class Transports { public: @@ -133,22 +76,23 @@ namespace transport Transports (); ~Transports (); - void Start (bool enableNTCP2=true, bool enableSSU2=true); + void Start (bool enableNTCP=true, bool enableSSU=true); void Stop (); - bool IsRunning () const { return m_IsRunning; } - bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } + bool IsBoundNTCP() const { return m_NTCPServer != 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); + void SetOnline (bool online) { m_IsOnline = online; }; - auto& GetService () { return *m_Service; }; - std::shared_ptr GetNextX25519KeysPair (); - void ReuseX25519KeysPair (std::shared_ptr pair); + boost::asio::io_service& GetService () { return *m_Service; }; + std::shared_ptr GetNextDHKeysPair (); + void ReuseDHKeysPair (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 CloseSession (std::shared_ptr router); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); @@ -160,103 +104,82 @@ namespace transport uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; uint64_t GetTotalTransitTransmittedBytes () const { return m_TotalTransitTransmittedBytes; } void UpdateTotalTransitTransmittedBytes (uint32_t add) { m_TotalTransitTransmittedBytes += add; }; - uint32_t GetInBandwidth () const { return m_InBandwidth; }; + 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(); - /** 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); + /** get a trusted first hop for restricted routes */ + 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(std::set families); + /** restrict routes to use only these routers for first hops */ + 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; + void PeerTest (); 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); + void PostCloseSession (std::shared_ptr router); + 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; + bool m_IsOnline, m_IsRunning, m_IsNAT; 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; - SSU2Server * m_SSU2Server; + NTCPServer * m_NTCPServer; + SSUServer * m_SSUServer; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; - std::unordered_map > m_Peers; + std::map m_Peers; - X25519KeysPairSupplier m_X25519KeysPairSupplier; + DHKeysPairSupplier m_DHKeysPairSupplier; 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; + 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 NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; + 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 1b317121..66620717 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,14 +1,5 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "I2PEndian.h" -#include #include #include #include @@ -22,8 +13,9 @@ #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" -#include "util.h" -#include "ECIESX25519AEADRatchetSession.h" +#ifdef WITH_EVENTS +#include "Event.h" +#endif namespace i2p { @@ -31,9 +23,8 @@ namespace tunnel { 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_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), + m_Latency (0) { } @@ -43,21 +34,24 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { +#ifdef WITH_EVENTS + std::string peers = i2p::context.GetIdentity()->GetIdentHash().ToBase64(); +#endif auto numHops = m_Config->GetNumHops (); - const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; - auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); + int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; + auto msg = NewI2NPShortMessage (); *msg->GetPayload () = numRecords; - const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; - msg->len += numRecords*recordSize + 1; + msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; // 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::random_shuffle (recordIndicies.begin(), recordIndicies.end()); // create real records uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; + BN_CTX * ctx = BN_CTX_new (); while (hop) { uint32_t msgID; @@ -65,157 +59,116 @@ namespace tunnel RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; - hop->recordIndex = recordIndicies[i]; i++; - hop->CreateBuildRequestRecord (records, msgID); + int idx = recordIndicies[i]; + hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); + hop->recordIndex = idx; + i++; +#ifdef WITH_EVENTS + peers += ":" + hop->ident->GetIdentHash().ToBase64(); +#endif hop = hop->next; } + BN_CTX_free (ctx); +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnel.build", this, peers); +#endif // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; - RAND_bytes (records + idx*recordSize, recordSize); + RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); } // decrypt real records + i2p::crypto::CBCDecryption decryption; hop = m_Config->GetLastHop ()->prev; while (hop) { + decryption.SetKey (hop->replyKey); // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { - hop->DecryptRecord (records, hop1->recordIndex); + decryption.SetIV (hop->replyIV); + uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); hop1 = hop1->next; } hop = hop->prev; } - msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); - auto s = shared_from_this (); - msg->onDrop = [s]() - { - LogPrint (eLogInfo, "I2NP: Tunnel ", s->GetTunnelID (), " request was not sent"); - s->SetState (i2p::tunnel::eTunnelStateBuildFailed); - }; - + msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); + // 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 - { - auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); - if (msg1) msg = msg1; - } - } - outboundTunnel->SendTunnelDataMsgTo (GetNextIdentHash (), 0, msg); - } + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); else - { - if (m_Config->IsShort () && m_Config->GetLastHop () && - m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent) - { - // add garlic key/tag for reply - uint8_t key[32]; - uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); - if (m_Pool && m_Pool->GetLocalDestination ()) - m_Pool->GetLocalDestination ()->SubmitECIESx25519Key (key, tag); - else - i2p::context.SubmitECIESx25519Key (key, tag); - } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); - } } 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."); + + i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { - // decrypt current hop - if (hop->recordIndex >= 0 && hop->recordIndex < msg[0]) - { - if (!hop->DecryptBuildResponseRecord (msg + 1)) - return false; - } - else - { - LogPrint (eLogWarning, "Tunnel: Hop index ", hop->recordIndex, " is out of range"); - return false; - } - - // decrypt records before current hop - TunnelHopConfig * hop1 = hop->prev; + decryption.SetKey (hop->replyKey); + // decrypt records before and including current hop + TunnelHopConfig * hop1 = hop; while (hop1) { auto idx = hop1->recordIndex; - if (idx >= 0 && idx < num) - hop->DecryptRecord (msg + 1, idx); + if (idx >= 0 && idx < msg[0]) + { + uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; + decryption.SetIV (hop->replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + } else - LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range"); + LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; } hop = hop->prev; } bool established = true; - size_t numHops = 0; hop = m_Config->GetFirstHop (); while (hop) { - uint8_t ret = hop->GetRetCode (msg + 1); + const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; + uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; 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; hop = hop->next; - numHops++; } if (established) { // create tunnel decryptions from layer and iv keys in reverse order - m_Hops.resize (numHops); hop = m_Config->GetLastHop (); - int i = 0; while (hop) { - m_Hops[i].ident = hop->ident; - m_Hops[i].decryption.SetKeys (hop->layerKey, hop->ivKey); + auto tunnelHop = new TunnelHop; + tunnelHop->ident = hop->ident; + tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); + m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; - i++; } - m_IsShortBuildMessage = m_Config->IsShort (); - m_FarEndTransports = m_Config->GetFarEndTransports (); m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; 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) @@ -224,7 +177,7 @@ namespace tunnel uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { - it.decryption.Decrypt (inPayload, outPayload); + it->decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } @@ -245,57 +198,45 @@ namespace tunnel { // hops are in inverted order std::vector > ret; - for (const auto& it: m_Hops) - ret.push_back (it.ident); + for (auto& it: m_Hops) + ret.push_back (it->ident); return ret; } void Tunnel::SetState(TunnelState state) { m_State = state; +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnel.state", this, state); +#endif } - void Tunnel::VisitTunnelHops(TunnelHopVisitor v) + + void Tunnel::PrintHops (std::stringstream& s) const { - // hops are in inverted order, we must return in direct order + // hops are in inverted order, we must print in direct order for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) - v((*it).ident); - } - - 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); - } - } - EncryptTunnelMsg (msg, msg); - msg->from = GetSharedFromThis (); - m_Endpoint.HandleDecryptedTunnelDataMsg (msg); - } - - bool InboundTunnel::Recreate () - { - if (!IsRecreated ()) { - auto pool = GetTunnelPool (); - if (pool) - { - SetRecreated (true); - pool->RecreateInboundTunnel (std::static_pointer_cast(shared_from_this ())); - return true; - } + s << " ⇒ "; + s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); } - return false; - } - + } + + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) + { + if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive + auto newMsg = CreateEmptyTunnelDataMsg (); + EncryptTunnelMsg (msg, newMsg); + newMsg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + } + + void InboundTunnel::Print (std::stringstream& s) const + { + PrintHops (s); + s << " ⇒ " << GetTunnelID () << ":me"; + } + ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) @@ -307,39 +248,38 @@ 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 ZeroHopsInboundTunnel::Print (std::stringstream& s) const + { + s << " ⇒ " << GetTunnelID () << ":me"; + } + + 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) @@ -347,42 +287,32 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); + LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } - bool OutboundTunnel::Recreate () + void OutboundTunnel::Print (std::stringstream& s) const { - if (!IsRecreated ()) - { - auto pool = GetTunnelPool (); - if (pool) - { - SetRecreated (true); - pool->RecreateOutboundTunnel (std::static_pointer_cast(shared_from_this ())); - return true; - } - } - return false; + s << GetTunnelID () << ":me"; + PrintHops (s); + s << " ⇒ "; } - + 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) { - if (!msg.data) continue; - m_NumSentBytes += msg.data->GetLength (); switch (msg.deliveryType) { case eDeliveryTypeLocal: - HandleI2NPMessage (msg.data); + i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); @@ -396,42 +326,30 @@ namespace tunnel } } + void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const + { + s << GetTunnelID () << ":me ⇒ "; + } + 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); @@ -473,7 +391,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) { @@ -487,12 +405,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) { - auto pool = std::make_shared (numInboundHops, numOutboundHops, - numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth); + auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -519,16 +435,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) @@ -541,25 +463,20 @@ namespace tunnel void Tunnels::Run () { - i2p::util::SetThreadName("Tunnels"); 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; + uint64_t lastTs = 0; 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) @@ -578,76 +495,47 @@ namespace tunnel if (tunnel) { if (typeID == eI2NPTunnelData) - tunnel->HandleTunnelDataMsg (std::move (msg)); + tunnel->HandleTunnelDataMsg (msg); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } else - LogPrint (eLogWarning, "Tunnel: Tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); + LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); break; } - case eI2NPShortTunnelBuild: - HandleShortTunnelBuildMsg (msg); - break; case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msg); - break; - case eI2NPShortTunnelBuildReply: - HandleTunnelBuildReplyMsg (msg, true); - break; case eI2NPVariableTunnelBuildReply: - HandleTunnelBuildReplyMsg (msg, false); - break; case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - 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); + 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 >= 15) // manage tunnels every 15 seconds { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= TUNNEL_MANAGE_INTERVAL || // manage tunnels every 15 seconds - ts + TUNNEL_MANAGE_INTERVAL < lastTs) - { - ManageTunnels (ts); - lastTs = ts; - } - if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL || // manage pools every 5 seconds - ts + TUNNEL_POOLS_MANAGE_INTERVAL < lastPoolsTs) - { - ManageTunnelPools (ts); - lastPoolsTs = ts; - } - if (ts - lastMemoryPoolTs >= TUNNEL_MEMORY_POOL_MANAGE_INTERVAL || - ts + TUNNEL_MEMORY_POOL_MANAGE_INTERVAL < lastMemoryPoolTs) // manage memory pool every 2 minutes - { - m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt (); - m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); - lastMemoryPoolTs = ts; - } + ManageTunnels (); + lastTs = ts; } } catch (std::exception& ex) { - LogPrint (eLogError, "Tunnel: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ()); } } } @@ -656,7 +544,7 @@ namespace tunnel { if (!tunnel) { - LogPrint (eLogError, "Tunnel: Missing tunnel for gateway"); + LogPrint (eLogError, "Tunnel: missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); @@ -665,124 +553,50 @@ namespace tunnel msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; if (msg->offset + len > msg->len) { - LogPrint (eLogError, "Tunnel: Gateway payload ", (int)len, " exceeds message length ", (int)msg->len); + LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len); return; } msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); - LogPrint (eLogDebug, "Tunnel: Gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); + 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 (); + ManageTunnelPools (); } - 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; + auto pool = tunnel->GetTunnelPool(); 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"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -791,25 +605,36 @@ 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; } } +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed); +#endif + // for i2lua + if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout); // delete it = pendingTunnels.erase (it); - FailedTunnelCreation(); + m_NumFailedTunnelCreations++; } else ++it; break; case eTunnelStateBuildFailed: - LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed); +#endif + // for i2lua + if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected); + it = pendingTunnels.erase (it); - FailedTunnelCreation(); + m_NumFailedTunnelCreations++; break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed @@ -818,108 +643,118 @@ 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->SetIsRecreated (); + 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 (); if (!inboundTunnel || !router) return; - LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); + LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), false), nullptr + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) ); } } - 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->SetIsRecreated (); + 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++; } } if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); - CreateZeroHopsInboundTunnel (nullptr); - CreateZeroHopsOutboundTunnel (nullptr); + CreateZeroHopsInboundTunnel (); + CreateZeroHopsOutboundTunnel (); if (!m_ExploratoryPool) { int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); 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); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; @@ -930,26 +765,48 @@ namespace tunnel // trying to create one more inbound 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 (); if (!router) { - LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); + LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); return; } - LogPrint (eLogDebug, "Tunnel: Creating one hop inbound tunnel"); + LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }, false), nullptr + std::make_shared (std::vector > { router->GetRouterIdentity () }) ); } } - void Tunnels::ManageTunnelPools (uint64_t ts) + 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 () { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) - pool->ManageTunnels (ts); + { + pool->CreateTunnels (); + pool->TestTunnels (); + } } } @@ -958,17 +815,15 @@ 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); } template - std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, - std::shared_ptr pool, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); - newTunnel->SetTunnelPool (pool); uint32_t replyMsgID; RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); @@ -976,21 +831,20 @@ namespace tunnel return newTunnel; } - std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, - std::shared_ptr pool, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { if (config) - return CreateTunnel(config, pool, outboundTunnel); + return CreateTunnel(config, outboundTunnel); else - return CreateZeroHopsInboundTunnel (pool); + return CreateZeroHopsInboundTunnel (); } - std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) + std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) { if (config) - return CreateTunnel(config, pool); + return CreateTunnel(config); else - return CreateZeroHopsOutboundTunnel (pool); + return CreateZeroHopsOutboundTunnel (); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) @@ -1016,7 +870,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 (); @@ -1024,7 +878,7 @@ namespace tunnel { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash (), false), nullptr, + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), GetNextOutboundTunnel ()); } else @@ -1036,56 +890,45 @@ namespace tunnel } } else - LogPrint (eLogError, "Tunnel: Tunnel with id ", newTunnel->GetTunnelID (), " already exists"); + LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } - std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) + std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () { auto inboundTunnel = std::make_shared (); - inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); - AddTunnel (inboundTunnel); + m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } - std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr pool) + std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () { auto outboundTunnel = std::make_shared (); - outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; } - std::shared_ptr Tunnels::NewI2NPTunnelMessage (bool endpoint) - { - if (endpoint) - { - // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet - auto msg = m_I2NPTunnelEndpointMessagesMemoryPool.AcquireSharedMt (); - msg->Align (6); - msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - return msg; - } - else - { - auto msg = m_I2NPTunnelMessagesMemoryPool.AcquireSharedMt (); - msg->Align (12); - return msg; - } - } - 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 @@ -1099,14 +942,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 5d21cd8b..3244faad 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,11 +1,3 @@ -/* -* 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 TUNNEL_H__ #define TUNNEL_H__ @@ -18,8 +10,6 @@ #include #include #include -#include -#include "util.h" #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" @@ -29,30 +19,54 @@ #include "TunnelGateway.h" #include "TunnelBase.h" #include "I2NPProtocol.h" +#include "Event.h" namespace i2p { namespace tunnel { + + template + static void EmitTunnelEvent(const std::string & ev, const TunnelT & t) + { +#ifdef WITH_EVENTS + EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}}); +#else + (void) ev; + (void) t; +#endif + } + + template + static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const T & val) + { +#ifdef WITH_EVENTS + EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", std::to_string(val)}, {"inbound", std::to_string(t->IsInbound())}}); +#else + (void) ev; + (void) t; + (void) val; +#endif + } + + template + static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const std::string & val) + { +#ifdef WITH_EVENTS + EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", val}, {"inbound", std::to_string(t->IsInbound())}}); +#else + (void) ev; + (void) t; + (void) val; +#endif + } + + const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes 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 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 + const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message enum TunnelState { @@ -67,8 +81,7 @@ namespace tunnel class OutboundTunnel; class InboundTunnel; - class Tunnel: public TunnelBase, - public std::enable_shared_from_this + class Tunnel: public TunnelBase { struct TunnelHop { @@ -78,9 +91,6 @@ namespace tunnel public: - /** function for visiting a hops stored in a tunnel */ - typedef std::function)> TunnelHopVisitor; - Tunnel (std::shared_ptr config); ~Tunnel (); @@ -89,50 +99,46 @@ namespace tunnel 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; }; + void SetIsRecreated () { m_IsRecreated = true; }; 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; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); + virtual void Print (std::stringstream&) const {}; + // 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; } + protected: - /** visit all hops we currently store */ - void VisitTunnelHops(TunnelHopVisitor v); + void PrintHops (std::stringstream& s) const; private: std::shared_ptr m_Config; - std::vector m_Hops; - bool m_IsShortBuildMessage; + std::vector > m_Hops; 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 + bool m_IsRecreated; + uint64_t m_Latency; // in milliseconds }; class OutboundTunnel: public Tunnel @@ -140,18 +146,18 @@ namespace tunnel public: OutboundTunnel (std::shared_ptr config): - Tunnel (config), m_Gateway (*this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; + Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; - void 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 (); }; + void Print (std::stringstream& s) const; // 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 +166,19 @@ 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; + void HandleTunnelDataMsg (std::shared_ptr msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - bool IsInbound() const override { return true; } - bool Recreate () override; + void Print (std::stringstream& s) const; + 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 +189,9 @@ 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); + void Print (std::stringstream& s) const; + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; private: @@ -203,8 +203,9 @@ 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); + void Print (std::stringstream& s) const; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; private: @@ -226,116 +227,78 @@ 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); + std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); + std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); 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); 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 - std::shared_ptr CreateTunnel (std::shared_ptr config, - std::shared_ptr pool, std::shared_ptr outboundTunnel = nullptr); + std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); template 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 ManageTunnelPools (uint64_t ts); + void ManagePendingTunnels (PendingTunnels& pendingTunnels); + void ManageTunnelPools (); - 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; - } + std::shared_ptr CreateZeroHopsInboundTunnel (); + std::shared_ptr CreateZeroHopsOutboundTunnel (); 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; - + + // 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..53782ae3 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,29 +1,14 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef TUNNEL_BASE_H__ #define TUNNEL_BASE_H__ #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,13 +33,13 @@ 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 () {}; virtual void Cleanup () {}; - virtual void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) = 0; + virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; @@ -83,25 +68,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 deleted file mode 100644 index fe0e8573..00000000 --- a/libi2pd/TunnelConfig.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* -* Copyright (c) 2013-2021, 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 "Transports.h" -#include "Timestamp.h" -#include "I2PEndian.h" -#include "I2NPProtocol.h" -#include "TunnelConfig.h" - -namespace i2p -{ -namespace tunnel -{ - TunnelHopConfig::TunnelHopConfig (std::shared_ptr r) - { - RAND_bytes ((uint8_t *)&tunnelID, 4); - if (!tunnelID) tunnelID = 1; // tunnelID can't be zero - isGateway = true; - isEndpoint = true; - ident = r; - //nextRouter = nullptr; - nextTunnelID = 0; - - next = nullptr; - prev = nullptr; - } - - void TunnelHopConfig::SetNextIdent (const i2p::data::IdentHash& ident) - { - nextIdent = ident; - isEndpoint = false; - RAND_bytes ((uint8_t *)&nextTunnelID, 4); - if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero - } - - void TunnelHopConfig::SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) - { - nextIdent = replyIdent; - nextTunnelID = replyTunnelID; - isEndpoint = true; - } - - void TunnelHopConfig::SetNext (TunnelHopConfig * n) - { - next = n; - if (next) - { - next->prev = this; - next->isGateway = false; - isEndpoint = false; - nextIdent = next->ident->GetIdentHash (); - nextTunnelID = next->tunnelID; - } - } - - void TunnelHopConfig::SetPrev (TunnelHopConfig * p) - { - prev = p; - if (prev) - { - prev->next = this; - prev->isEndpoint = false; - isGateway = false; - } - } - - void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const - { - 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); - } - - void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) - { - if (!ident) return; - i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); - auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); - MixHash (encrypted, 32); // h = SHA256(h || sepk) - encrypted += 32; - uint8_t sharedSecret[32]; - ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) - MixKey (sharedSecret); - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); - return; - } - MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) - } - - bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const - { - return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt - } - - void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) - { - // generate keys - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); - // fill clear text - uint8_t flag = 0; - if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; - if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); - // encrypt - uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; - EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } - - bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const - { - uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) - { - LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); - return false; - } - return true; - } - - void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) - { - // fill clear text - uint8_t flag = 0; - if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; - if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; - uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; - htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; - memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); - clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES - htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); - htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes - htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); - // encrypt - uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; - EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); - // derive keys - i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); - memcpy (replyKey, m_CK + 32, 32); - i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); - memcpy (layerKey, m_CK + 32, 32); - if (isEndpoint) - { - i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); - memcpy (ivKey, m_CK + 32, 32); - i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK - } - else - memcpy (ivKey, m_CK, 32); // last HKDF - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } - - bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const - { - uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; - uint8_t nonce[12]; - memset (nonce, 0, 12); - nonce[4] = recordIndex; // nonce is record index - if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) - { - LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); - return false; - } - return true; - } - - void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const - { - uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; - uint8_t nonce[12]; - memset (nonce, 0, 12); - nonce[4] = index; // nonce is index - i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); - } - - uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const - { - uint64_t tag; - memcpy (&tag, m_CK, 8); - memcpy (key, m_CK + 32, 32); - return tag; - } - - void TunnelConfig::CreatePeers (const std::vector >& peers) - { - TunnelHopConfig * prev = nullptr; - for (const auto& it: peers) - { - TunnelHopConfig * hop = nullptr; - if (m_IsShort) - hop = new ShortECIESTunnelHopConfig (it); - else - { - if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - hop = new LongECIESTunnelHopConfig (it); - else - LogPrint (eLogError, "Tunnel: ElGamal router is not supported"); - } - if (hop) - { - if (prev) - prev->SetNext (hop); - else - m_FirstHop = hop; - prev = hop; - } - } - m_LastHop = prev; - } -} -} \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 718a6fdb..48e66f2e 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,18 +1,13 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ +#include +#include #include +#include #include "Identity.h" #include "RouterContext.h" -#include "Crypto.h" +#include "Timestamp.h" namespace i2p { @@ -32,74 +27,107 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - TunnelHopConfig (std::shared_ptr r); - virtual ~TunnelHopConfig () {}; + TunnelHopConfig (std::shared_ptr r) + { + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); + RAND_bytes ((uint8_t *)&tunnelID, 4); + if (!tunnelID) tunnelID = 1; // tunnelID can't be zero + isGateway = true; + isEndpoint = true; + ident = r; + //nextRouter = nullptr; + nextTunnelID = 0; - void SetNextIdent (const i2p::data::IdentHash& ident); - void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); - void SetNext (TunnelHopConfig * n); - void SetPrev (TunnelHopConfig * p); + next = nullptr; + prev = nullptr; + } - virtual uint8_t GetRetCode (const uint8_t * records) const = 0; - virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; - virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; - virtual void DecryptRecord (uint8_t * records, int index) const; // AES - virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag - }; + void SetNextIdent (const i2p::data::IdentHash& ident) + { + nextIdent = ident; + isEndpoint = false; + RAND_bytes ((uint8_t *)&nextTunnelID, 4); + if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero + } - struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState - { - ECIESTunnelHopConfig (std::shared_ptr r): - TunnelHopConfig (r) {}; - void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); - bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; - }; + void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) + { + nextIdent = replyIdent; + nextTunnelID = replyTunnelID; + isEndpoint = true; + } - struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig - { - LongECIESTunnelHopConfig (std::shared_ptr r): - ECIESTunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const override - { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; - bool DecryptBuildResponseRecord (uint8_t * records) const override; - }; + void SetNext (TunnelHopConfig * n) + { + next = n; + if (next) + { + next->prev = this; + next->isGateway = false; + isEndpoint = false; + nextIdent = next->ident->GetIdentHash (); + nextTunnelID = next->tunnelID; + } + } - struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig - { - ShortECIESTunnelHopConfig (std::shared_ptr r): - ECIESTunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const override - { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; - bool DecryptBuildResponseRecord (uint8_t * records) const override; - void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 - uint64_t GetGarlicKey (uint8_t * key) const override; + void SetPrev (TunnelHopConfig * p) + { + prev = p; + if (prev) + { + prev->next = this; + prev->isEndpoint = false; + isGateway = false; + } + } + + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const + { + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); + htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + uint8_t flag = 0; + if (isGateway) flag |= 0x80; + if (isEndpoint) flag |= 0x40; + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); + htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); + auto encryptor = ident->CreateEncryptor (nullptr); + if (encryptor) + encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } }; class TunnelConfig { public: - TunnelConfig (const std::vector >& peers, - bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // inbound - m_IsShort (isShort), m_FarEndTransports (farEndTransports) + TunnelConfig (std::vector > peers) // inbound { 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 - m_IsShort (isShort), m_FarEndTransports (farEndTransports) + TunnelConfig (std::vector > peers, + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound { CreatePeers (peers); m_FirstHop->isGateway = false; m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } - virtual ~TunnelConfig () + ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; @@ -111,13 +139,6 @@ namespace tunnel } } - bool IsShort () const { return m_IsShort; } - - i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const - { - return m_FarEndTransports; - } - TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -181,25 +202,34 @@ namespace tunnel return peers; } - size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; }; - protected: // this constructor can't be called from outside - TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false), - m_FarEndTransports (i2p::data::RouterInfo::eAllTransports) + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) { } private: - void CreatePeers (const std::vector >& peers); + template + void CreatePeers (const Peers& peers) + { + TunnelHopConfig * prev = nullptr; + for (const auto& it: peers) + { + auto hop = new TunnelHopConfig (it); + if (prev) + prev->SetNext (hop); + else + m_FirstHop = hop; + prev = hop; + } + m_LastHop = prev; + } private: TunnelHopConfig * m_FirstHop, * m_LastHop; - bool m_IsShort; - i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; }; class ZeroHopsTunnelConfig: public TunnelConfig diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 66b7effa..324d5315 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,11 +1,3 @@ -/* -* 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 "I2PEndian.h" #include #include "Crypto.h" @@ -21,13 +13,16 @@ namespace i2p { namespace tunnel { - + TunnelEndpoint::~TunnelEndpoint () + { + } + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16 - uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // without 4-byte checksum + uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum if (zero) { uint8_t * fragment = zero + 1; @@ -37,7 +32,7 @@ namespace tunnel SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { - LogPrint (eLogError, "TunnelMessage: Checksum verification failed"); + LogPrint (eLogError, "TunnelMessage: checksum verification failed"); return; } // process fragments @@ -49,28 +44,28 @@ namespace tunnel bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; + TunnelMessageBlockEx m; if (!isFollowOnFragment) { // first fragment - if (m_CurrentMsgID) - AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete - m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); - switch (m_CurrentMessage.deliveryType) + m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); + switch (m.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 - m_CurrentMessage.tunnelID = bufbe32toh (fragment); + m.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID - m_CurrentMessage.hash = i2p::data::IdentHash (fragment); + m.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 - m_CurrentMessage.hash = i2p::data::IdentHash (fragment); + m.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; - default: ; + default: + ; } bool isFragmented = flag & 0x08; @@ -79,7 +74,6 @@ namespace tunnel // Message ID msgID = bufbe32toh (fragment); fragment += 4; - m_CurrentMsgID = msgID; isLastFragment = false; } } @@ -95,78 +89,78 @@ namespace tunnel uint16_t size = bufbe16toh (fragment); fragment += 2; - // handle fragment - if (isFollowOnFragment) + msg->offset = fragment - msg->buf; + msg->len = msg->offset + size; + if (msg->len > msg->maxLen) { - // existing message - if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) - HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous - else - { - HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } + LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); + return; + } + if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) + { + // this is not last message. we have to copy it + m.data = NewI2NPTunnelMessage (); + m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; + *(m.data) = *msg; } else - { - // new message - msg->offset = fragment - msg->buf; - msg->len = msg->offset + size; - // check message size - if (msg->len > msg->maxLen) - { - LogPrint (eLogError, "TunnelMessage: Fragment is too long ", (int)size); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - return; - } - // create new or assign I2NP message - if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) - { - // this is not last message. we have to copy it - m_CurrentMessage.data = NewI2NPTunnelMessage (true); - *(m_CurrentMessage.data) = *msg; - } - else - m_CurrentMessage.data = msg; + m.data = msg; - if (isLastFragment) + if (!isFollowOnFragment && isLastFragment) + HandleNextMessage (m); + else + { + if (msgID) // msgID is presented, assume message is fragmented { - // single message - HandleNextMessage (m_CurrentMessage); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } - else if (msgID) - { - // first fragment of a new message - m_CurrentMessage.nextFragmentNum = 1; - m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); - HandleOutOfSequenceFragments (msgID, m_CurrentMessage); + if (!isFollowOnFragment) // create new incomlete message + { + m.nextFragmentNum = 1; + m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); + auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); + if (ret.second) + HandleOutOfSequenceFragments (msgID, ret.first->second); + else + LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); + } + else + { + m.nextFragmentNum = fragmentNum; + HandleFollowOnFragment (msgID, isLastFragment, m); + } } else - { LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } } fragment += size; } } else - LogPrint (eLogError, "TunnelMessage: Zero not found"); + LogPrint (eLogError, "TunnelMessage: zero not found"); } - void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, - uint8_t fragmentNum, const uint8_t * fragment, size_t size) + void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) { + auto fragment = m.data->GetBuffer (); + auto size = m.data->GetLength (); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; - if (fragmentNum == msg.nextFragmentNum) + if (m.nextFragmentNum == msg.nextFragmentNum) { - if (ConcatFollowOnFragment (msg, fragment, size)) + if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long { + if (msg.data->len + size > msg.data->maxLen) + { + // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); + auto newMsg = NewI2NPMessage (); + *newMsg = *(msg.data); + msg.data = newMsg; + } + if (msg.data->Concat (fragment, size) < size) // concatenate fragment + LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -181,86 +175,27 @@ namespace tunnel } else { - LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); + LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { - LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); - AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); + LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); + AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } else { - LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); - AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); + LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); + AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } - bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const + void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) { - if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long - { - 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); - *newMsg = *(msg.data); - msg.data = newMsg; - } - if (msg.data->Concat (fragment, size) < size) // concatenate fragment - { - LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); - return false; - } - } - else - return false; - return true; - } - - void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment) - { - if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size)) - { - if (isLastFragment) - { - // message complete - HandleNextMessage (m_CurrentMessage); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } - else - { - m_CurrentMessage.nextFragmentNum++; - HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage); - } - } - else - { - LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped"); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } - } - - void TunnelEndpoint::AddIncompleteCurrentMessage () - { - if (m_CurrentMsgID) - { - auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage); - if (!ret.second) - LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists"); - m_CurrentMessage.data = nullptr; - m_CurrentMsgID = 0; - } - } - - 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) - LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); + if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) + LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg) @@ -270,14 +205,7 @@ namespace tunnel if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); - if (&msg == &m_CurrentMessage) - { - m_CurrentMsgID = 0; - m_CurrentMessage.data = nullptr; - } - else - m_IncompleteMessages.erase (msgID); - LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found"); + m_IncompleteMessages.erase (msgID); break; } } @@ -285,19 +213,19 @@ namespace tunnel bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { - auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum); + auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); 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->GetLength (); 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->GetBuffer (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (it->second.isLastFragment) // message complete @@ -314,12 +242,16 @@ namespace tunnel { if (!m_IsInbound && msg.data->IsExpired ()) { - LogPrint (eLogInfo, "TunnelMessage: Message expired"); + LogPrint (eLogInfo, "TunnelMessage: message expired"); return; } uint8_t typeID = msg.data->GetTypeID (); - LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); - + 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 +259,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; @@ -362,35 +294,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..c2ffe53d 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,20 +1,9 @@ -/* -* 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 TUNNEL_ENDPOINT_H__ #define TUNNEL_ENDPOINT_H__ #include -#include -#include +#include #include -#include -#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -22,7 +11,7 @@ namespace i2p { namespace tunnel { - class TunnelEndpoint final + class TunnelEndpoint { struct TunnelMessageBlockEx: public TunnelMessageBlock { @@ -32,51 +21,35 @@ 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); }; bool isLastFragment; + std::shared_ptr data; uint64_t receiveTime; // milliseconds since epoch - std::vector data; }; public: - TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; - ~TunnelEndpoint () = default; + TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; + ~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 HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); 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); + void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); - void AddIncompleteCurrentMessage (); private: - std::unordered_map m_IncompleteMessages; - std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + std::map m_IncompleteMessages; + std::map, Fragment> m_OutOfSequenceFragments; // (msgID, 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..7d0069a9 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "Crypto.h" #include "I2PEndian.h" @@ -19,14 +11,16 @@ namespace i2p namespace tunnel { TunnelGatewayBuffer::TunnelGatewayBuffer (): - m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0), m_NonZeroRandomBuffer (nullptr) + m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) { + RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); + for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) + if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; } TunnelGatewayBuffer::~TunnelGatewayBuffer () { ClearTunnelDataMsgs (); - if (m_NonZeroRandomBuffer) delete[] m_NonZeroRandomBuffer; } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) @@ -35,13 +29,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; } @@ -64,14 +51,14 @@ namespace tunnel // create fragments const std::shared_ptr & msg = block.data; size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length - + if (!messageCreated && fullMsgLen > m_RemainingSize) // check if we should complete previous message { size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; // length of bytes doesn't fit full tunnel message // every follow-on fragment adds 7 bytes size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; - if (!nonFit || nonFit > m_RemainingSize || m_RemainingSize < fullMsgLen/5) + if (!nonFit || nonFit > m_RemainingSize) { CompleteCurrentTunnelDataMessage (); CreateCurrentTunnelDataMessage (); @@ -162,7 +149,9 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { - m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size + m_CurrentTunnelDataMsg = nullptr; + m_CurrentTunnelDataMsg = NewI2NPShortMessage (); + m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset; @@ -183,17 +172,10 @@ namespace tunnel SHA256(payload, size+16, hash); memcpy (buf+20, hash, 4); // checksum payload[-1] = 0; // zero - ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 + ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 if (paddingSize > 0) { // non-zero padding - if (!m_NonZeroRandomBuffer) // first time? - { - m_NonZeroRandomBuffer = new uint8_t[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; - RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); - for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) - if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; - } auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1); memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize); } @@ -220,24 +202,21 @@ 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 ()); + auto newMsg = CreateEmptyTunnelDataMsg (); + 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..7959b57b 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef TUNNEL_GATEWAY_H__ #define TUNNEL_GATEWAY_H__ @@ -38,27 +30,25 @@ namespace tunnel std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; - uint8_t * m_NonZeroRandomBuffer; + uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; }; class TunnelGateway { 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..4f740a09 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "I2PEndian.h" #include "Crypto.h" @@ -13,54 +5,25 @@ #include "NetDb.hpp" #include "Timestamp.h" #include "Garlic.h" -#include "ECIESX25519AEADRatchetSession.h" #include "Transports.h" #include "Log.h" #include "Tunnel.h" #include "TunnelPool.h" #include "Destination.h" +#ifdef WITH_EVENTS +#include "Event.h" +#endif namespace i2p { namespace tunnel { - void Path::Add (std::shared_ptr r) - { - if (r) - { - peers.push_back (r->GetRouterIdentity ()); - if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION || - r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - isShort = false; - } - } - void Path::Reverse () - { - std::reverse (peers.begin (), peers.end ()); - } - - TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, - int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth): + TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): 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_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), + m_CustomPeerSelector(nullptr) { - if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) - m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY; - if (m_NumOutboundTunnels > TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY) - m_NumOutboundTunnels = TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY; - if (m_InboundVariance < 0 && m_NumInboundHops + m_InboundVariance <= 0) - m_InboundVariance = m_NumInboundHops ? -m_NumInboundHops + 1 : 0; - if (m_OutboundVariance < 0 && m_NumOutboundHops + m_OutboundVariance <= 0) - m_OutboundVariance = m_NumOutboundHops ? -m_NumOutboundHops + 1 : 0; - if (m_InboundVariance > 0 && m_NumInboundHops + m_InboundVariance > STANDARD_NUM_RECORDS) - 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; } TunnelPool::~TunnelPool () @@ -77,12 +40,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,14 +66,10 @@ 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) - { + bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) { if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0) { m_NumInboundHops = inHops; @@ -121,39 +80,33 @@ namespace tunnel } return false; } - + void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnels.created", createdTunnel); +#endif std::unique_lock l(m_InboundTunnelsMutex); - if (createdTunnel->IsRecreated ()) - { - // find and mark old tunnel as expired - createdTunnel->SetRecreated (false); - for (auto& it: m_InboundTunnels) - if (it->IsRecreated () && it->GetNextIdentHash () == createdTunnel->GetNextIdentHash ()) - { - it->SetState (eTunnelStateExpiring); - break; - } - } m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) - m_LocalDestination->SetLeaseSetUpdated (true); + m_LocalDestination->SetLeaseSetUpdated (); + + OnTunnelBuildResult(createdTunnel, eBuildResultOkay); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnels.expired", expiredTunnel); +#endif 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); @@ -164,21 +117,27 @@ namespace tunnel { if (!m_IsActive) return; { +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnels.created", createdTunnel); +#endif std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } + OnTunnelBuildResult(createdTunnel, eBuildResultOkay); + + //CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { +#ifdef WITH_EVENTS + EmitTunnelEvent("tunnels.expired", expiredTunnel); +#endif 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); @@ -189,57 +148,43 @@ namespace tunnel { std::vector > v; int i = 0; - std::shared_ptr slowTunnel; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { - if (it->IsSlow () && !slowTunnel) - slowTunnel = it; - else - { - v.push_back (it); - i++; - } + v.push_back (it); + i++; } } - if (slowTunnel && (int)v.size () < (num/2+1)) - v.push_back (slowTunnel); return v; } - std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, - i2p::data::RouterInfo::CompatibleTransports compatible) + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, excluded, compatible); + return GetNextTunnel (m_OutboundTunnels, excluded); } - std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, - i2p::data::RouterInfo::CompatibleTransports compatible) + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, excluded, compatible); + return GetNextTunnel (m_InboundTunnels, excluded); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const { if (tunnels.empty ()) return nullptr; - uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; - bool skipped = false; + uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { - if (it->IsEstablished () && it != excluded && (compatible & it->GetFarEndTransports ())) + if (it->IsEstablished () && it != excluded) { - if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && - !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) - { - i++; skipped = true; + if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { + i ++; continue; } tunnel = it; @@ -247,15 +192,14 @@ namespace tunnel } if (i > ind && tunnel) break; } - if (!tunnel && skipped) - { - ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; + if(HasLatencyRequirement() && !tunnel) { + ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { - tunnel = it; - i++; + tunnel = it; + i++; } if (i > ind && tunnel) break; } @@ -264,11 +208,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 +224,8 @@ namespace tunnel } if (!tunnel) - { tunnel = GetNextOutboundTunnel (); - freshTunnel = true; - } - return std::make_pair(tunnel, freshTunnel); + return tunnel; } void TunnelPool::CreateTunnels () @@ -296,13 +236,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 +245,11 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0 && - m_NumInboundHops == m_NumOutboundHops) - { - for (auto it: m_OutboundTunnels) - { - // try to create inbound tunnel through the same path as successive outbound - CreatePairedInboundTunnel (it); - num++; - if (num >= m_NumInboundTunnels) break; - } - } - 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 () @@ -343,7 +262,7 @@ namespace tunnel for (auto& it: tests) { - LogPrint (eLogWarning, "Tunnels: Test of tunnel ", it.first, " failed"); + LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { @@ -351,15 +270,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,109 +281,45 @@ 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; + auto it1 = m_OutboundTunnels.begin (); + 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) - { - if (ts > m_NextManageTime || ts + 2*TUNNEL_POOL_MANAGE_INTERVAL < m_NextManageTime) // in case if clock was adjusted - { - CreateTunnels (); - TestTunnels (); - m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL)/2; } } @@ -479,29 +328,16 @@ namespace tunnel if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else - LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); + LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); } 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,175 +352,104 @@ 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 - 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) - test.first->SetState (eTunnelStateEstablished); - // update latency - int latency = 0; - if (numHops) latency = dlt*test.first->GetNumHops ()/numHops; - if (!latency) latency = dlt/2; - test.first->AddLatencySample (latency); - } - if (test.second) - { - if (test.second->GetState () != eTunnelStateExpiring) - test.second->SetState (eTunnelStateEstablished); - // update latency - int latency = 0; - if (numHops) latency = dlt*test.second->GetNumHops ()/numHops; - if (!latency) latency = dlt/2; - test.second->AddLatencySample (latency); - } + if (test.first->GetState () == eTunnelStateTestFailed) + test.first->SetState (eTunnelStateEstablished); + if (test.second->GetState () == eTunnelStateTestFailed) + test.second->SetState (eTunnelStateEstablished); + uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; + LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); + // update latency + uint64_t latency = dlt / 2; + test.first->AddLatencySample(latency); + test.second->AddLatencySample(latency); + } + else + { + if (m_LocalDestination) + m_LocalDestination->ProcessDeliveryStatusMessage (msg); + else + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } - return found; - } - - bool TunnelPool::IsExploratory () const - { - return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); } - std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, - bool reverse, bool endpoint) const + std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop) 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; - } + bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); + auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): + i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); + + if (!hop || hop->GetProfile ()->IsBad ()) + hop = i2p::data::netdb.GetRandomRouter (prevHop); return hop; } - bool TunnelPool::StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) + bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) { - int start = 0; - std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); + auto prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ auto hop = i2p::transport::transports.GetRestrictedPeer(); if(!hop) return false; - path.Add (hop); + peers.push_back(hop->GetRouterIdentity()); prevHop = hop; - start++; } - else if (i2p::transport::transports.GetNumPeers () > 100 || - (inbound && i2p::transport::transports.GetNumPeers () > 25)) + else if (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->GetProfile ()->IsBad ()) { prevHop = r; - path.Add (r); - start++; + peers.push_back (r->GetRouterIdentity ()); + numHops--; } } - for(int i = start; i < numHops; i++ ) + for(int i = 0; i < numHops; i++ ) { - auto hop = nextHop (prevHop, inbound, i == numHops - 1); - if (!hop && !i) // if no suitable peer found for first hop, try already connected - { - LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); - hop = i2p::transport::transports.GetRandomPeer (false); - if (hop && !hop->IsECIES ()) hop = nullptr; - } + auto hop = nextHop (prevHop); if (!hop) { LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } prevHop = hop; - path.Add (hop); + peers.push_back (hop->GetRouterIdentity ()); } - path.farEndTransports = prevHop->GetCompatibleTransports (inbound); // last hop return true; } - bool TunnelPool::SelectPeers (Path& path, bool isInbound) + bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) { - // explicit peers in use - if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); - // calculate num hops - int numHops; - if (isInbound) - { - numHops = m_NumInboundHops; - if (m_InboundVariance) - { - int offset = m_Rng () % (std::abs (m_InboundVariance) + 1); - if (m_InboundVariance < 0) offset = -offset; - numHops += offset; - } - } - else - { - numHops = m_NumOutboundHops; - if (m_OutboundVariance) - { - int offset = m_Rng () % (std::abs (m_OutboundVariance) + 1); - if (m_OutboundVariance < 0) offset = -offset; - numHops += offset; - } - } + int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty if (numHops <= 0) return true; // custom peer selector in use ? { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) - return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); + return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); } - return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + // explicit peers in use + if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); + return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1)); } - bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) + bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) { - if (!m_ExplicitPeers->size ()) return false; + int size = m_ExplicitPeers->size (); + std::vector peerIndicies; + for (int i = 0; i < size; i++) peerIndicies.push_back(i); + std::random_shuffle (peerIndicies.begin(), peerIndicies.end()); + int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; - if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); for (int i = 0; i < numHops; i++) { - auto& ident = (*m_ExplicitPeers)[i]; + auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - { - if (r->IsECIES ()) - { - path.Add (r); - if (i == numHops - 1) - path.farEndTransports = r->GetCompatibleTransports (isInbound); - } - else - { - LogPrint (eLogError, "Tunnels: ElGamal router ", ident.ToBase64 (), " is not supported"); - return false; - } - } + peers.push_back (r->GetRouterIdentity ()); else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); @@ -697,20 +462,21 @@ namespace tunnel void TunnelPool::CreateInboundTunnel () { + auto outboundTunnel = GetNextOutboundTunnel (); + if (!outboundTunnel) + outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); - Path path; - if (SelectPeers (path, true)) + std::vector > peers; + if (SelectPeers (peers, true)) { - auto outboundTunnel = GetNextOutboundTunnel (nullptr, path.farEndTransports); - if (!outboundTunnel) - outboundTunnel = tunnels.GetNextOutboundTunnel (); std::shared_ptr config; if (m_NumInboundHops > 0) { - path.Reverse (); - config = std::make_shared (path.peers, path.isShort, path.farEndTransports); + std::reverse (peers.begin (), peers.end ()); + config = std::make_shared (peers); } - auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); + auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); + tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } @@ -720,96 +486,67 @@ namespace tunnel void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { - if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow - { - CreateInboundTunnel (); - return; - } - auto outboundTunnel = GetNextOutboundTunnel (nullptr, tunnel->GetFarEndTransports ()); + auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; - if (m_NumInboundHops > 0) + if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) { - auto peers = tunnel->GetPeers(); - if (peers.size ()&& ValidatePeers (peers)) - config = std::make_shared(tunnel->GetPeers (), - tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); - } - if (!m_NumInboundHops || config) + config = std::make_shared(tunnel->GetPeers ()); + } + if (m_NumInboundHops == 0 || config) { - auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); + auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); + newTunnel->SetTunnelPool (shared_from_this()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); - else - newTunnel->SetRecreated (true); } } void TunnelPool::CreateOutboundTunnel () { - LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); - Path path; - if (SelectPeers (path, false)) + auto inboundTunnel = GetNextInboundTunnel (); + if (!inboundTunnel) + inboundTunnel = tunnels.GetNextInboundTunnel (); + if (inboundTunnel) { - auto inboundTunnel = GetNextInboundTunnel (nullptr, path.farEndTransports); - if (!inboundTunnel) - inboundTunnel = tunnels.GetNextInboundTunnel (); - if (!inboundTunnel) + LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); + std::vector > peers; + if (SelectPeers (peers, false)) { - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); - return; - } - - if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - path.isShort = false; // because can't handle ECIES encrypted reply - - std::shared_ptr config; - if (m_NumOutboundHops > 0) - config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports); - - std::shared_ptr tunnel; - if (path.isShort) - { - // TODO: implement it better - tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); + std::shared_ptr config; + if (m_NumOutboundHops > 0) + config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); + auto tunnel = tunnels.CreateOutboundTunnel (config); tunnel->SetTunnelPool (shared_from_this ()); + if (tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } else - tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); - if (tunnel && tunnel->IsEstablished ()) // zero hops - TunnelCreated (tunnel); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { - if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow - { - CreateOutboundTunnel (); - return; - } - auto inboundTunnel = GetNextInboundTunnel (nullptr, tunnel->GetFarEndTransports ()); + auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { 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 ()); } - if (!m_NumOutboundHops || config) + if(m_NumOutboundHops == 0 || config) { - auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); + auto newTunnel = tunnels.CreateOutboundTunnel (config); + newTunnel->SetTunnelPool (shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } @@ -821,12 +558,8 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateInboundTunnel ( - m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers (), - outboundTunnel->IsShortBuildMessage ()) : nullptr, - shared_from_this (), outboundTunnel); - if (tunnel->IsEstablished ()) // zero hops - TunnelCreated (tunnel); + auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); + tunnel->SetTunnelPool (shared_from_this ()); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) @@ -846,26 +579,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 +599,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(); @@ -892,5 +610,11 @@ namespace tunnel } return tun; } + + void TunnelPool::OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result) + { + auto peers = tunnel->GetPeers(); + if(m_CustomPeerSelector) m_CustomPeerSelector->OnBuildResult(peers, tunnel->IsInbound(), result); + } } } diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 0ebfd1ac..fc46930c 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef TUNNEL_POOL__ #define TUNNEL_POOL__ @@ -15,7 +7,6 @@ #include #include #include -#include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" @@ -28,41 +19,38 @@ namespace i2p { 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; class OutboundTunnel; - typedef std::shared_ptr Peer; - struct Path - { - std::vector peers; - bool isShort = true; - i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports; - void Add (std::shared_ptr r); - void Reverse (); + enum TunnelBuildResult { + eBuildResultOkay, // tunnel was built okay + eBuildResultRejected, // tunnel build was explicitly rejected + eBuildResultTimeout // tunnel build timed out }; + typedef std::shared_ptr Peer; + typedef std::vector Path; + /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector { virtual ~ITunnelPeerSelector() {}; virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; + virtual bool OnBuildResult(const Path & peers, bool isInbound, TunnelBuildResult result) = 0; }; + + typedef std::function(std::shared_ptr)> SelectHopFunc; + // standard peer selection algorithm + bool StandardSelectPeers(Path & path, int hops, 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); + TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -77,18 +65,13 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr 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); - 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); - void ManageTunnels (uint64_t ts); + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; + void TestTunnels (); 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; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); @@ -100,45 +83,40 @@ namespace tunnel /** i2cp reconfigure */ bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant); - + void SetCustomPeerSelector(ITunnelPeerSelector * selector); void UnsetCustomPeerSelector(); 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; } + /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ + void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } - /** @brief return true if this tunnel pool has a latency requirement */ - bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } + /** @brief return true if this tunnel pool has a latency requirement */ + bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } - /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ - std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude = nullptr) const; - std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude = nullptr) const; + /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ + std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; + 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); + void OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result); + + // for overriding tunnel peer selection + std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; - std::mt19937& GetRng () { return m_Rng; } - private: - void TestTunnels (); void CreateInboundTunnel (); void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template - typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible); - bool SelectPeers (Path& path, bool isInbound); - bool SelectExplicitPeers (Path& path, bool isInbound); - bool ValidatePeers (std::vector >& peers) const; + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; + bool SelectPeers (std::vector >& hops, bool isInbound); + bool SelectExplicitPeers (std::vector >& hops, bool isInbound); private: std::shared_ptr m_LocalDestination; - int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels, - m_InboundVariance, m_OutboundVariance; + int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first @@ -146,16 +124,13 @@ namespace tunnel std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; - bool m_IsActive, m_IsHighBandwidth; - uint64_t m_NextManageTime; // in seconds + bool m_IsActive; 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..ca72ae49 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include "Config.h" @@ -39,11 +31,8 @@ namespace api bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); i2p::crypto::InitCrypto (precomputation); - int netID; i2p::config::GetOption("netid", netID); - i2p::context.SetNetID (netID); - - bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); - i2p::transport::transports.SetCheckReserved(checkReserved); + int netID; i2p::config::GetOption("netid", netID); + i2p::context.SetNetID (netID); i2p::context.Init (); } @@ -60,27 +49,22 @@ namespace api else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); - i2p::transport::InitTransports (); - LogPrint(eLogInfo, "API: Starting NetDB"); + LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); - LogPrint(eLogInfo, "API: Starting Transports"); + LogPrint(eLogInfo, "API: starting Transports"); i2p::transport::transports.Start(); - LogPrint(eLogInfo, "API: Starting Tunnels"); + 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"); + LogPrint(eLogInfo, "API: shutting down"); + LogPrint(eLogInfo, "API: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); - LogPrint(eLogInfo, "API: Stopping Transports"); + LogPrint(eLogInfo, "API: stopping Transports"); i2p::transport::transports.Stop(); - LogPrint(eLogInfo, "API: Stopping NetDB"); + LogPrint(eLogInfo, "API: stopping NetDB"); i2p::data::netdb.Stop(); i2p::log::Logger().Stop (); } @@ -93,7 +77,7 @@ namespace api std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { - auto localDestination = std::make_shared (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } @@ -102,7 +86,7 @@ namespace api const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); - auto localDestination = std::make_shared (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } @@ -149,3 +133,4 @@ namespace api } } } + diff --git a/libi2pd/api.h b/libi2pd/api.h index 9b0256d8..f64590d1 100644 --- a/libi2pd/api.h +++ b/libi2pd/api.h @@ -1,11 +1,3 @@ -/* -* 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 API_H__ #define API_H__ @@ -43,3 +35,4 @@ namespace api } #endif + diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 925cf629..96e8ad4c 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,45 +1,11 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include -#include -#include #include #include "util.h" #include "Log.h" -#include "I2PEndian.h" -#if !defined (__FreeBSD__) && !defined(_MSC_VER) -#include -#endif - -#if defined(__OpenBSD__) || defined(__FreeBSD__) -#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 +#ifdef WIN32 #include #include #include @@ -49,25 +15,16 @@ #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 +#ifdef _MSC_VER +#pragma comment(lib, "IPHLPAPI.lib") +#endif // _MSC_VER #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 haven't 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) +int inet_pton_xp(int af, const char *src, void *dst) { struct sockaddr_storage ss; int size = sizeof (ss); @@ -91,145 +48,40 @@ 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 */ +#else /* !WIN32 => UNIX */ #include -#ifdef ANDROID -#include "ifaddrs.h" -#else #include #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 () } namespace i2p { namespace util { - - void RunnableService::StartIOService () - { - if (!m_IsRunning) - { - m_IsRunning = true; - m_Thread.reset (new std::thread (std::bind (& RunnableService::Run, this))); - } - } - - void RunnableService::StopIOService () - { - if (m_IsRunning) - { - m_IsRunning = false; - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - m_Thread = nullptr; - } - } - } - - void RunnableService::Run () - { - SetThreadName(m_Name.c_str()); - - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, m_Name, ": Runtime exception: ", ex.what ()); - } - } - } - - 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 - 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 - int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) +#ifdef WIN32 + bool IsWindowsXPorLater() { - 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 + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + + if (osvi.dwMajorVersion <= 5) + return true; + else + return false; + } + + int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) + { 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 +92,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 +123,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) + 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 +146,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 +177,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,12 +188,12 @@ 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; } - int GetMTUWindows (const boost::asio::ip::address& localAddress, int fallback) + int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); @@ -360,33 +202,32 @@ namespace net std::string localAddressUniversal = localAddress.to_string(); #endif - typedef int (* IPN)(int af, const char *src, void *dst); - IPN inetpton = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); - if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found + if (IsWindowsXPorLater()) + { + #define inet_pton inet_pton_xp + } - if (localAddress.is_v4()) + if(localAddress.is_v4()) { sockaddr_in inputAddress; - inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + inet_pton(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)); + inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); - } - else - { - LogPrint(eLogError, "NetIface: GetMTU: Address family is not supported"); + } else { + LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); return fallback; } } #else // assume unix - int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) + 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 +235,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) + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query + if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); @@ -431,18 +272,18 @@ namespace net LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else - LogPrint(eLogWarning, "NetIface: Interface for local address", localAddress.to_string(), " not found"); + LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; } -#endif // _WIN32 +#endif // WIN32 - int GetMTU (const boost::asio::ip::address& localAddress) + int GetMTU(const boost::asio::ip::address& localAddress) { - int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU + const int fallback = 576; // fallback MTU -#ifdef _WIN32 +#ifdef WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); @@ -450,251 +291,55 @@ namespace net return fallback; } - const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6) + const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) { -#ifdef _WIN32 - LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32"); - if (ipv6) - return boost::asio::ip::make_address("::1"); +#ifdef WIN32 + LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); + if(ipv6) + return boost::asio::ip::address::from_string("::1"); 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; - try + ifaddrs * addrs = nullptr; + if(getifaddrs(&addrs) == 0) { - if (!getifaddrs(&addrs)) + // got ifaddrs + ifaddrs * cur = addrs; + while(cur) { - for (auto cur = addrs; cur; cur = cur->ifa_next) + std::string cur_ifname(cur->ifa_name); + if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { - std::string cur_ifname(cur->ifa_name); - if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) - { - // match - char addr[INET6_ADDRSTRLEN]; - memset (addr, 0, INET6_ADDRSTRLEN); - 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); - } + // match + char * addr = new char[INET6_ADDRSTRLEN]; + bzero(addr, INET6_ADDRSTRLEN); + 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); + delete[] addr; + return boost::asio::ip::address::from_string(cur_ifaddr); } + cur = cur->ifa_next; } } - catch (std::exception& ex) - { - 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); + LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname); } else { fallback = "127.0.0.1"; - LogPrint(eLogWarning, "NetIface: Cannot find IPv4 address for interface ", ifname); + 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; - } - - bool IsYggdrasilAddress (const boost::asio::ip::address& addr) - { - if (!addr.is_v6 ()) return false; - 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) - 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) - == ERROR_BUFFER_OVERFLOW) - { - FREE(pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); - } - - DWORD dwRetVal = GetAdaptersAddresses( - AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen - ); - - if (dwRetVal != NO_ERROR) - { - LogPrint(eLogError, "NetIface: GetYggdrasilAddress(): enclosed GetAdaptersAddresses() call has failed"); - FREE(pAddresses); - return boost::asio::ip::address_v6 (); - } - - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - pUnicast = pCurrAddresses->FirstUnicastAddress; - - while (pUnicast != nullptr) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; - if (IsYggdrasilAddress(localInterfaceAddress->sin6_addr.u.Byte)) { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), &localInterfaceAddress->sin6_addr.u.Byte, 16); - FREE(pAddresses); - return boost::asio::ip::address_v6 (bytes); - } - pUnicast = pUnicast->Next; - } - pCurrAddresses = pCurrAddresses->Next; - } - LogPrint(eLogWarning, "NetIface: Interface with Yggdrasil network address not found"); - FREE(pAddresses); - return boost::asio::ip::address_v6 (); -#else - ifaddrs *addrs; - try - { - if (!getifaddrs(&addrs)) - { - for (auto cur = addrs; cur; cur = cur->ifa_next) - { - if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET6) - { - sockaddr_in6* sa = (sockaddr_in6*)cur->ifa_addr; - if (IsYggdrasilAddress(sa->sin6_addr.s6_addr)) - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), &sa->sin6_addr, 16); - freeifaddrs(addrs); - return boost::asio::ip::address_v6 (bytes); - } - } - } - } - } - catch (std::exception& ex) - { - 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); - return boost::asio::ip::address_v6 (); -#endif - } - - bool IsLocalAddress (const boost::asio::ip::address& addr) - { - auto mtu = // TODO: implement better -#ifdef _WIN32 - GetMTUWindows(addr, 0); -#else - GetMTUUnix(addr, 0); -#endif - return mtu > 0; - } - - bool IsInReservedRange (const boost::asio::ip::address& host) - { - // https://en.wikipedia.org/wiki/Reserved_IP_addresses - if (host.is_unspecified ()) return false; - if (host.is_v4()) - { - static const std::array, 14> reservedIPv4Ranges - { - address_pair_v4("0.0.0.0", "0.255.255.255"), - address_pair_v4("10.0.0.0", "10.255.255.255"), - address_pair_v4("100.64.0.0", "100.127.255.255"), - address_pair_v4("127.0.0.0", "127.255.255.255"), - address_pair_v4("169.254.0.0", "169.254.255.255"), - address_pair_v4("172.16.0.0", "172.31.255.255"), - address_pair_v4("192.0.0.0", "192.0.0.255"), - address_pair_v4("192.0.2.0", "192.0.2.255"), - address_pair_v4("192.88.99.0", "192.88.99.255"), - address_pair_v4("192.168.0.0", "192.168.255.255"), - address_pair_v4("198.18.0.0", "192.19.255.255"), - address_pair_v4("198.51.100.0", "198.51.100.255"), - address_pair_v4("203.0.113.0", "203.0.113.255"), - 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) { - if (ipv4_address >= it.first && ipv4_address <= it.second) - return true; - } - } - if (host.is_v6()) - { - static const std::array, 7> reservedIPv6Ranges - { - address_pair_v6("64:ff9b::", "64:ff9b:ffff:ffff:ffff:ffff:ffff:ffff"), // NAT64 - address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("::", "::"), - address_pair_v6("::1", "::1") - }; - - boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); - for (const auto& it : reservedIPv6Ranges) { - if (ipv6_address >= it.first && ipv6_address <= it.second) - return true; - } - if (IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? - return true; - } - return false; - } -} // net +} } // util } // i2p diff --git a/libi2pd/util.h b/libi2pd/util.h index 7bd35e67..6da20ad6 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef UTIL_H #define UTIL_H @@ -13,7 +5,6 @@ #include #include #include -#include #include #include @@ -51,13 +42,12 @@ namespace util MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { - CleanUp (); - } - - void CleanUp () - { - CleanUp (m_Head); - m_Head = nullptr; + while (m_Head) + { + auto tmp = m_Head; + m_Head = static_cast(*(void * *)m_Head); // next + delete tmp; + } } template @@ -94,25 +84,13 @@ namespace util std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } - protected: - - void CleanUp (T * head) - { - while (head) - { - auto tmp = head; - head = static_cast(*(void * *)head); // next - ::operator delete ((void *)tmp); - } - } - protected: T * m_Head; }; template - class MemoryPoolMt: private MemoryPool + class MemoryPoolMt: public MemoryPool { public: @@ -131,14 +109,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) { @@ -147,94 +117,15 @@ namespace util this->Release (it); } - template - std::shared_ptr AcquireSharedMt (TArgs&&... args) - { - return std::shared_ptr(AcquireMt (std::forward(args)...), - std::bind::*)(T *)> (&MemoryPoolMt::ReleaseMt, this, std::placeholders::_1)); - } - - void CleanUpMt () - { - T * head; - { - std::lock_guard l(m_Mutex); - head = this->m_Head; - this->m_Head = nullptr; - } - if (head) this->CleanUp (head); - } - private: std::mutex m_Mutex; }; - class RunnableService - { - protected: - - RunnableService (const std::string& name): m_Name (name), m_IsRunning (false) {} - virtual ~RunnableService () {} - - auto& GetIOService () { return m_Service; } - bool IsRunning () const { return m_IsRunning; }; - - void StartIOService (); - void StopIOService (); - - void SetName (std::string_view name); - - private: - - void Run (); - - private: - - std::string m_Name; - volatile bool m_IsRunning; - std::unique_ptr m_Thread; - boost::asio::io_context m_Service; - }; - - class RunnableServiceWithWork: public RunnableService - { - protected: - - RunnableServiceWithWork (const std::string& name): - RunnableService (name), m_Work (GetIOService ().get_executor ()) {} - - private: - - boost::asio::executor_work_guard m_Work; - }; - - void SetThreadName (const char *name); - - template - class SaveStateHelper - { - public: - - SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; - ~SaveStateHelper () { m_Original = m_Copy; }; - - private: - - T& m_Original; - T m_Copy; - }; - 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; + const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); } } } diff --git a/libi2pd/version.h b/libi2pd/version.h index 1e63ae08..4fdb5f27 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,41 +1,28 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef _VERSION_H_ #define _VERSION_H_ #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 56 +#define I2PD_VERSION_MINOR 25 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 -#ifdef GITVER - #define I2PD_VERSION XSTRINGIZE(GITVER) -#else - #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) -#endif - +#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION +#ifdef MESHNET +#define I2PD_NET_ID 3 +#else #define I2PD_NET_ID 2 +#endif #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 65 +#define I2P_VERSION_MICRO 40 #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) #endif diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 8333e87d..75d95da1 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,23 +1,15 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include -#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 +19,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 @@ -42,40 +26,35 @@ namespace client // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { - public: - - AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") - { - i2p::config::GetOption("persist.addressbook", m_IsPersist); - 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; - - bool Init () override; - int Load (Addresses& addresses) override; - int LoadLocal (Addresses& addresses) override; - int Save (const Addresses& addresses) override; - - void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified) override; - bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) override; - void ResetEtags () override; - private: - - int LoadFromFile (const std::string& filename, Addresses& addresses); // returns -1 if can't open file, otherwise number of records - - private: - i2p::fs::HashedStorage storage; std::string etagsPath, indexPath, localPath; + + public: + AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") + { + i2p::config::GetOption("persist.addressbook", m_IsPersist); + } + 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 (); + 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 (); + + private: + + int LoadFromFile (const std::string& filename, std::map >& addresses); // returns -1 if can't open file, otherwise number of records + + private: + 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,92 +75,57 @@ 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) + if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); - return nullptr; + return nullptr; } 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; + 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 +151,7 @@ namespace client return num; } - int AddressBookFilesystemStorage::Load (Addresses& addresses) + int AddressBookFilesystemStorage::Load (std::map >& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) @@ -215,13 +159,13 @@ namespace client LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } - LogPrint(eLogInfo, "Addressbook: Using index file ", indexPath); + LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; } - int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) + int AddressBookFilesystemStorage::LoadLocal (std::map >& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; @@ -229,61 +173,32 @@ namespace client return num; } - int AddressBookFilesystemStorage::Save (const Addresses& addresses) + int AddressBookFilesystemStorage::Save (const std::map >& addresses) { - if (addresses.empty()) - { - LogPrint(eLogWarning, "Addressbook: Not saving empty addressbook"); + if (addresses.empty()) { + LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); return 0; } int num = 0; - { - // save index file - std::ofstream f (indexPath, std::ofstream::out); // in text mode - if (f.is_open ()) - { - for (const auto& it: addresses) - { - if (it.second->IsValid ()) - { - f << it.first << ","; - if (it.second->IsIdentHash ()) - f << it.second->identHash.ToBase32 (); - else - f << it.second->blindedPublicKey->ToB33 (); - f << std::endl; - num++; - } - else - LogPrint (eLogWarning, "Addressbook: Invalid address ", it.first); - } - LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); - } - else - LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); - } - if (!m_HostsFile.empty ()) - { - // dump full hosts.txt - std::ofstream f (m_HostsFile, std::ofstream::out); // in text mode - if (f.is_open ()) - { - for (const auto& it: addresses) - { - std::shared_ptr addr; - if (it.second->IsIdentHash ()) - { - addr = GetAddress (it.second->identHash); - if (addr) - f << it.first << "=" << addr->ToBase64 () << std::endl; - } - } - } - else - LogPrint (eLogWarning, "Addressbook: Can't open ", m_HostsFile); + std::ofstream f (indexPath, std::ofstream::out); // in text mode + + if (!f.is_open ()) { + LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); + return 0; } + for (const auto& it: addresses) + { + f << it.first << ","; + if (it.second->IsIdentHash ()) + f << it.second->identHash.ToBase32 (); + else + f << it.second->blindedPublicKey->ToB33 (); + f << std::endl; + num++; + } + LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); return num; } @@ -311,55 +226,39 @@ namespace client void AddressBookFilesystemStorage::ResetEtags () { - LogPrint (eLogError, "Addressbook: Resetting eTags"); - for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) + LogPrint (eLogError, "Addressbook: resetting eTags"); + 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): - addressType (eAddressInvalid) + Address::Address (const std::string& b32) { if (b32.length () <= B33_ADDRESS_THRESHOLD) { - if (identHash.FromBase32 (b32) > 0) - addressType = eAddressIndentHash; + addressType = eAddressIndentHash; + identHash.FromBase32 (b32); } else { + addressType = eAddressBlindedPublicKey; blindedPublicKey = std::make_shared(b32); - if (blindedPublicKey->IsValid ()) - addressType = eAddressBlindedPublicKey; } - } + } Address::Address (const i2p::data::IdentHash& hash) { addressType = eAddressIndentHash; - identHash = hash; + 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 +269,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 +288,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,27 +316,22 @@ 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) + return std::make_shared(address.substr (0, pos)); + else { - 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 - { - 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; if (!dest.FromBase64 (address)) @@ -463,7 +339,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,67 +347,43 @@ 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"); if (pos != std::string::npos) { m_Addresses[address] = std::make_shared
(jump.substr (0, pos)); - LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", jump); - } + LogPrint (eLogInfo, "Addressbook: added ", address," -> ", jump); + } else - { - // assume base64 + { + // assume base64 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 ())); + LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); } else - LogPrint (eLogError, "Addressbook: Malformed address ", jump); + LogPrint (eLogError, "Addressbook: malformed address ", jump); } } 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,36 +419,16 @@ 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) - 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 - { - 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 - { - LogPrint (eLogError, "Addressbook: Malformed domain: ", name); - continue; - } + size_t pos = s.find('#'); + if (pos != std::string::npos) + addr = addr.substr(pos); // remove comments auto ident = std::make_shared (); - if (!ident->FromBase64(addr)) - { - LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); + if (!ident->FromBase64(addr)) { + LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); incomplete = f.eof (); continue; } @@ -604,24 +436,20 @@ namespace client auto it = m_Addresses.find (name); if (it != m_Addresses.end ()) // already exists ? { - if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash () && // address changed? - ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA + if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash ()) // address changed? { it->second->identHash = ident->GetIdentHash (); - if (m_Storage) - { - m_Storage->AddAddress (ident); - m_Storage->RemoveAddress (it->second->identHash); - } - LogPrint (eLogInfo, "Addressbook: Updated host: ", name); + m_Storage->AddAddress (ident); + LogPrint (eLogInfo, "Addressbook: updated host: ", name); } } else { - m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); - if (m_Storage) m_Storage->AddAddress (ident); + //m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); + m_Addresses[name] = std::make_shared
(ident->GetIdentHash ()); // for gcc 4.7 + m_Storage->AddAddress (ident); if (is_update) - LogPrint (eLogInfo, "Addressbook: Added new host: ", name); + LogPrint (eLogInfo, "Addressbook: added new host: ", name); } } else @@ -631,7 +459,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; } @@ -647,34 +475,33 @@ namespace client while (!f.eof ()) { getline(f, s); - if (s.empty () || s[0] == '#') continue; // skip empty line or comment + if (!s.length()) continue; // skip empty line m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); } - else - { - LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config"); - // using config file items - std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); - std::vector subsList; - boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); + else if (!i2p::config::IsDefault("addressbook.subscriptions")) + { + // 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)); - LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); - } + for (size_t i = 0; i < subsList.size (); i++) + { + m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); + } + LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); + } } else - LogPrint (eLogError, "Addressbook: Subscriptions already loaded"); + LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } void AddressBook::LoadLocal () { - if (!m_Storage) return; - AddressBookStorage::Addresses localAddresses; + std::map> localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { @@ -683,7 +510,7 @@ namespace client if (dot != std::string::npos) { auto domain = it.first.substr (dot + 1); - auto it1 = m_Addresses.find (domain); // find domain in our addressbook + auto it1 = m_Addresses.find (domain); // find domain in our addressbook if (it1 != m_Addresses.end () && it1->second->IsIdentHash ()) { auto dest = context.FindLocalDestination (it1->second->identHash); @@ -717,6 +544,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 +575,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 () @@ -768,33 +596,29 @@ namespace client { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) { - LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); + 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) { // download it from default subscription - LogPrint (eLogInfo, "Addressbook: Trying to download it from default subscription."); - std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); + LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); + 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 +655,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 +685,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,38 +722,13 @@ 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) { } void AddressBookSubscription::CheckUpdates () { - i2p::util::SetThreadName("Addressbook"); - bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } @@ -939,70 +738,91 @@ namespace client i2p::http::URL url; // must be run in separate thread LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); - if (!url.parse(m_Link)) + if (!url.parse(m_Link)) { - LogPrint(eLogError, "Addressbook: Failed to parse url: ", m_Link); + LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); return false; } auto addr = m_Book.GetAddress (url.host); - if (!addr || !addr->IsIdentHash ()) + if (!addr || !addr->IsIdentHash ()) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return false; } 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); + 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"); - req.AddHeader("Accept-Encoding", "gzip"); req.AddHeader("X-Accept-Encoding", "x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0"); req.AddHeader("Connection", "close"); if (!m_Etag.empty()) req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); - // convert url to relative - url.schema = ""; - url.host = ""; - req.uri = url.to_string(); - req.version = "HTTP/1.1"; + /* convert url to relative */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); + 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) + 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) { - response.append ((char *)recv_buf, received); - if (!stream->IsOpen ()) end = true; - } - else if (!stream->IsOpen ()) - end = true; - else - { - LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); + LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; } @@ -1010,46 +830,46 @@ 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) { - LogPrint(eLogError, "Addressbook: Can't parse http response from ", dest_host); + LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); return false; } if (res_head_len == 0) { - LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); + 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) { - LogPrint (eLogInfo, "Addressbook: No updates from ", dest_host, ", code 304"); + LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); return false; } if (res.code != 200) { - LogPrint (eLogWarning, "Adressbook: Can't get updates from ", dest_host, ", response code ", res.code); + LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); return false; } int len = res.content_length(); if (response.empty()) { - LogPrint(eLogError, "Addressbook: Empty response from ", dest_host, ", expected ", len, " bytes"); + LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); return false; } if (!res.is_gzipped () && len > 0 && len != (int) response.length()) { - LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); + 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"); + it = res.headers.find("If-Modified-Since"); if (it != res.headers.end()) m_LastModified = it->second; if (res.is_chunked()) { @@ -1057,20 +877,20 @@ namespace client i2p::http::MergeChunkedResponse (in, out); response = out.str(); } - if (res.is_gzipped()) + else if (res.is_gzipped()) { std::stringstream out; i2p::data::GzipInflator inflator; inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); if (out.fail()) { - LogPrint(eLogError, "Addressbook: Can't gunzip http response"); + LogPrint(eLogError, "Addressbook: can't gunzip http response"); return false; } response = out.str(); } std::stringstream ss(response); - LogPrint (eLogInfo, "Addressbook: Got update from ", dest_host); + LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); m_Book.LoadHostsFromStream (ss, true); return true; } @@ -1095,7 +915,7 @@ namespace client { auto datagram = m_LocalDestination->GetDatagramDestination (); if (datagram) - datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); + datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); } } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 8b32aa93..47ad9993 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,22 +1,12 @@ -/* -* 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 ADDRESS_BOOK_H__ #define ADDRESS_BOOK_H__ #include #include -#include #include #include #include #include -#include #include #include #include "Base.h" @@ -34,9 +24,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; @@ -45,14 +33,13 @@ namespace client struct Address { - enum { eAddressIndentHash, eAddressBlindedPublicKey, eAddressInvalid } addressType; + enum { eAddressIndentHash, eAddressBlindedPublicKey } addressType; i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; - Address (std::string_view b32); - Address (const i2p::data::IdentHash& hash); + Address (const std::string& b32); + Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; - bool IsValid () const { return addressType != eAddressInvalid; }; }; inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } @@ -61,18 +48,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 +68,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 +88,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 +104,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: @@ -176,9 +153,11 @@ namespace client private: std::shared_ptr m_LocalDestination; - std::map m_LocalAddresses; + std::map m_LocalAddresses; }; } } #endif + + diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index 8d94e94b..94d74726 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include "Log.h" #include "ClientContext.h" @@ -16,24 +8,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) { @@ -76,17 +50,17 @@ namespace client void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( - receiver->buffer + receiver->bufferOffset, - BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), + receiver->buffer + receiver->bufferOffset, + BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, - std::placeholders::_1, std::placeholders::_2, receiver)); + std::placeholders::_1, std::placeholders::_2, receiver)); } void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver) { if (ecode) - LogPrint (eLogError, "BOB: Inbound tunnel read error: ", ecode.message ()); + LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ()); else { receiver->bufferOffset += bytes_transferred; @@ -101,7 +75,7 @@ namespace client auto addr = context.GetAddressBook ().GetAddress (receiver->buffer); if (!addr) { - LogPrint (eLogError, "BOB: Address ", receiver->buffer, " not found"); + LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); return; } if (addr->IsIdentHash ()) @@ -124,7 +98,7 @@ namespace client if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else - LogPrint (eLogError, "BOB: Missing inbound address"); + LogPrint (eLogError, "BOB: missing inbound address"); } } } @@ -145,9 +119,9 @@ namespace client connection->I2PConnect (receiver->data, receiver->dataLen); } - BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, + BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, 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 (address), port), m_IsQuiet (quiet) { } @@ -174,19 +148,15 @@ 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 (); } } - 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): + BOBDestination::BOBDestination (std::shared_ptr localDestination): 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_OutboundTunnel (nullptr), m_InboundTunnel (nullptr) { } @@ -201,7 +171,6 @@ namespace client { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); - m_IsRunning = true; } void BOBDestination::Stop () @@ -212,7 +181,6 @@ namespace client void BOBDestination::StopTunnels () { - m_IsRunning = false; if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); @@ -227,18 +195,15 @@ namespace client } } - void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) + void BOBDestination::CreateInboundTunnel (int port, const std::string& address) { if (!m_InboundTunnel) { - // update inport and inhost (user can stop tunnel and change) - m_InPort = port; - m_InHost = inhost; boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port); - if (!inhost.empty ()) + if (!address.empty ()) { boost::system::error_code ec; - auto addr = boost::asio::ip::make_address (inhost, ec); + auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) ep.address (addr); else @@ -248,21 +213,15 @@ namespace client } } - void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet) + void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet) { if (!m_OutboundTunnel) - { - // update outport and outhost (user can stop tunnel and change) - m_OutPort = port; - m_OutHost = outhost; - m_OutboundTunnel = new BOBI2POutboundTunnel (outhost, port, m_LocalDestination, quiet); - } + m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet); } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), - m_ReceiveBuffer(BOB_COMMAND_BUFFER_SIZE + 1), m_SendBuffer(BOB_COMMAND_BUFFER_SIZE + 1), - m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), + m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } @@ -279,48 +238,65 @@ namespace client void BOBCommandSession::Receive () { - boost::asio::async_read_until(m_Socket, m_ReceiveBuffer, '\n', - std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset), + std::bind(&BOBCommandSession::HandleReceived, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } - void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) + void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if(ecode) + if (ecode) { - LogPrint (eLogError, "BOB: Command channel read error: ", ecode.message()); + LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - std::string line; - - std::istream is(&m_ReceiveBuffer); - std::getline(is, line); - - std::string command, operand; - std::istringstream iss(line); - iss >> command >> operand; - - // process command - auto& handlers = m_Owner.GetCommandHandlers(); - auto it = handlers.find(command); - if(it != handlers.end()) + size_t size = m_ReceiveBufferOffset + bytes_transferred; + m_ReceiveBuffer[size] = 0; + char * eol = strchr (m_ReceiveBuffer, '\n'); + if (eol) { - (this->*(it->second))(operand.c_str(), operand.length()); + *eol = 0; + char * operand = strchr (m_ReceiveBuffer, ' '); + if (operand) + { + *operand = 0; + operand++; + } + else + operand = eol; + // process command + auto& handlers = m_Owner.GetCommandHandlers (); + auto it = handlers.find (m_ReceiveBuffer); + if (it != handlers.end ()) + (this->*(it->second))(operand, eol - operand); + else + { + LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer); + SendReplyError ("unknown command"); + } + + m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1; + memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset); } else { - LogPrint (eLogError, "BOB: Unknown command ", command.c_str()); - SendReplyError ("unknown command"); + if (size < BOB_COMMAND_BUFFER_SIZE) + m_ReceiveBufferOffset = size; + else + { + LogPrint (eLogError, "BOB: Malformed input of the command channel"); + Terminate (); + } } } } - void BOBCommandSession::Send () + void BOBCommandSession::Send (size_t len) { - boost::asio::async_write (m_Socket, m_SendBuffer, + boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len), boost::asio::transfer_all (), std::bind(&BOBCommandSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -329,8 +305,8 @@ namespace client void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) - { - LogPrint (eLogError, "BOB: Command channel send error: ", ecode.message ()); + { + LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -345,66 +321,39 @@ namespace client void BOBCommandSession::SendReplyOK (const char * msg) { - std::ostream os(&m_SendBuffer); - os << "OK"; - if(msg) - { - os << " " << msg; - } - os << std::endl; - Send (); +#ifdef _MSC_VER + size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); +#else + size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); +#endif + Send (len); } void BOBCommandSession::SendReplyError (const char * msg) { - std::ostream os(&m_SendBuffer); - os << "ERROR " << msg << std::endl; - Send (); +#ifdef _MSC_VER + size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); +#else + size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); +#endif + Send (len); } void BOBCommandSession::SendVersion () { - std::ostream os(&m_SendBuffer); - os << "BOB 00.00.10" << std::endl; - SendReplyOK(); + size_t len = strlen (BOB_VERSION); + memcpy (m_SendBuffer, BOB_VERSION, len); + Send (len); } - void BOBCommandSession::SendRaw (const char * data) + void BOBCommandSession::SendData (const char * nickname) { - std::ostream os(&m_SendBuffer); - os << data << std::endl; - } - - void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr dest, std::string &out) - { - // helper lambdas - const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost - const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport - const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; - const auto destReady = [](const BOBDestination * const dest) { return dest && dest->IsRunning(); }; - const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str - - // tunnel info - const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); - const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet(); - const std::string inhost = issetStr(currentTunnel ? m_InHost : dest->GetInHost()); - 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 stopping = false; - - // build line - std::stringstream ss; - ss << "DATA " - << "NICKNAME: " << nickname << " " << "STARTING: " << bool_str(starting) << " " - << "RUNNING: " << bool_str(running) << " " << "STOPPING: " << bool_str(stopping) << " " - << "KEYS: " << bool_str(keys) << " " << "QUIET: " << bool_str(quiet) << " " - << "INPORT: " << inport << " " << "INHOST: " << inhost << " " - << "OUTPORT: " << outport << " " << "OUTHOST: " << outhost; - out = ss.str(); +#ifdef _MSC_VER + size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); +#else + size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); +#endif + Send (len); } void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) @@ -428,50 +377,15 @@ namespace client SendReplyError ("tunnel is active"); return; } - if (!m_Keys.GetPublic ()) // keys are set ? - { - SendReplyError("Keys must be set."); - return; - } - if (m_InPort == 0 - && m_OutHost.empty() && m_OutPort == 0) - { - SendReplyError("(inhost):inport or outhost:outport must be set."); - return; - } - if(!m_InHost.empty()) - { - // TODO: FIXME: temporary validation, until hostname support is added - boost::system::error_code ec; - boost::asio::ip::make_address(m_InHost, ec); - if (ec) - { - SendReplyError("inhost must be a valid IPv4 address."); - return; - } - } - if(!m_OutHost.empty()) - { - // TODO: FIXME: temporary validation, until hostname support is added - boost::system::error_code ec; - boost::asio::ip::make_address(m_OutHost, ec); - if (ec) - { - SendReplyError("outhost must be a IPv4 address."); - return; - } - } - if (!m_CurrentDestination) { - m_CurrentDestination = std::make_shared (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command - m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); + m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) - m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); - if (m_OutPort && !m_OutHost.empty ()) - m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); + m_CurrentDestination->CreateInboundTunnel (m_InPort, m_Address); + if (m_OutPort && !m_Address.empty ()) + m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); SendReplyOK ("Tunnel starting"); m_IsActive = true; @@ -499,43 +413,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; - std::string msg ("Nickname set to "); - msg += m_Nickname; - SendReplyOK (msg.c_str ()); - } - 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); - if (m_CurrentDestination) - { - m_Keys = m_CurrentDestination->GetKeys (); - m_IsActive = m_CurrentDestination->IsRunning (); - 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"); + 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"); @@ -560,19 +457,19 @@ namespace client } catch (std::invalid_argument& ex) { - LogPrint (eLogWarning, "BOB: Error on newkeys: ", ex.what ()); + LogPrint (eLogWarning, "BOB: newkeys ", ex.what ()); } } - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); + 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)) + if (m_Keys.FromBase64 (operand)) SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); @@ -599,61 +496,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_Address = 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_Address = 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) @@ -676,68 +547,37 @@ 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"); + 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; } - auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? - m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); - if (!localDestination) - { - SendReplyError ("No local destination"); - return; - } - if (addr->IsIdentHash ()) - { - // we might have leaseset already - auto leaseSet = localDestination->FindLeaseSet (addr->identHash); - if (leaseSet) - { - SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); - return; - } - } - // trying to request - auto s = shared_from_this (); - auto requstCallback = [s](std::shared_ptr ls) + } + // 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 - localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); - } + if (addr->IsIdentHash ()) + localDestination->RequestDestination (addr->identHash, requstCallback); else - SendReplyError ("empty lookup address"); - } - - 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) - { - SendReplyError ("Address Not found"); - return; - } - auto ls = i2p::data::netdb.FindLeaseSet (addr->identHash); - if (ls) - SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); - else - SendReplyError ("Local LeaseSet Not found"); - } - else - SendReplyError ("empty lookup address"); + localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); } void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) @@ -751,23 +591,9 @@ namespace client void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); - std::string statusLine; - bool sentCurrent = false; const auto& destinations = m_Owner.GetDestinations (); for (const auto& it: destinations) - { - BuildStatusLine(false, it.second, statusLine); - SendRaw(statusLine.c_str()); - if(m_Nickname.compare(it.second->GetNickname()) == 0) - sentCurrent = true; - } - if(!sentCurrent && !m_Nickname.empty()) - { - // add the current tunnel to the list. - // this is for the incomplete tunnel which has not been started yet. - BuildStatusLine(true, m_CurrentDestination, statusLine); - SendRaw(statusLine.c_str()); - } + SendData (it.first.c_str ()); SendReplyOK ("Listing done"); } @@ -783,7 +609,7 @@ namespace client msg += operand; *(const_cast(value)) = '='; msg += " set to "; - msg += value + 1; + msg += value; SendReplyOK (msg.c_str ()); } else @@ -793,60 +619,39 @@ namespace client void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); - const std::string name = operand; - std::string statusLine; - - // always prefer destination - auto dest = m_Owner.FindDestination(name); - if(dest) + if (m_Nickname == operand) { - // tunnel destination exists - BuildStatusLine(false, dest, statusLine); - SendReplyOK(statusLine.c_str()); - } - else - { - if(m_Nickname == name && !name.empty()) + std::stringstream s; + s << "DATA"; s << " NICKNAME: "; s << m_Nickname; + if (m_CurrentDestination) { - // tunnel is incomplete / has not been started yet - BuildStatusLine(true, nullptr, statusLine); - SendReplyOK(statusLine.c_str()); + if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) + s << " STARTING: false RUNNING: true STOPPING: false"; + else + s << " STARTING: true RUNNING: false STOPPING: false"; } else + s << " STARTING: false RUNNING: false STOPPING: false"; + s << " KEYS: true"; s << " QUIET: "; s << (m_IsQuiet ? "true":"false"); + if (m_InPort) { - SendReplyError("no nickname has been set"); + s << " INPORT: " << m_InPort; + s << " INHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); } - } - } - void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) - { - auto helpStrings = m_Owner.GetHelpStrings(); - if(!*operand) - { - std::stringstream ss; - ss << "COMMANDS:"; - for (auto const& x : helpStrings) + if (m_OutPort) { - ss << " " << x.first; + s << " OUTPORT: " << m_OutPort; + s << " OUTHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); } - const std::string &str = ss.str(); - SendReplyOK(str.c_str()); + SendReplyOK (s.str().c_str()); } else - { - auto it = helpStrings.find(operand); - if (it != helpStrings.end ()) - { - SendReplyOK(it->second.c_str()); - return; - } - SendReplyError("No such command"); - } + SendReplyError ("no nickname has been set"); } - BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port): - RunnableService ("BOB"), - m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)) + BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; @@ -865,59 +670,59 @@ namespace client m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; 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_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; - // command -> help string - m_HelpStrings[BOB_COMMAND_ZAP] = BOB_HELP_ZAP; - m_HelpStrings[BOB_COMMAND_QUIT] = BOB_HELP_QUIT; - m_HelpStrings[BOB_COMMAND_START] = BOB_HELP_START; - m_HelpStrings[BOB_COMMAND_STOP] = BOB_HELP_STOP; - m_HelpStrings[BOB_COMMAND_SETNICK] = BOB_HELP_SETNICK; - m_HelpStrings[BOB_COMMAND_GETNICK] = BOB_HELP_GETNICK; - m_HelpStrings[BOB_COMMAND_NEWKEYS] = BOB_HELP_NEWKEYS; - m_HelpStrings[BOB_COMMAND_GETKEYS] = BOB_HELP_GETKEYS; - m_HelpStrings[BOB_COMMAND_SETKEYS] = BOB_HELP_SETKEYS; - m_HelpStrings[BOB_COMMAND_GETDEST] = BOB_HELP_GETDEST; - m_HelpStrings[BOB_COMMAND_OUTHOST] = BOB_HELP_OUTHOST; - m_HelpStrings[BOB_COMMAND_OUTPORT] = BOB_HELP_OUTPORT; - m_HelpStrings[BOB_COMMAND_INHOST] = BOB_HELP_INHOST; - m_HelpStrings[BOB_COMMAND_INPORT] = BOB_HELP_INPORT; - m_HelpStrings[BOB_COMMAND_QUIET] = BOB_HELP_QUIET; - m_HelpStrings[BOB_COMMAND_LOOKUP] = BOB_HELP_LOOKUP; - m_HelpStrings[BOB_COMMAND_CLEAR] = BOB_HELP_CLEAR; - m_HelpStrings[BOB_COMMAND_LIST] = BOB_HELP_LIST; - m_HelpStrings[BOB_COMMAND_OPTION] = BOB_HELP_OPTION; - m_HelpStrings[BOB_COMMAND_STATUS] = BOB_HELP_STATUS; - m_HelpStrings[BOB_COMMAND_HELP] = BOB_HELP_HELP; } BOBCommandChannel::~BOBCommandChannel () { - if (IsRunning ()) - Stop (); + Stop (); + for (const auto& it: m_Destinations) + delete it.second; } void BOBCommandChannel::Start () { Accept (); - StartIOService (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this)); } void BOBCommandChannel::Stop () { + m_IsRunning = false; for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); - StopIOService (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } } - void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr dest) + void BOBCommandChannel::Run () { - m_Destinations.emplace (name, dest); + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "BOB: runtime exception: ", ex.what ()); + } + } + } + + void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) + { + m_Destinations[name] = dest; } void BOBCommandChannel::DeleteDestination (const std::string& name) @@ -926,11 +731,12 @@ 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 ()) @@ -956,7 +762,8 @@ namespace client session->SendVersion (); } else - LogPrint (eLogError, "BOB: Accept error: ", ecode.message ()); + LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); } } } + diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index f5aefd0a..a2a24164 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,11 +1,3 @@ -/* -* 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 BOB_H__ #define BOB_H__ @@ -15,7 +7,6 @@ #include #include #include -#include "util.h" #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" @@ -42,52 +33,16 @@ namespace client const char BOB_COMMAND_INPORT[] = "inport"; const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; - const char BOB_COMMAND_LOOKUP_LOCAL[] = "lookuplocal"; 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_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."; - const char BOB_HELP_STOP[] = "stop - Stops the current nicknamed tunnel."; - const char BOB_HELP_SETNICK[] = "setnick - Creates a new nickname."; - const char BOB_HELP_GETNICK[] = "getnick - Sets the nickname from the database."; - const char BOB_HELP_NEWKEYS[] = "newkeys - Generate a new keypair for the current nickname."; - const char BOB_HELP_GETKEYS[] = "getkeys - Return the keypair for the current nickname."; - const char BOB_HELP_SETKEYS[] = "setkeys - Sets the keypair for the current nickname."; - const char BOB_HELP_GETDEST[] = "getdest - Return the destination for the current nickname."; - const char BOB_HELP_OUTHOST[] = "outhost - Set the outhound hostname or IP."; - const char BOB_HELP_OUTPORT[] = "outport - Set the outbound port that nickname contacts."; - const char BOB_HELP_INHOST[] = "inhost - Set the inbound hostname or IP."; - const char BOB_HELP_INPORT[] = "inport - Set the inbound port number nickname listens on."; - const char BOB_HELP_QUIET[] = "quiet - Whether to send the incoming destination."; - const char BOB_HELP_LOOKUP[] = "lookup - Look up an I2P hostname."; - const char BOB_HELP_CLEAR[] = "clear - Clear the current nickname out of the list."; - const char BOB_HELP_LIST[] = "list - List all tunnels."; - 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_VERSION[] = "BOB 00.00.10\nOK\n"; + const char BOB_REPLY_OK[] = "OK %s\n"; + const char BOB_REPLY_ERROR[] = "ERROR %s\n"; + const char BOB_DATA[] = "NICKNAME %s\n"; - 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: @@ -101,15 +56,15 @@ namespace client class BOBI2PInboundTunnel: public BOBI2PTunnel { - struct AddressReceiver - { - std::shared_ptr socket; - char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address - uint8_t * data; // pointer to buffer - size_t dataLen, bufferOffset; + struct AddressReceiver + { + std::shared_ptr socket; + char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address + uint8_t * data; // pointer to buffer + size_t dataLen, bufferOffset; - AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; - }; + AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; + }; public: @@ -141,7 +96,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -164,23 +119,14 @@ namespace client { public: - 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); + BOBDestination (std::shared_ptr localDestination); ~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); - 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; } - bool GetQuiet() const { return m_Quiet; } - bool IsRunning() const { return m_IsRunning; } + void CreateInboundTunnel (int port, const std::string& address); + void CreateOutboundTunnel (const std::string& address, int port, bool quiet); const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -189,12 +135,6 @@ namespace client std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; - - std::string m_Nickname; - std::string m_InHost, m_OutHost; - uint16_t m_InPort, m_OutPort; - bool m_Quiet; - bool m_IsRunning; }; class BOBCommandChannel; @@ -226,75 +166,74 @@ namespace client void InportCommandHandler (const char * operand, size_t len); 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 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); private: void Receive (); - void HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void Send (); + void Send (size_t len); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void SendReplyOK (const char * msg = nullptr); + void SendReplyOK (const char * msg); void SendReplyError (const char * msg); - void SendRaw (const char * data); - - void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); + void SendData (const char * nickname); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; - boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; + char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1]; + size_t m_ReceiveBufferOffset; bool m_IsOpen, m_IsQuiet, m_IsActive; - std::string m_Nickname, m_InHost, m_OutHost; - uint16_t m_InPort, m_OutPort; + std::string m_Nickname, m_Address; + int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; - std::shared_ptr m_CurrentDestination; + BOBDestination * m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); - class BOBCommandChannel: private i2p::util::RunnableService + class BOBCommandChannel { 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 m_Service; }; + void AddDestination (const std::string& name, BOBDestination * dest); void DeleteDestination (const std::string& name); - std::shared_ptr FindDestination (const std::string& name); + BOBDestination * FindDestination (const std::string& name); private: + void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session); private: + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; - std::map > m_Destinations; + std::map m_Destinations; std::map m_CommandHandlers; - std::map m_HelpStrings; public: const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; }; - const decltype(m_HelpStrings)& GetHelpStrings () const { return m_HelpStrings; }; const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; }; } } #endif + diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index d26e33ab..3292419a 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -16,8 +8,8 @@ #include "Identity.h" #include "util.h" #include "ClientContext.h" -#include "HTTPProxy.h" #include "SOCKS.h" +#include "WebSocks.h" #include "MatchedDestination.h" namespace i2p @@ -61,22 +53,15 @@ namespace client // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); - if (sam) - { + 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); - bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP, "]"); - try - { - m_SamBridge = new SAMBridge (samAddr, samPortTCP, samPortUDP, 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 ()); + uint16_t samPort; i2p::config::GetOption("sam.port", samPort); + LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); + try { + m_SamBridge = new SAMBridge (samAddr, samPort); + m_SamBridge->Start (); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); } } @@ -85,16 +70,12 @@ namespace client if (bob) { std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); - LogPrint(eLogInfo, "Clients: Starting BOB command channel at ", bobAddr, ":", bobPort); - try - { - m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); - m_BOBCommandChannel->Start (); - } - catch (std::exception& e) - { - LogPrint(eLogCritical, "Clients: Exception in BOB bridge: ", e.what()); - ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); + LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); + try { + m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); + m_BOBCommandChannel->Start (); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); } } @@ -104,17 +85,15 @@ namespace client { std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); - bool singleThread; i2p::config::GetOption("i2cp.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: Starting I2CP at ", i2cpAddr, ":", i2cpPort); + LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); try { - m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort, singleThread); + m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); m_I2CPServer->Start (); } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in I2CP: ", e.what()); - ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); + LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); } } @@ -132,7 +111,7 @@ namespace client { if (m_HttpProxy) { - LogPrint(eLogInfo, "Clients: Stopping HTTP Proxy"); + LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; @@ -140,7 +119,7 @@ namespace client if (m_SocksProxy) { - LogPrint(eLogInfo, "Clients: Stopping SOCKS Proxy"); + LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; @@ -148,21 +127,21 @@ namespace client for (auto& it: m_ClientTunnels) { - LogPrint(eLogInfo, "Clients: Stopping I2P client tunnel on port ", it.first); + LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first); it.second->Stop (); } m_ClientTunnels.clear (); for (auto& it: m_ServerTunnels) { - LogPrint(eLogInfo, "Clients: Stopping I2P server tunnel"); + LogPrint(eLogInfo, "Clients: stopping I2P server tunnel"); it.second->Stop (); } m_ServerTunnels.clear (); if (m_SamBridge) { - LogPrint(eLogInfo, "Clients: Stopping SAM bridge"); + LogPrint(eLogInfo, "Clients: stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; @@ -170,7 +149,7 @@ namespace client if (m_BOBCommandChannel) { - LogPrint(eLogInfo, "Clients: Stopping BOB command channel"); + LogPrint(eLogInfo, "Clients: stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; @@ -178,40 +157,30 @@ namespace client if (m_I2CPServer) { - LogPrint(eLogInfo, "Clients: Stopping I2CP"); + LogPrint(eLogInfo, "Clients: stopping I2CP"); m_I2CPServer->Stop (); delete m_I2CPServer; m_I2CPServer = nullptr; } - LogPrint(eLogInfo, "Clients: Stopping AddressBook"); + 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 (); - } - - LogPrint(eLogInfo, "Clients: Stopping SharedLocalDestination"); - m_SharedLocalDestination->Release (); + for (auto& it: m_Destinations) + it.second->Stop (); + m_Destinations.clear (); m_SharedLocalDestination = nullptr; } @@ -221,6 +190,14 @@ namespace client /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ + // handle tunnels + // reset isUpdated for each tunnel + VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); + // reload tunnels + ReadTunnels(); + // delete not updated tunnels (not in config anymore) + VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); + // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); @@ -229,7 +206,6 @@ namespace client if (m_HttpProxy) { m_HttpProxy->Stop (); - delete m_HttpProxy; m_HttpProxy = nullptr; } ReadHttpProxy (); @@ -238,19 +214,10 @@ namespace client if (m_SocksProxy) { m_SocksProxy->Stop (); - delete m_SocksProxy; m_SocksProxy = nullptr; } ReadSocksProxy (); - // handle tunnels - // reset isUpdated for each tunnel - VisitTunnels (false); - // reload tunnels - ReadTunnels(); - // delete not updated tunnels (not in config anymore) - VisitTunnels (true); - // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) @@ -265,17 +232,12 @@ 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"); - if (!filename.compare (0, transient.length (), transient)) // starts with transient -#endif + if (filename == "transient") { - 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 +254,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 +263,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,35 +304,22 @@ 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); - auto localDestination = std::make_shared (keys, isPublic, params); - AddLocalDestination (localDestination); - return localDestination; - } - - std::shared_ptr ClientContext::CreateNewLocalDestination ( - boost::asio::io_context& service, bool isPublic, - i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, - const std::map * params) - { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); - auto localDestination = std::make_shared (service, keys, isPublic, params); - AddLocalDestination (localDestination); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + auto localDestination = std::make_shared (keys, isPublic, params); + std::unique_lock l(m_DestinationsMutex); + m_Destinations[localDestination->GetIdentHash ()] = localDestination; + localDestination->Start (); return localDestination; } std::shared_ptr ClientContext::CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params) { - auto localDestination = std::make_shared(keys, name, params); - AddLocalDestination (localDestination); - return localDestination; - } - - void ClientContext::AddLocalDestination (std::shared_ptr localDestination) - { + MatchedTunnelDestination * cl = new MatchedTunnelDestination(keys, name, params); + auto localDestination = std::shared_ptr(cl); std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); + return localDestination; } void ClientContext::DeleteLocalDestination (std::shared_ptr destination) @@ -395,37 +344,20 @@ namespace client if (it != m_Destinations.end ()) { LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); - it->second->Start (); // make sure to start + if (!it->second->IsRunning ()) + it->second->Start (); return it->second; } - auto localDestination = std::make_shared (keys, isPublic, params); - AddLocalDestination (localDestination); - return localDestination; - } - - std::shared_ptr ClientContext::CreateNewLocalDestination (boost::asio::io_context& service, - const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) - { - auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); - if (it != m_Destinations.end ()) - { - LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); - it->second->Start (); // make sure to start - return it->second; - } - auto localDestination = std::make_shared (service, keys, isPublic, params); - AddLocalDestination (localDestination); + auto localDestination = std::make_shared (keys, isPublic, params); + std::unique_lock l(m_DestinationsMutex); + m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination; + localDestination->Start (); return localDestination; } void ClientContext::CreateNewSharedLocalDestination () { - std::map params; - ReadI2CPOptionsFromConfig ("shareddest.", params); - params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SharedDest"; - - m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, - i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA + m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, EDDSA m_SharedLocalDestination->Acquire (); } @@ -440,61 +372,29 @@ namespace client template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template std::string ClientContext::GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); } template - void ClientContext::ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const + void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const { - for (auto it: section.second) - { - if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group)) - options[it.first] = it.second.get_value (""); - } - } - - template - void ClientContext::ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const - { - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); - options[I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE, DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE); - options[I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE, DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE); options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); - options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED); - options[I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, DEFAULT_MAX_INBOUND_SPEED); - options[I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS); - options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); - options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "0,4"); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, ""); 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; - auto authType = GetI2CPOption(section, I2CP_PARAM_LEASESET_AUTH_TYPE, 0); - if (authType != "0") // auth is set - { - options[I2CP_PARAM_LEASESET_AUTH_TYPE] = authType; - if (authType == "1") // DH - ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_DH, options); - else if (authType == "2") // PSK - ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_PSK, options); - } - std::string explicitPeers = GetI2CPStringOption(section, I2CP_PARAM_EXPLICIT_PEERS, ""); - if (explicitPeers.length () > 0) options[I2CP_PARAM_EXPLICIT_PEERS] = explicitPeers; - std::string ratchetInboundTags = GetI2CPStringOption(section, I2CP_PARAM_RATCHET_INBOUND_TAGS, ""); - if (ratchetInboundTags.length () > 0) options[I2CP_PARAM_RATCHET_INBOUND_TAGS] = ratchetInboundTags; } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const @@ -504,26 +404,14 @@ namespace client options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE, value)) - options[I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE, value)) - options[I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_TYPE, value)) - options[I2CP_PARAM_LEASESET_TYPE] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, value)) - options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ()) - options[I2CP_PARAM_LEASESET_PRIV_KEY] = value; - if (i2p::config::GetOption(prefix + I2CP_PARAM_STREAMING_PROFILE, value)) - options[I2CP_PARAM_STREAMING_PROFILE] = value; } void ClientContext::ReadTunnels () @@ -531,15 +419,20 @@ namespace client int numClientTunnels = 0, numServerTunnels = 0; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf.empty ()) - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - - LogPrint(eLogDebug, "Clients: Tunnels config file: ", tunConf); + { + // TODO: cleanup this in 2.8.0 + tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); + if (i2p::fs::Exists(tunConf)) + LogPrint(eLogWarning, "Clients: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); + else + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + } + LogPrint(eLogDebug, "Clients: tunnels config file: ", tunConf); ReadTunnels (tunConf, numClientTunnels, numServerTunnels); std::string tunDir; i2p::config::GetOption("tunnelsdir", tunDir); if (tunDir.empty ()) tunDir = i2p::fs::DataDirPath ("tunnels.d"); - if (i2p::fs::Exists (tunDir)) { std::vector files; @@ -547,12 +440,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); + LogPrint(eLogDebug, "Clients: tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } } @@ -562,17 +450,20 @@ namespace client LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } + void ClientContext::ReadTunnels (const std::string& tunConf, int& numClientTunnels, int& numServerTunnels) { boost::property_tree::ptree pt; - try { + try + { boost::property_tree::read_ini (tunConf, pt); - } catch (std::exception& ex) { + } + catch (std::exception& ex) + { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } - std::map > destinations; // keys -> destination for (auto& section: pt) { std::string name = section.first; @@ -580,55 +471,40 @@ namespace client { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT - || type == I2P_TUNNELS_SECTION_TYPE_SOCKS - || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS - || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY - || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) + || type == I2P_TUNNELS_SECTION_TYPE_SOCKS + || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS + || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY + || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params 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); 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; + ReadI2CPOptions (section, options); 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 ()) - localDestination = it->second; - else + i2p::data::PrivateKeys k; + if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { - i2p::data::PrivateKeys k; - if(LoadPrivateKeys (k, keys, sigType, cryptoType)) + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) { - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) - { - if(matchTunnels) - localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); - else - localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); - if (keys != "transient") - destinations[keys] = localDestination; - } + if(matchTunnels) + localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); + else + localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); } } } @@ -636,32 +512,18 @@ namespace client 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); - auto clientTunnel = std::make_shared (name, dest, end, localDestination, destinationPort, gzip); - - auto ins = m_ClientForwards.insert (std::make_pair (end, clientTunnel)); - if (ins.second) { - clientTunnel->Start (); - numClientTunnels++; + localDestination = m_SharedLocalDestination; + } + auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort); + if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) + { + clientTunnel->Start(); } else - { - // TODO: update - if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) - { - LogPrint (eLogInfo, "Clients: I2P UDP client tunnel destination updated"); - ins.first->second->Stop (); - ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); - ins.first->second->Start (); - } - ins.first->second->isUpdated = true; LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); - } } else { boost::asio::ip::tcp::endpoint clientEndpoint; @@ -679,16 +541,16 @@ 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 (); } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { - LogPrint(eLogWarning, "Clients: I2P Client tunnel websocks is deprecated, not starting ", name, " tunnel"); - continue; + // websocks proxy + auto tun = std::make_shared(address, port, localDestination); + clientTunnel = tun; + clientEndpoint = tun->GetLocalEndpoint(); } else { @@ -696,15 +558,7 @@ namespace client auto tun = std::make_shared (name, dest, address, port, localDestination, destinationPort); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); - - uint32_t keepAlive = section.second.get(I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL, 0); - if (keepAlive) - { - tun->SetKeepAliveInterval (keepAlive); - LogPrint(eLogInfo, "Clients: I2P Client tunnel keep alive interval set to ", keepAlive); - } } - uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { @@ -724,113 +578,74 @@ namespace client if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); - ins.first->second->Stop (); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); - ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } - else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER - || type == I2P_TUNNELS_SECTION_TYPE_HTTP - || type == I2P_TUNNELS_SECTION_TYPE_IRC - || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) + || type == I2P_TUNNELS_SECTION_TYPE_HTTP + || type == I2P_TUNNELS_SECTION_TYPE_IRC + || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // 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, ""); - if(accessList == "") - accessList = section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); - std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_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); + bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); - std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, ""); - bool isUniqueLocal = section.second.get (I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); - bool ssl = section.second.get (I2P_SERVER_TUNNEL_SSL, false); + std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); + 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; + ReadI2CPOptions (section, options); std::shared_ptr localDestination = nullptr; - if (keys == "shareddest") - localDestination = m_SharedLocalDestination; - else - { - auto it = destinations.find (keys); - if (it != destinations.end ()) - { - localDestination = it->second; - localDestination->SetPublic (true); - } - 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); - } - } + i2p::data::PrivateKeys k; + if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) + continue; + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + localDestination = CreateNewLocalDestination (k, true, &options); 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); - if (address.empty ()) - { - if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) - address = "::1"; - 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); + boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port); if(!isUniqueLocal) { - LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); + LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); - auto ins = m_ServerForwards.insert(std::make_pair( - std::make_pair(localDestination->GetIdentHash(), port), - serverTunnel)); - if (ins.second) + if(m_ServerForwards.insert( + std::make_pair( + std::make_pair( + localDestination->GetIdentHash(), port), + serverTunnel)).second) { serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else - { - ins.first->second->isUpdated = true; - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, " already exists"); - } + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); continue; } - std::shared_ptr serverTunnel; + std::shared_ptr serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = std::make_shared (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) @@ -838,15 +653,12 @@ namespace client else // regular server tunnel by default serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); - if (!address.empty ()) - serverTunnel->SetLocalAddress (address); - if (!isUniqueLocal) + if(!isUniqueLocal) { - LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); + LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } - if (ssl) - serverTunnel->SetSSL (true); + if (accessList.length () > 0) { std::set idents; @@ -876,22 +688,20 @@ namespace client if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); - ins.first->second->Stop (); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); - ins.first->second->Start (); } ins.first->second->isUpdated = true; - LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); + LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); } } 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 ()); - ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); + LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); } } } @@ -902,45 +712,34 @@ namespace client bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { - std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); - std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); - 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); - LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); - if (httpProxyKeys == "shareddest") - { - localDestination = m_SharedLocalDestination; - localDestination->Acquire (); - } - else if (httpProxyKeys.length () > 0) + std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); + std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); + uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); + i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); + std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); + bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); + 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()); - ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); + LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); } } } @@ -951,39 +750,26 @@ namespace client bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { - std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); - // we still need httpProxyKeys to compare with sockProxyKeys - std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); - std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); - uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); - bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); - 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); - LogPrint(eLogInfo, "Clients: Starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - if (socksProxyKeys == "shareddest") - { - localDestination = m_SharedLocalDestination; - localDestination->Acquire (); - } - else if (httpProxyKeys == socksProxyKeys && m_HttpProxy) - { - localDestination = m_HttpProxy->GetLocalDestination (); - localDestination->Acquire (); - } - else if (socksProxyKeys.length () > 0) + std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); + std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); + uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); + bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); + 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); + LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); + if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if (LoadPrivateKeys (keys, socksProxyKeys, sigType)) { 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 { @@ -993,8 +779,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); - ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); + LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); } } } @@ -1019,52 +804,27 @@ namespace client } } - void ClientContext::VisitTunnels (bool clean) + template + void VisitTunnelsContainer (Container& c, Visitor v) { - for (auto it = m_ClientTunnels.begin (); it != m_ClientTunnels.end ();) + for (auto it = c.begin (); it != c.end ();) { - if(clean && !it->second->isUpdated) { + if (!v (it->second.get ())) + { it->second->Stop (); - it = m_ClientTunnels.erase(it); - } else { - it->second->isUpdated = false; - it++; + it = c.erase (it); } + else + it++; } + } - for (auto it = m_ServerTunnels.begin (); it != m_ServerTunnels.end ();) - { - if(clean && !it->second->isUpdated) { - it->second->Stop (); - it = m_ServerTunnels.erase(it); - } else { - it->second->isUpdated = false; - it++; - } - } - - // TODO: Write correct UDP tunnels stop - for (auto it = m_ClientForwards.begin (); it != m_ClientForwards.end ();) - { - if(clean && !it->second->isUpdated) { - it->second->Stop (); - it = m_ClientForwards.erase(it); - } else { - it->second->isUpdated = false; - it++; - } - } - - for (auto it = m_ServerForwards.begin (); it != m_ServerForwards.end ();) - { - if(clean && !it->second->isUpdated) { - it->second->Stop (); - it = m_ServerForwards.erase(it); - } else { - it->second->isUpdated = false; - it++; - } - } + template + void ClientContext::VisitTunnels (Visitor v) + { + VisitTunnelsContainer (m_ClientTunnels, v); + VisitTunnelsContainer (m_ServerTunnels, v); + // TODO: implement UDP forwards } } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 3f7eaf9a..71e052c3 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,11 +1,3 @@ -/* -* 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 CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ @@ -15,13 +7,13 @@ #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" #include "AddressBook.h" -#include "I18N_langs.h" namespace i2p { @@ -41,13 +33,11 @@ namespace client const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; - const char I2P_CLIENT_TUNNEL_GZIP[] = "gzip"; const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_CRYPTO_TYPE[] = "cryptotype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_CLIENT_TUNNEL_MATCH_TUNNELS[] = "matchtunnels"; - const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; - const char I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL[] = "keepaliveinterval"; + const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; @@ -55,12 +45,11 @@ namespace client const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; - const char I2P_SERVER_TUNNEL_WHITE_LIST[] = "whitelist"; const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; - const char I2P_SERVER_TUNNEL_SSL[] = "ssl"; + class ClientContext { @@ -79,20 +68,12 @@ 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, - 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, - 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); + 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); @@ -103,10 +84,6 @@ namespace client std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); - // i18n - std::shared_ptr GetLanguage () { return m_Language; }; - void SetLanguage (const std::shared_ptr language) { m_Language = language; }; - private: void ReadTunnels (); @@ -118,18 +95,16 @@ namespace client template std::string GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const; // GetI2CPOption with string default value template - void ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const; - template - void ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const; // for tunnels + void ReadI2CPOptions (const Section& section, std::map& options) const; // for tunnels void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); - void VisitTunnels (bool clean); + template + void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain - void CreateNewSharedLocalDestination (); - void AddLocalDestination (std::shared_ptr localDestination); + void CreateNewSharedLocalDestination (); private: @@ -139,9 +114,10 @@ namespace client AddressBook m_AddressBook; - I2PService * m_HttpProxy, * m_SocksProxy; - std::map > m_ClientTunnels; // local endpoint -> tunnel - std::map, std::shared_ptr > m_ServerTunnels; // -> tunnel + 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 std::mutex m_ForwardsMutex; std::map > m_ClientForwards; // local endpoint -> udp tunnel @@ -153,19 +129,15 @@ namespace client std::unique_ptr m_CleanupUDPTimer; - // i18n - std::shared_ptr m_Language; - public: - // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; 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..df4895a4 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,20 +1,10 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include -#include #include #include #include #include -#include #include #include "I2PService.h" @@ -29,39 +19,25 @@ #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" -#include "I18N.h" -#include "Socks5.h" namespace i2p { namespace proxy { - static const std::vector jumporder = { - "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=" } + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; static const char *pageHead = "\r\n" - " \r\n" " I2Pd HTTP proxy\r\n" " \r\n" "\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 +54,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 char *title, const char *description); + void GenericProxyInfo(const char *title, const char *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 +86,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,17 +104,16 @@ 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 */ }; void HTTPReqHandler::AsyncSockRead() { - LogPrint(eLogDebug, "HTTPProxy: Async sock read"); + LogPrint(eLogDebug, "HTTPProxy: async sock read"); if (!m_sock) { - LogPrint(eLogError, "HTTPProxy: No socket for read"); + LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), @@ -148,13 +125,13 @@ namespace proxy { if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug, "HTTPProxy: Close sock"); + LogPrint(eLogDebug, "HTTPProxy: close sock"); m_sock->close(); m_sock = nullptr; } if(m_proxysock) { - LogPrint(eLogDebug, "HTTPProxy: Close proxysock"); + LogPrint(eLogDebug, "HTTPProxy: close proxysock"); if(m_proxysock->is_open()) m_proxysock->close(); m_proxysock = nullptr; @@ -162,40 +139,37 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) - { + void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { std::stringstream ss; - ss << "

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

\r\n"; + ss << "

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 char *title, const char *description) { std::stringstream ss; - ss << "

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

\r\n"; + ss << "

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" - << "

" << tr("You may try to find this host on jump services below") << ":

\r\n" + ss << "

Proxy error: Host not found

\r\n" + << "

Remote host not found in router's addressbook

\r\n" + << "

You may try to find this host on jump services below:

\r\n" << "\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 +180,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::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + 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,110 +201,26 @@ 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; } 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("Referrer"); 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-* + 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"); - } - - /** - * according to i2p ticket #1862: - * leave Referer if requested URL with same schema, host and port, - * otherwise, drop it. - */ - if(req.GetHeader("Referer") != "") { - i2p::http::URL reqURL; reqURL.parse(req.uri); - i2p::http::URL refURL; refURL.parse(req.GetHeader("Referer")); - if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) - req.RemoveHeader("Referer"); - } - + req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /* add headers */ /* close connection, if not Connection: (U|u)pgrade (for websocket) */ auto h = req.GetHeader ("Connection"); @@ -364,79 +243,42 @@ namespace proxy { return false; /* need more data */ if (m_req_len < 0) { - LogPrint(eLogError, "HTTPProxy: Unable to parse request"); - GenericProxyError(tr("Invalid request"), tr("Proxy unable to parse your request")); + LogPrint(eLogError, "HTTPProxy: unable to parse request"); + GenericProxyError("Invalid request", "Proxy unable to parse your request"); return true; /* parse error */ } /* parsing success, now let's look inside request */ - LogPrint(eLogDebug, "HTTPProxy: Requested: ", m_ClientRequest.uri); + LogPrint(eLogDebug, "HTTPProxy: requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); bool m_Confirm; 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")); + LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); + GenericProxyError("Invalid request", "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); + 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 << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " + << "Click here to proceed."; + GenericProxyInfo("Addresshelper found", ss.str().c_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 << "Host " << m_RequestURL.host << " already in router's addressbook. " + << "Click here to update record."; + GenericProxyInfo("Addresshelper found", ss.str().c_str()); return true; /* request processed */ } } @@ -445,11 +287,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("Invalid Request", "invalid request uri"); return true; } else @@ -472,7 +314,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); @@ -492,7 +334,7 @@ namespace proxy { else { /* relative url and missing 'Host' header */ - GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); + GenericProxyError("Invalid request", "Can't detect destination host from request"); return true; } } @@ -505,15 +347,15 @@ namespace proxy { } } else { if(m_OutproxyUrl.size()) { - LogPrint (eLogDebug, "HTTPProxy: Using outproxy ", m_OutproxyUrl); + LogPrint (eLogDebug, "HTTPProxy: use outproxy ", m_OutproxyUrl); if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else - GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); + GenericProxyError("Outproxy failure", "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 ()); - GenericProxyError(tr("Outproxy failure"), ss.str()); + LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outproxy enabled"); + std::string message = "Host " + dest_host + " not inside I2P network, but outproxy is not enabled"; + GenericProxyError("Outproxy failure", message.c_str()); } return true; } @@ -534,7 +376,7 @@ namespace proxy { m_send_buf = m_ClientRequest.to_string(); m_send_buf.append(m_recv_buf); /* connect to destination */ - LogPrint(eLogDebug, "HTTPProxy: Connecting to host ", dest_host, ":", dest_port); + LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; @@ -542,9 +384,9 @@ namespace proxy { void HTTPReqHandler::ForwardToUpstreamProxy() { - LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); + LogPrint(eLogDebug, "HTTPProxy: forward to upstream"); + // build http requset - /* build http request */ m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; @@ -552,28 +394,28 @@ 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()) { m_ClientRequest.uri = origURI; - auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); - if (!auth.empty ()) + if (!m_ProxyURL.user.empty () || !m_ProxyURL.pass.empty ()) { - /* remove existing authorization if any */ + // remove existing authorization if any m_ClientRequest.RemoveHeader("Proxy-"); - /* add own http proxy authorization */ - m_ClientRequest.AddHeader("Proxy-Authorization", auth); + // add own http proxy authorization + std::string s = "Basic " + i2p::data::ToBase64Standard (m_ProxyURL.user + ":" + m_ProxyURL.pass); + m_ClientRequest.AddHeader("Proxy-Authorization", s); } m_send_buf = m_ClientRequest.to_string(); m_recv_buf.erase(0, m_req_len); @@ -583,68 +425,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("unknown outproxy url", m_ProxyURL.to_string().c_str()); } } - 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("cannot resolve upstream proxy", ec.message().c_str()); + 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("hostname too long", m_RequestURL.host.c_str()); return; } uint16_t port = m_RequestURL.port; if(!port) port = 80; - LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream"); + 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("cannot connect to upstream socks proxy", ec.message().c_str()); + } + + void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) + { + LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); + if(ec) GenericProxyError("Cannot negotiate with socks proxy", ec.message().c_str()); + 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); + LogPrint(eLogDebug, "HTTPProxy: handover to socks proxy"); + auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); @@ -652,10 +505,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 +532,7 @@ namespace proxy { } else { - GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); + GenericProxyError("CONNECT error", "Failed to Connect"); } } @@ -687,37 +541,53 @@ namespace proxy { if(m_ClientRequest.method == "CONNECT") { m_ClientResponse.code = 200; 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()); + 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("socks proxy error", ec.message().c_str()); else HandoverToUpstreamProxy(); }); } else { m_send_buf = m_ClientRequestBuffer.str(); - 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()); + 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("failed to send request to upstream", ec.message().c_str()); 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("Socks Proxy error", msg.c_str()); + } + } + else GenericProxyError("No Reply From socks proxy", ec.message().c_str()); + } + 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()); + LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); + GenericProxyError("cannot connect", "http out proxy not implemented"); + } else GenericProxyError("cannot connect to upstream http proxy", ec.message().c_str()); } /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "HTTPProxy: Sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); + LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { - LogPrint(eLogWarning, "HTTPProxy: Sock recv got error: ", ecode); + LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); Terminate(); return; } @@ -740,8 +610,8 @@ namespace proxy { void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (!stream) { - LogPrint (eLogError, "HTTPProxy: Error when creating the stream, check the previous warnings for more info"); - GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); + LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); + GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); return; } if (Kill()) @@ -753,10 +623,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..590166e3 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ @@ -14,28 +6,23 @@ namespace proxy { class HTTPProxy: public i2p::client::TCPIPAcceptor { 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: - // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: - 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 11278e7a..78190c89 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-2019, 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,136 +23,46 @@ 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): - LeaseSetDestination (service, isPublic, ¶ms), - m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), - m_IsCreatingLeaseSet (false), m_IsSameThread (isSameThread), - m_LeaseSetCreationTimer (service), m_ReadinessCheckTimer (service) + I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): + LeaseSetDestination (isPublic, ¶ms), m_Owner (owner), m_Identity (identity) { } - void I2CPDestination::Stop () - { - m_LeaseSetCreationTimer.cancel (); - m_ReadinessCheckTimer.cancel (); - LeaseSetDestination::Stop (); - m_Owner = nullptr; - } - void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { - m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); + memcpy (m_EncryptionPrivateKey, key, 256); + m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), m_EncryptionPrivateKey); } - void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) + bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const { - if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? - { - 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 - { - 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); + return m_Decryptor->Decrypt (encrypted, data, ctx, true); else - LogPrint (eLogError, "I2CP: Decryptor is not set"); + LogPrint (eLogError, "I2CP: decryptor is not set"); return false; } - const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const - { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->GetPubicKey (); - return nullptr; - } - - bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const - { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; - } - - i2p::data::CryptoKeyType I2CPDestination::GetPreferredCryptoType () const - { - if (m_ECIESx25519Decryptor) - return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; - return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; - } - void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; - if (m_Owner) - m_Owner->SendMessagePayloadMessage (buf + 4, length); + m_Owner->SendMessagePayloadMessage (buf + 4, length); } - void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) + void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { - boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, GetSharedFromThis (), tunnels)); - } - - void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) - { - if (m_IsCreatingLeaseSet) - { - 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 + i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); - int numLeases = leases[-1]; - if (m_Owner && numLeases) - { - uint16_t sessionID = m_Owner->GetSessionID (); - if (sessionID != 0xFFFF) - { - m_IsCreatingLeaseSet = true; - htobe16buf (leases - 3, sessionID); - size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*numLeases; - m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); - m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); - auto s = GetSharedFromThis (); - m_LeaseSetCreationTimer.async_wait ([s](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogInfo, "I2CP: LeaseSet creation timeout expired. Terminate"); - if (s->m_Owner) s->m_Owner->Stop (); - } - }); - } - } - else - LogPrint (eLogError, "I2CP: Can't request LeaseSet"); + leases[-1] = tunnels.size (); + htobe16buf (leases - 3, m_Owner->GetSessionID ()); + 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); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) { - m_IsCreatingLeaseSet = false; - m_LeaseSetCreationTimer.cancel (); auto ls = std::make_shared (m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); @@ -161,59 +70,43 @@ namespace client void I2CPDestination::LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len) { - m_IsCreatingLeaseSet = false; - m_LeaseSetCreationTimer.cancel (); auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); - ls->SetExpirationTime (m_LeaseSetExpirationTime); + ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); } void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce) { - auto msg = m_I2NPMsgsPool.AcquireSharedMt (); + auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); htobe32buf (buf, len); 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); + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + }); } else { - auto s = GetSharedFromThis (); RequestDestination (ident, [s, msg, nonce](std::shared_ptr ls) { if (ls) { bool sent = s->SendMsg (msg, ls); - if (s->m_Owner) - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } - else if (s->m_Owner) + else s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } @@ -227,7 +120,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; @@ -241,43 +133,29 @@ 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 + outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + auto leases = remote->GetNonExpiredLeases (); if (!leases.empty ()) - { - auto pool = GetTunnelPool (); - remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()]; - auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway); - outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr, - leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); - } + remoteLease = leases[rand () % leases.size ()]; 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 @@ -287,100 +165,22 @@ 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) - { - } - - RunnableI2CPDestination::~RunnableI2CPDestination () - { - if (IsRunning ()) - Stop (); - } - - void RunnableI2CPDestination::Start () - { - if (!IsRunning ()) - { - I2CPDestination::Start (); - StartIOService (); } } - void RunnableI2CPDestination::Stop () - { - if (IsRunning ()) - { - I2CPDestination::Stop (); - StopIOService (); - } - } - - 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) + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): + m_Owner (owner), m_Socket (socket), m_Payload (nullptr), + m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { } I2CPSession::~I2CPSession () { - Terminate (); + delete[] m_Payload; } 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 (); } @@ -396,7 +196,7 @@ namespace client auto s = shared_from_this (); m_Socket->async_read_some (boost::asio::buffer (m_Header, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { + { if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE) s->ReceiveHeader (); else @@ -407,11 +207,6 @@ namespace client void I2CPSession::ReceiveHeader () { - if (!m_Socket) - { - LogPrint (eLogError, "I2CP: Can't receive header"); - return; - } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -426,33 +221,8 @@ namespace client m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET); 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 (); - } - } - else - { - LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); - Terminate (); - } + m_Payload = new uint8_t[m_PayloadLen]; + ReceivePayload (); } else // no following payload { @@ -464,11 +234,6 @@ namespace client void I2CPSession::ReceivePayload () { - if (!m_Socket) - { - LogPrint (eLogError, "I2CP: Can't receive payload"); - return; - } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -481,6 +246,8 @@ namespace client else { HandleMessage (); + delete[] m_Payload; + m_Payload = nullptr; m_PayloadLen = 0; ReceiveHeader (); // next message } @@ -507,100 +274,59 @@ namespace client m_Socket->close (); m_Socket = nullptr; } - if (!m_SendQueue.IsEmpty ()) - m_SendQueue.CleanUp (); - if (m_SessionID != 0xFFFF) - { - m_Owner.RemoveSession (GetSessionID ()); - LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " terminated"); - m_SessionID = 0xFFFF; - } + m_Owner.RemoveSession (GetSessionID ()); + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { - auto l = len + I2CP_HEADER_SIZE; - if (l > I2CP_MAX_MESSAGE_LENGTH) + auto socket = m_Socket; + if (socket) { - LogPrint (eLogError, "I2CP: Message to send is too long ", l); - return; - } - auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; - uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); - buf[I2CP_HEADER_TYPE_OFFSET] = type; - memcpy (buf + I2CP_HEADER_SIZE, payload, len); - if (sendBuf) - { - if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (std::move(sendBuf)); - else - { - LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); - return; - } + auto l = len + I2CP_HEADER_SIZE; + uint8_t * buf = new uint8_t[l]; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); + buf[I2CP_HEADER_TYPE_OFFSET] = type; + memcpy (buf + I2CP_HEADER_SIZE, payload, len); + boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); } else - { - auto socket = m_Socket; - if (socket) - { - m_IsSending = true; - boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), - boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, - shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - } + LogPrint (eLogError, "I2CP: Can't write to the socket"); } - void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) { - if (ecode) - { - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else if (!m_SendQueue.IsEmpty ()) - { - auto socket = m_Socket; - if (socket) - { - auto len = m_SendQueue.Get (m_SendBuffer, I2CP_MAX_MESSAGE_LENGTH); - boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, len), - boost::asio::transfer_all (),std::bind(&I2CPSession::HandleI2CPMessageSent, - shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - else - m_IsSending = false; - } - else - m_IsSending = false; + delete[] buf; + if (ecode && ecode != boost::asio::error::operation_aborted) + Terminate (); } - 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] != '=') { @@ -609,7 +335,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] != ';') { @@ -617,7 +343,7 @@ namespace client break; } offset++; - mapping.emplace (param, value); + mapping.insert (std::make_pair (param, value)); } } @@ -639,26 +365,21 @@ namespace client void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { RAND_bytes ((uint8_t *)&m_SessionID, 2); + m_Owner.InsertSession (shared_from_this ()); auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); if (!offset) { - LogPrint (eLogError, "I2CP: Create session malformed identity"); - SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid - return; - } - if (m_Owner.FindSessionByIdentHash (identity->GetIdentHash ())) - { - LogPrint (eLogError, "I2CP: Create session duplicate address ", identity->GetIdentHash ().ToBase32 ()); - SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid + LogPrint (eLogError, "I2CP: create session malformed identity"); + SendSessionStatusMessage (3); // invalid return; } uint16_t optionsSize = bufbe16toh (buf + offset); offset += 2; if (optionsSize > len - offset) { - LogPrint (eLogError, "I2CP: Options size ", optionsSize, "exceeds message size"); - SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid + LogPrint (eLogError, "I2CP: options size ", optionsSize, "exceeds message size"); + SendSessionStatusMessage (3); // invalid return; } std::map params; @@ -669,46 +390,42 @@ namespace client offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { + bool isPublic = true; + if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; if (!m_Destination) { - m_Destination = m_Owner.IsSingleThread () ? - std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, true, params): - std::make_shared(shared_from_this (), identity, true, params); - if (m_Owner.InsertSession (shared_from_this ())) - { - LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " created"); - m_Destination->Start (); - SendSessionStatusMessage (eI2CPSessionStatusCreated); // created - } - else - { - LogPrint (eLogError, "I2CP: Session already exists"); - SendSessionStatusMessage (eI2CPSessionStatusRefused); - } + m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); + SendSessionStatusMessage (1); // created + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); + m_Destination->Start (); } else { - LogPrint (eLogError, "I2CP: Session already exists"); - SendSessionStatusMessage (eI2CPSessionStatusRefused); // refused + LogPrint (eLogError, "I2CP: session already exists"); + SendSessionStatusMessage (4); // refused } } else { - LogPrint (eLogError, "I2CP: Create session signature verification failed"); - SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid + LogPrint (eLogError, "I2CP: create session signature verification failed"); + SendSessionStatusMessage (3); // invalid } } - + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { - SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy - LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " destroyed"); - Terminate (); + SendSessionStatusMessage (0); // destroy + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); + if (m_Destination) + { + m_Destination->Stop (); + m_Destination = 0; + } } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) { - I2CPSessionStatus status = eI2CPSessionStatusInvalid; // rejected + uint8_t status = 3; // rejected if(len > sizeof(uint16_t)) { uint16_t sessionID = bufbe16toh(buf); @@ -737,37 +454,37 @@ namespace client { if(m_Destination->Reconfigure(opts)) { - LogPrint(eLogInfo, "I2CP: Reconfigured destination"); - status = eI2CPSessionStatusUpdated; // updated + LogPrint(eLogInfo, "I2CP: reconfigured destination"); + status = 2; // updated } else - LogPrint(eLogWarning, "I2CP: Failed to reconfigure destination"); + LogPrint(eLogWarning, "I2CP: failed to reconfigure destination"); } else - LogPrint(eLogError, "I2CP: Invalid reconfigure message signature"); + LogPrint(eLogError, "I2CP: invalid reconfigure message signature"); } else - LogPrint(eLogError, "I2CP: Mapping size mismatch"); + LogPrint(eLogError, "I2CP: mapping size mismatch"); } else - LogPrint(eLogError, "I2CP: Destination mismatch"); + LogPrint(eLogError, "I2CP: destination mismatch"); } else - LogPrint(eLogError, "I2CP: Malfromed destination"); + LogPrint(eLogError, "I2CP: malfromed destination"); } else - LogPrint(eLogError, "I2CP: Session mismatch"); + LogPrint(eLogError, "I2CP: session mismatch"); } else - LogPrint(eLogError, "I2CP: Short message"); - SendSessionStatusMessage (status); - } + LogPrint(eLogError, "I2CP: short message"); + SendSessionStatusMessage (status); + } - void I2CPSession::SendSessionStatusMessage (I2CPSessionStatus status) + void I2CPSession::SendSessionStatusMessage (uint8_t status) { uint8_t buf[3]; htobe16buf (buf, m_SessionID); - buf[2] = (uint8_t)status; + buf[2] = status; SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } @@ -783,26 +500,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); @@ -821,7 +518,7 @@ namespace client } } else - LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len) @@ -836,33 +533,36 @@ namespace client i2p::data::LeaseSet2 ls (storeType, buf + offset, len - offset); // outer layer only for encrypted if (!ls.IsValid ()) { - LogPrint (eLogError, "I2CP: Invalid LeaseSet2 of type ", storeType); + LogPrint (eLogError, "I2CP: invalid LeaseSet2 of type ", storeType); return; - } + } offset += ls.GetBufferLen (); // private keys int numPrivateKeys = buf[offset]; offset++; + uint16_t currentKeyType = 0; + const uint8_t * currentKey = nullptr; for (int i = 0; i < numPrivateKeys; i++) { if (offset + 4 > len) return; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type - uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length + uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); - else + if (keyType > currentKeyType) { - m_Destination->SetEncryptionType (keyType); - m_Destination->SetEncryptionPrivateKey (buf + offset); + currentKeyType = keyType; + currentKey = buf + offset; } offset += keyLen; - } + } + // TODO: support multiple keys + if (currentKey) + m_Destination->SetEncryptionPrivateKey (currentKey); - m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); + m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } } else - LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) @@ -873,54 +573,29 @@ 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"); + LogPrint(eLogError, "I2CP: cannot send message, too big"); } else - LogPrint(eLogError, "I2CP: Invalid identity"); + LogPrint(eLogError, "I2CP: invalid identity"); } } else - LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) @@ -948,7 +623,7 @@ namespace client if (!addr || !addr->IsIdentHash ()) { // TODO: handle blinded addresses - LogPrint (eLogError, "I2CP: Address ", name, " not found"); + LogPrint (eLogError, "I2CP: address ", name, " not found"); SendHostReplyMessage (requestID, nullptr); return; } @@ -957,7 +632,7 @@ namespace client break; } default: - LogPrint (eLogError, "I2CP: Request type ", (int)buf[10], " is not supported"); + LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported"); SendHostReplyMessage (requestID, nullptr); return; } @@ -983,7 +658,7 @@ namespace client SendHostReplyMessage (requestID, nullptr); } else - LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) @@ -1050,12 +725,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); } @@ -1063,46 +734,26 @@ namespace client { // we don't use SendI2CPMessage to eliminate additional copy auto l = len + 10 + I2CP_HEADER_SIZE; - if (l > I2CP_MAX_MESSAGE_LENGTH) - { - LogPrint (eLogError, "I2CP: Message to send is too long ", l); - return; - } - auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; - uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; + uint8_t * buf = new uint8_t[l]; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); - if (sendBuf) - { - if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) - m_SendQueue.Add (std::move(sendBuf)); - else - { - LogPrint (eLogWarning, "I2CP: Send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); - return; - } - } - else - { - auto socket = m_Socket; - if (socket) - { - m_IsSending = true; - boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), - boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, - shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - } + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); } - I2CPServer::I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread): - RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), - m_Acceptor (GetIOService (), - boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(interface), port)) + I2CPServer::I2CPServer (const std::string& interface, int port): + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, +#ifdef ANDROID + I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address +#else + I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) +#endif { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -1120,37 +771,57 @@ namespace client I2CPServer::~I2CPServer () { - if (IsRunning ()) + if (m_IsRunning) Stop (); } void I2CPServer::Start () { Accept (); - StartIOService (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&I2CPServer::Run, this)); } void I2CPServer::Stop () { + m_IsRunning = false; m_Acceptor.cancel (); - - decltype(m_Sessions) sessions; - m_Sessions.swap (sessions); - for (auto& it: sessions) + for (auto& it: m_Sessions) it.second->Stop (); - - StopIOService (); + m_Sessions.clear (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + + void I2CPServer::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "I2CP: runtime exception: ", ex.what ()); + } + } } void I2CPServer::Accept () { - auto newSocket = std::make_shared (GetIOService ()); + auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2CPServer::HandleAccept(const boost::system::error_code& ecode, - std::shared_ptr socket) + std::shared_ptr socket) { if (!ecode && socket) { @@ -1158,15 +829,15 @@ namespace client auto ep = socket->remote_endpoint (ec); if (!ec) { - LogPrint (eLogDebug, "I2CP: New connection from ", ep); + LogPrint (eLogDebug, "I2CP: new connection from ", ep); auto session = std::make_shared(*this, socket); session->Start (); } else - LogPrint (eLogError, "I2CP: Incoming connection error ", ec.message ()); + LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); } else - LogPrint (eLogError, "I2CP: Accept error: ", ecode.message ()); + LogPrint (eLogError, "I2CP: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); @@ -1177,7 +848,7 @@ namespace client if (!session) return false; if (!m_Sessions.insert({session->GetSessionID (), session}).second) { - LogPrint (eLogError, "I2CP: Duplicate session id ", session->GetSessionID ()); + LogPrint (eLogError, "I2CP: duplicate session id ", session->GetSessionID ()); return false; } return true; @@ -1187,19 +858,6 @@ namespace client { m_Sessions.erase (sessionID); } +} +} - std::shared_ptr I2CPServer::FindSessionByIdentHash (const i2p::data::IdentHash& ident) const - { - for (const auto& it: m_Sessions) - { - if (it.second) - { - auto dest = it.second->GetDestination (); - if (dest && dest->GetIdentHash () == ident) - return it.second; - } - } - return nullptr; - } -} -} diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 37c14dbb..0d235161 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-2019, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,17 +11,11 @@ #include #include -#include #include -#include #include #include -#include #include -#include "util.h" #include "Destination.h" -#include "Streaming.h" -#include "CryptoKey.h" namespace i2p { @@ -29,11 +23,6 @@ namespace client { const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; - 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,20 +53,11 @@ namespace client eI2CPMessageStatusAccepted = 1, eI2CPMessageStatusGuaranteedSuccess = 4, eI2CPMessageStatusGuaranteedFailure = 5, - eI2CPMessageStatusNoLocalTunnels = 16, eI2CPMessageStatusNoLeaseSet = 21 }; - enum I2CPSessionStatus - { - eI2CPSessionStatusDestroyed = 0, - eI2CPSessionStatusCreated = 1, - eI2CPSessionStatusUpdated = 2, - eI2CPSessionStatusInvalid = 3, - eI2CPSessionStatusRefused = 4 - }; - // params + const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; @@ -85,75 +65,36 @@ 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 () {}; - - void Stop () override; + I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); 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, BN_CTX * ctx) const; + std::shared_ptr GetIdentity () const { return m_Identity; }; protected: - // GarlicDestination - i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override - { - return m_ECIESx25519Decryptor ? i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD : 0; - } - // LeaseSetDestination - void CleanupDestination () override; - i2p::data::CryptoKeyType GetPreferredCryptoType () const override; // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len) override; - void CreateNewLeaseSet (const std::vector >& tunnels) override; - + void HandleDataMessage (const uint8_t * buf, size_t len); + void CreateNewLeaseSet (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; - i2p::data::CryptoKeyType m_EncryptionKeyType; - std::shared_ptr m_Decryptor; // standard - std::shared_ptr m_ECIESx25519Decryptor; - uint8_t m_ECIESx25519PrivateKey[32]; + uint8_t m_EncryptionPrivateKey[256]; + std::shared_ptr m_Decryptor; uint64_t m_LeaseSetExpirationTime; - bool m_IsCreatingLeaseSet, m_IsSameThread; - boost::asio::deadline_timer m_LeaseSetCreationTimer, m_ReadinessCheckTimer; - i2p::util::MemoryPoolMt > m_I2NPMsgsPool; - }; - - class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination - { - public: - - RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, - bool isPublic, const std::map& params); - ~RunnableI2CPDestination (); - - void Start (); - void Stop (); }; class I2CPServer; @@ -161,7 +102,13 @@ namespace client { public: - I2CPSession (I2CPServer& owner, std::shared_ptr socket); +#ifdef ANDROID + typedef boost::asio::local::stream_protocol proto; +#else + typedef boost::asio::ip::tcp proto; +#endif + + I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); @@ -174,8 +121,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); @@ -200,64 +145,59 @@ namespace client void HandleMessage (); void Terminate (); - void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); + std::string ExtractString (const uint8_t * buf, size_t len); + size_t PutString (uint8_t * buf, size_t len, const std::string& str); + void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); - std::string_view ExtractString (const uint8_t * buf, size_t len) const; - size_t PutString (uint8_t * buf, size_t len, std::string_view str); - void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) const; - void SendSessionStatusMessage (I2CPSessionStatus status); + void SendSessionStatusMessage (uint8_t status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); - + private: I2CPServer& m_Owner; - std::shared_ptr m_Socket; - uint8_t m_Header[I2CP_HEADER_SIZE], m_Payload[I2CP_MAX_MESSAGE_LENGTH]; + std::shared_ptr m_Socket; + uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; 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; - - // to client - bool m_IsSending; - uint8_t m_SendBuffer[I2CP_MAX_MESSAGE_LENGTH]; - i2p::stream::SendBufferQueue m_SendQueue; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); - class I2CPServer: private i2p::util::RunnableService + class I2CPServer { public: - I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread); + I2CPServer (const std::string& interface, int port); ~I2CPServer (); void Start (); void Stop (); - auto& GetService () { return GetIOService (); }; - bool IsSingleThread () const { return m_IsSingleThread; }; + boost::asio::io_service& GetService () { return m_Service; }; bool InsertSession (std::shared_ptr session); void RemoveSession (uint16_t sessionID); - std::shared_ptr FindSessionByIdentHash (const i2p::data::IdentHash& ident) const; private: + void Run (); + void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: - bool m_IsSingleThread; I2CPMessageHandler m_MessagesHandlers[256]; std::map > m_Sessions; - boost::asio::ip::tcp::acceptor m_Acceptor; + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; + I2CPSession::proto::acceptor m_Acceptor; public: @@ -270,3 +210,4 @@ namespace client } #endif + diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 4ec2648a..7157020f 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,11 +1,3 @@ -/* -* 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 "Destination.h" #include "Identity.h" #include "ClientContext.h" @@ -92,7 +84,7 @@ namespace client auto itr = m_ReadyCallbacks.begin(); while(itr != m_ReadyCallbacks.end()) { - if(itr->second != NEVER_TIMES_OUT && now >= itr->second) + if(itr->second != NEVER_TIMES_OUT && now >= itr->second) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); @@ -102,12 +94,12 @@ namespace client } } if(!ec && m_ReadyCallbacks.size()) - TriggerReadyCheckTimer(); + TriggerReadyCheckTimer(); else - m_ReadyTimerTriggered = false; + 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,33 +111,222 @@ 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()) { - AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) - { + AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) { if(ec) { - LogPrint(eLogWarning, "I2PService::CreateStream() ", ec.message()); + LogPrint(eLogWarning, "I2PService::CeateStream() ", ec.message()); streamRequestComplete(nullptr); } else - { - if (address->IsIdentHash ()) + { if (address->IsIdentHash ()) this->m_LocalDestination->CreateStream(streamRequestComplete, address->identHash, port); else - this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); + this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } }); } else - { + { if (address->IsIdentHash ()) m_LocalDestination->CreateStream (streamRequestComplete, address->identHash, port); else 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..e0dfd2da 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,11 +1,3 @@ -/* -* 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 I2PSERVICE_H__ #define I2PSERVICE_H__ @@ -26,12 +18,10 @@ namespace client class I2PService : public std::enable_shared_from_this { public: - typedef std::function ReadyCallback; public: - - I2PService (std::shared_ptr localDestination = nullptr); + I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService (); @@ -52,16 +42,16 @@ namespace client void AddReadyCallback(ReadyCallback cb); inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } - inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } + inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } inline void SetLocalDestination (std::shared_ptr dest) { if (m_LocalDestination) m_LocalDestination->Release (); 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; @@ -69,24 +59,21 @@ namespace client virtual const char* GetName() { return "Generic I2P Service"; } private: - void TriggerReadyCheckTimer(); void HandleReadyCheckTimer(const boost::system::error_code & ec); private: - std::shared_ptr m_LocalDestination; std::unordered_set > m_Handlers; std::mutex m_HandlersMutex; std::vector > m_ReadyCallbacks; boost::asio::deadline_timer m_ReadyTimer; - bool m_ReadyTimerTriggered; + bool m_ReadyTimerTriggered; uint32_t m_ConnectTimeout; - const size_t NEVER_TIMES_OUT = 0; - + const size_t NEVER_TIMES_OUT = 0; + public: - bool isUpdated; // transient, used during reload only }; @@ -94,17 +81,14 @@ namespace client class I2PServiceHandler { public: - I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } 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 (); }; protected: - // Call when terminating or handing over to avoid race conditions inline bool Kill () { return m_Dead.exchange(true); } // Call to know if the handler is dead @@ -115,176 +99,70 @@ namespace client inline I2PService * GetOwner() { return m_Service; } private: - I2PService *m_Service; 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, 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 (); - 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) {} - }; + 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 d6436c78..290ce11e 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,19 +1,9 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include -#include #include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" -#include "util.h" namespace i2p { @@ -21,7 +11,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,8 +21,9 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, uint16_t port): - I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()) + std::shared_ptr leaseSet, int port): + I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), + m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -40,17 +31,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_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) + 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 () @@ -70,7 +59,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; @@ -80,27 +69,21 @@ namespace client return ourIP; } -#ifdef __linux__ - static void MapToLoopback(std::shared_ptr sock, const i2p::data::IdentHash & addr) + static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) { - if (sock) - { - // bind to 127.x.x.x address - // where x.x.x are first three bytes from ident - auto ourIP = GetLoopbackAddressFor(addr); - boost::system::error_code ec; - sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0), ec); - if (ec) - LogPrint (eLogError, "I2PTunnel: Can't bind ourIP to ", ourIP.to_string (), ": ", ec.message ()); - } + + // bind to 127.x.x.x address + // where x.x.x are first three bytes from ident + auto ourIP = GetLoopbackAddressFor(addr); + sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); + } -#endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { + I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { - I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) @@ -115,26 +98,9 @@ namespace client } } - void I2PTunnelConnection::Connect (const boost::asio::ip::address& localAddress) - { - if (m_Socket) - { - if (m_RemoteEndpoint.address().is_v6 ()) - m_Socket->open (boost::asio::ip::tcp::v6 ()); - else - m_Socket->open (boost::asio::ip::tcp::v4 ()); - boost::system::error_code ec; - m_Socket->bind (boost::asio::ip::tcp::endpoint (localAddress, 0), ec); - if (ec) - LogPrint (eLogError, "I2PTunnel: Can't bind to ", localAddress.to_string (), ": ", ec.message ()); - } - Connect (false); - } - void I2PTunnelConnection::Terminate () { if (Kill()) return; - if (m_SSL) m_SSL = nullptr; if (m_Stream) { m_Stream->Close (); @@ -149,51 +115,43 @@ namespace client void I2PTunnelConnection::Receive () { - if (m_SSL) - m_SSL->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), - std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - else - m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), - std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + 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) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "I2PTunnel: Read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ()); Terminate (); } } else - WriteToStream (m_Buffer, bytes_transferred); - } - - void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) - { - if (m_Stream) { - auto s = shared_from_this (); - m_Stream->AsyncSend (buf, len, - [s](const boost::system::error_code& ecode) - { - if (!ecode) - s->Receive (); - else - s->Terminate (); - }); + if (m_Stream) + { + auto s = shared_from_this (); + m_Stream->AsyncSend (m_Buffer, bytes_transferred, + [s](const boost::system::error_code& ecode) + { + if (!ecode) + s->Receive (); + else + s->Terminate (); + }); } + } } void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) { - LogPrint (eLogError, "I2PTunnel: Write error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -210,12 +168,12 @@ namespace client { 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), + std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); } else // closed by peer { - // get remaining data + // get remaning data auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); @@ -231,7 +189,7 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "I2PTunnel: Stream read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); if (bytes_transferred > 0) Write (m_StreamBuffer, bytes_transferred); // postpone termination else if (ecode == boost::asio::error::timed_out && m_Stream && m_Stream->IsOpen ()) @@ -248,52 +206,36 @@ 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) { if (ecode) { - LogPrint (eLogError, "I2PTunnel: Connect error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ()); Terminate (); } 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)); + LogPrint (eLogDebug, "I2PTunnel: connected"); + 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) @@ -315,7 +257,7 @@ namespace client if (!m_ConnectionSent && !line.compare(0, 10, "Connection")) { /* close connection, if not Connection: (U|u)pgrade (for websocket) */ - auto x = line.find("pgrade"); + auto x = line.find("pgrade"); if (x != std::string::npos && std::tolower(line[x - 1]) == 'u') m_OutHeader << line << "\r\n"; else @@ -329,46 +271,31 @@ namespace client m_ProxyConnectionSent = true; } else - m_OutHeader << line << "\n"; + m_OutHeader << line << "\n"; } } else - { - // insert incomplete line back - m_InHeader.clear (); - m_InHeader << line; break; - } } if (endOfHeader) { if (!m_ConnectionSent) m_OutHeader << "Connection: close\r\n"; - if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\r\n"; + if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\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_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_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) @@ -380,122 +307,30 @@ namespace client m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; - bool endOfHeader = false, connection = false; - while (!endOfHeader) - { - std::getline(m_InHeader, line); - if (m_InHeader.fail ()) break; - if (!m_InHeader.eof ()) - { - if (line == "\r") endOfHeader = true; - else - { - // strip up some headers - static const 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:")) - 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 - 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"; - - m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header - m_InHeader.str (""); - 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 (); - } - } - } - - void I2PServerTunnelConnectionHTTP::WriteToStream (const uint8_t * buf, size_t len) - { - if (m_ResponseHeaderSent) - I2PTunnelConnection::WriteToStream (buf, len); - else - { - m_InHeader.clear (); - if (m_InHeader.str ().empty ()) m_OutHeader.str (""); // start of response - m_InHeader.write ((const char *)buf, len); - std::string line; 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 { - static const std::array excluded // list of excluded headers - { - "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" - }; - bool matched = false; - for (const auto& it: excluded) - if (!line.compare(0, it.length (), it)) - { - matched = true; - break; - } - if (!matched) + if (m_Host.length () > 0 && line.find ("Host:") != std::string::npos) + m_OutHeader << "Host: " << m_Host << "\r\n"; // override host + else m_OutHeader << line << "\n"; } } else - { - // insert incomplete line back - m_InHeader.clear (); - m_InHeader << line; break; - } + } + // add X-I2P fields + if (m_From) + { + m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; + m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; + m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; } if (endOfHeader) @@ -503,19 +338,16 @@ namespace client m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); - m_ResponseHeaderSent = true; - I2PTunnelConnection::WriteToStream ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); - m_OutHeader.str (""); + m_HeaderSent = true; + I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } - else - Receive (); } } 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) { } @@ -526,8 +358,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 (); @@ -558,12 +389,12 @@ namespace client } - /* This handler tries to establish a connection with the desired server and dies if it fails to do so */ + /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { 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(); @@ -571,7 +402,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; }; @@ -587,7 +418,7 @@ namespace client if (stream) { if (Kill()) return; - LogPrint (eLogDebug, "I2PTunnel: New connection"); + LogPrint (eLogDebug, "I2PTunnel: new connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); @@ -612,9 +443,9 @@ 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) + m_DestinationPort (destinationPort) { } @@ -622,26 +453,16 @@ namespace client { TCPIPAcceptor::Start (); GetAddress (); - if (m_KeepAliveInterval) - ScheduleKeepAliveTimer (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; - if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); - } - - void I2PClientTunnel::SetKeepAliveInterval (uint32_t keepAliveInterval) - { - m_KeepAliveInterval = keepAliveInterval; - if (m_KeepAliveInterval) - m_KeepAliveTimer.reset (new boost::asio::deadline_timer (GetLocalDestination ()->GetService ())); } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ - std::shared_ptr I2PClientTunnel::GetAddress () + std::shared_ptr I2PClientTunnel::GetAddress () { if (!m_Address) { @@ -656,50 +477,23 @@ namespace client { auto address = GetAddress (); if (address) - return std::make_shared(this, address, m_DestinationPort, socket); + return std::make_shared(this, address, m_DestinationPort, socket); else return nullptr; } - void I2PClientTunnel::ScheduleKeepAliveTimer () - { - if (m_KeepAliveTimer) - { - m_KeepAliveTimer->expires_from_now (boost::posix_time::seconds (m_KeepAliveInterval)); - m_KeepAliveTimer->async_wait (std::bind (&I2PClientTunnel::HandleKeepAliveTimer, - this, std::placeholders::_1)); - } - } - - void I2PClientTunnel::HandleKeepAliveTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - if (m_Address && m_Address->IsValid ()) - { - if (m_Address->IsIdentHash ()) - GetLocalDestination ()->SendPing (m_Address->identHash); - else - GetLocalDestination ()->SendPing (m_Address->blindedPublicKey); - } - ScheduleKeepAliveTimer (); - } - } - 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); @@ -708,7 +502,7 @@ namespace client else { auto resolver = std::make_shared(GetService ()); - resolver->async_resolve (m_Address, "", + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), std::bind (&I2PServerTunnel::HandleResolve, this, std::placeholders::_1, std::placeholders::_2, resolver)); } @@ -716,65 +510,21 @@ namespace client void I2PServerTunnel::Stop () { - if (m_PortDestination) - m_PortDestination->ResetAcceptor (); - auto localDestination = GetLocalDestination (); - if (localDestination) - localDestination->StopAcceptingStreams (); - ClearHandlers (); } - void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, + void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver) { if (!ecode) { - bool found = false; - boost::asio::ip::tcp::endpoint ep; - if (m_LocalAddress) - { - for (const auto& it: endpoints) - { - ep = it; - if (!ep.address ().is_unspecified ()) - { - if (ep.address ().is_v4 ()) - { - if (m_LocalAddress->is_v4 ()) found = true; - } - else if (ep.address ().is_v6 ()) - { - if (i2p::util::net::IsYggdrasilAddress (ep.address ())) - { - if (i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) - found = true; - } - else if (m_LocalAddress->is_v6 ()) - found = true; - } - } - if (found) break; - } - } - else - { - found = true; - ep = *endpoints.begin (); // first available - } - if (!found) - { - LogPrint (eLogError, "I2PTunnel: Unable to resolve ", m_Address, " to compatible address"); - return; - } - - auto addr = ep.address (); - LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*endpoints.begin ()).host_name (), " has been resolved to ", addr); + auto addr = (*it).endpoint ().address (); + LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); 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) @@ -783,27 +533,6 @@ namespace client m_IsAccessList = true; } - void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) - { - boost::system::error_code ec; - auto addr = boost::asio::ip::make_address(localAddress, ec); - if (!ec) - m_LocalAddress.reset (new boost::asio::ip::address (addr)); - else - LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress); - } - - void I2PServerTunnel::SetSSL (bool ssl) - { - if (ssl) - { - m_SSLCtx = std::make_shared (boost::asio::ssl::context::sslv23); - m_SSLCtx->set_verify_mode(boost::asio::ssl::context::verify_none); - } - else - m_SSLCtx = nullptr; - } - void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -835,22 +564,19 @@ namespace client // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); - if (m_LocalAddress) - conn->Connect (*m_LocalAddress); - else - conn->Connect (m_IsUniqueLocal); + 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) { @@ -858,22 +584,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) { @@ -881,8 +598,281 @@ 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) + { + std::lock_guard lock(m_SessionsMutex); + auto session = ObtainUDPSession(from, toPort, fromPort); + session->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + session->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 == ih) + { + /** 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) + { + 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); + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + m_Destination->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); + 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) : + m_IsUniqueLocal(true), + m_Name(name), + m_LocalAddress(localAddress), + m_RemoteEndpoint(forwardTo) + { + m_LocalDest = localDestination; + m_LocalDest->Start(); + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel() + { + auto dgram = m_LocalDest->GetDatagramDestination(); + if (dgram) dgram->ResetReceiver(); + + LogPrint(eLogInfo, "UDPServer: done"); + } + + void I2PUDPServerTunnel::Start() { + m_LocalDest->Start(); + } + + 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) : + m_Name(name), + m_RemoteDest(remoteDest), + m_LocalDest(localDestination), + m_LocalEndpoint(localEndpoint), + m_RemoteIdent(nullptr), + m_ResolveThread(nullptr), + m_LocalSocket(localDestination->GetService(), localEndpoint), + RemotePort(remotePort), + m_cancel_resolve(false) + { + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5)); + } + + void I2PUDPClientTunnel::Start() { + m_LocalDest->Start(); + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); + RecvFromLocal(); + } + + 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(ec) { + LogPrint(eLogError, "UDP Client: ", ec.message()); + return; + } + if(!m_RemoteIdent) { + LogPrint(eLogWarning, "UDP Client: remote endpoint not resolved yet"); + RecvFromLocal(); + return; // drop, remote not resolved + } + auto remotePort = m_RecvEndpoint.port(); + auto itr = m_Sessions.find(remotePort); + if (itr == m_Sessions.end()) { + // track new udp convo + m_Sessions[remotePort] = {boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0}; + } + // send off to remote i2p destination + LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); + m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); + // mark convo as active + m_Sessions[remotePort].second = i2p::util::GetMillisecondsSinceEpoch(); + RecvFromLocal(); + } + + std::vector > I2PUDPClientTunnel::GetSessions() + { + // TODO: implement + std::vector > infos; + return infos; + } + + void I2PUDPClientTunnel::TryResolving() { + 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) + { + 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 ", from.GetIdentHash().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); + } + else + LogPrint(eLogWarning, "UDP Client: unwarranted traffic from ", from.GetIdentHash().ToBase32()); + } + + I2PUDPClientTunnel::~I2PUDPClientTunnel() { + auto dgram = m_LocalDest->GetDatagramDestination(); + if (dgram) dgram->ResetReceiver(); + + m_Sessions.clear(); + + if(m_LocalSocket.is_open()) + m_LocalSocket.close(); + + m_cancel_resolve = true; + + if(m_ResolveThread) + { + m_ResolveThread->join(); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + if (m_RemoteIdent) delete m_RemoteIdent; } } } - diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 7d4c3400..0d1ac9a8 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,11 +1,3 @@ -/* -* 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 I2PTUNNEL_H__ #define I2PTUNNEL_H__ @@ -16,9 +8,9 @@ #include #include #include -#include #include "Identity.h" #include "Destination.h" +#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" @@ -27,78 +19,61 @@ namespace i2p { namespace client { - const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 16384; + 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 char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 - constexpr char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 - constexpr char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address - const int I2P_TUNNEL_HTTP_MAX_HEADER_SIZE = 8192; + 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); - void Connect (const boost::asio::ip::address& localAddress); protected: - - virtual void Established (); void Terminate (); void Receive (); + void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + virtual void Write (const uint8_t * buf, size_t len); // can be overloaded + void HandleWrite (const boost::system::error_code& ecode); + void StreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - virtual void Write (const uint8_t * buf, size_t len); // can be overloaded - virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded - - std::shared_ptr GetSocket () const { return m_Socket; }; - std::shared_ptr GetStream () const { return m_Stream; }; - std::shared_ptr > GetSSL () const { return m_SSL; }; - uint8_t * GetStreamBuffer () { return m_StreamBuffer; }; - - private: - void HandleConnect (const boost::system::error_code& ecode); - void HandleHandshake (const boost::system::error_code& ecode); - void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleWrite (const boost::system::error_code& ecode); - - private: + std::shared_ptr GetSocket () const { return m_Socket; }; + + private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; - std::shared_ptr > m_SSL; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; + bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection { public: - I2PClientTunnelConnectionHTTP (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PTunnelConnection (owner, socket, stream), m_HeaderSent (false), m_ConnectionSent (false), m_ProxyConnectionSent (false) {}; protected: - - void Write (const uint8_t * buf, size_t len) override; + void Write (const uint8_t * buf, size_t len); private: - std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ConnectionSent, m_ProxyConnectionSent; }; @@ -106,37 +81,31 @@ namespace client class I2PServerTunnelConnectionHTTP: public I2PTunnelConnection { 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); private: - - std::string m_Host, m_XI2P; + std::string m_Host; std::stringstream m_InHeader, m_OutHeader; - bool m_HeaderSent, m_ResponseHeaderSent; + bool m_HeaderSent; + std::shared_ptr m_From; }; class I2PTunnelConnectionIRC: public I2PTunnelConnection { 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: - std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; bool m_NeedsWebIrc; @@ -147,44 +116,155 @@ namespace client class I2PClientTunnel: public TCPIPAcceptor { protected: - // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); 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 (); void Stop (); const char* GetName() { return m_Name.c_str (); } - void SetKeepAliveInterval (uint32_t keepAliveInterval); private: - std::shared_ptr GetAddress (); - void ScheduleKeepAliveTimer (); - void HandleKeepAliveTimer (const boost::system::error_code& ecode); - private: - std::string m_Name, m_Destination; std::shared_ptr m_Address; - uint16_t m_DestinationPort; - uint32_t m_KeepAliveInterval; - std::unique_ptr m_KeepAliveTimer; + int m_DestinationPort; + }; + + + /** 2 minute timeout for udp sessions */ + const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; + + /** max size for i2p udp */ + const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; + + 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); + ~I2PUDPServerTunnel(); + /** expire stale udp conversations */ + void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + void Start(); + 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); + 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; + }; + + 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); + ~I2PUDPClientTunnel(); + void Start(); + 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; } + 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 TryResolving(); + const std::string m_Name; + std::mutex m_SessionsMutex; + std::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; + boost::asio::ip::udp::socket m_LocalSocket; + boost::asio::ip::udp::endpoint m_RecvEndpoint; + uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; + uint16_t RemotePort; + bool m_cancel_resolve; }; 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 (); @@ -194,21 +274,15 @@ 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; } const char* GetName() { return m_Name.c_str (); } private: - - void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); void Accept (); @@ -216,54 +290,42 @@ namespace client 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; }; 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: - std::shared_ptr CreateI2PConnection (std::shared_ptr stream); 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: - std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: - 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..fa08ec51 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-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 "MatchedDestination.h" #include "Log.h" #include "ClientContext.h" @@ -16,16 +8,16 @@ namespace i2p namespace client { MatchedTunnelDestination::MatchedTunnelDestination(const i2p::data::PrivateKeys & keys, const std::string & remoteName, const std::map * params) - : RunnableClientDestination(keys, false, params), + : ClientDestination(keys, false, params), m_RemoteName(remoteName) {} void MatchedTunnelDestination::ResolveCurrentLeaseSet() { - auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); + auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); if(addr && addr->IsIdentHash ()) { - m_RemoteIdent = addr->identHash; + m_RemoteIdent = addr->identHash; auto ls = FindLeaseSet(m_RemoteIdent); if(ls) HandleFoundCurrentLeaseSet(ls); @@ -33,76 +25,89 @@ namespace client RequestDestination(m_RemoteIdent, std::bind(&MatchedTunnelDestination::HandleFoundCurrentLeaseSet, this, std::placeholders::_1)); } else - LogPrint(eLogWarning, "Destination: Failed to resolve ", m_RemoteName); + LogPrint(eLogWarning, "Destination: failed to resolve ", m_RemoteName); } void MatchedTunnelDestination::HandleFoundCurrentLeaseSet(std::shared_ptr ls) { if(ls) { - LogPrint(eLogDebug, "Destination: Resolved remote lease set for ", m_RemoteName); + LogPrint(eLogDebug, "Destination: resolved remote lease set for ", m_RemoteName); m_RemoteLeaseSet = ls; } else { m_ResolveTimer->expires_from_now(boost::posix_time::seconds(1)); m_ResolveTimer->async_wait([&](const boost::system::error_code & ec) { - if(!ec) ResolveCurrentLeaseSet(); + if(!ec) ResolveCurrentLeaseSet(); }); } } - void MatchedTunnelDestination::Start() + bool MatchedTunnelDestination::Start() { - ClientDestination::Start(); - m_ResolveTimer = std::make_shared(GetService()); - GetTunnelPool()->SetCustomPeerSelector(this); - ResolveCurrentLeaseSet(); + if(ClientDestination::Start()) + { + m_ResolveTimer = std::make_shared(GetService()); + GetTunnelPool()->SetCustomPeerSelector(this); + ResolveCurrentLeaseSet(); + return true; + } + else + return false; } - void MatchedTunnelDestination::Stop() + bool MatchedTunnelDestination::Stop() { - ClientDestination::Stop(); - if(m_ResolveTimer) - m_ResolveTimer->cancel(); + if(ClientDestination::Stop()) + { + if(m_ResolveTimer) + m_ResolveTimer->cancel(); + return true; + } + else + return false; } 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))) return false; // more here for outbound tunnels if(!inbound && m_RemoteLeaseSet) { if(m_RemoteLeaseSet->IsExpired()) + { ResolveCurrentLeaseSet(); + } if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) { // remote lease set is good auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); // pick lease std::shared_ptr obep; - while(!obep && leases.size() > 0) - { + while(!obep && leases.size() > 0) { auto idx = rand() % leases.size(); auto lease = leases[idx]; obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); leases.erase(leases.begin()+idx); } - if(obep) - { - path.Add (obep); - LogPrint(eLogDebug, "Destination: Found OBEP matching IBGW"); + if(obep) { + path.push_back(obep->GetRouterIdentity()); + LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); } else - LogPrint(eLogWarning, "Destination: Could not find proper IBGW for matched outbound tunnel"); + LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); } } return true; } + + bool MatchedTunnelDestination::OnBuildResult(const i2p::tunnel::Path & path, bool inbound, i2p::tunnel::TunnelBuildResult result) + { + return true; + } } } diff --git a/libi2pd_client/MatchedDestination.h b/libi2pd_client/MatchedDestination.h index 30ad8942..bbeeb503 100644 --- a/libi2pd_client/MatchedDestination.h +++ b/libi2pd_client/MatchedDestination.h @@ -1,11 +1,3 @@ -/* -* 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 MATCHED_DESTINATION_H_ #define MATCHED_DESTINATION_H_ #include "Destination.h" @@ -16,30 +8,27 @@ namespace i2p namespace client { /** - * client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination + client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ - class MatchedTunnelDestination : public RunnableClientDestination, public i2p::tunnel::ITunnelPeerSelector + class MatchedTunnelDestination : public ClientDestination, public i2p::tunnel::ITunnelPeerSelector { - public: + public: + MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, const std::map * params = nullptr); + bool Start(); + bool Stop(); - MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, - const std::map * params = nullptr); - void Start(); - void Stop(); + bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); + bool OnBuildResult(const i2p::tunnel::Path & peers, bool inbound, i2p::tunnel::TunnelBuildResult result); - bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); + private: + void ResolveCurrentLeaseSet(); + void HandleFoundCurrentLeaseSet(std::shared_ptr ls); - private: - - void ResolveCurrentLeaseSet(); - void HandleFoundCurrentLeaseSet(std::shared_ptr ls); - - private: - - std::string m_RemoteName; - i2p::data::IdentHash m_RemoteIdent; - std::shared_ptr m_RemoteLeaseSet; - std::shared_ptr m_ResolveTimer; + private: + std::string m_RemoteName; + i2p::data::IdentHash m_RemoteIdent; + std::shared_ptr m_RemoteLeaseSet; + std::shared_ptr m_ResolveTimer; }; } } diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 2c0f8d92..acb9ea80 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2025, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #ifdef _MSC_VER @@ -25,7 +17,7 @@ namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), - m_BufferOffset (0), + m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_IsAccepting (false), m_Stream (nullptr) { @@ -34,7 +26,7 @@ namespace client SAMSocket::~SAMSocket () { m_Stream = nullptr; - } + } void SAMSocket::Terminate (const char* reason) { @@ -43,25 +35,27 @@ namespace client m_Stream->AsyncClose (); m_Stream = nullptr; } + auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; case eSAMSocketTypeStream: - break; - case eSAMSocketTypeAcceptor: - case eSAMSocketTypeForward: { - auto session = m_Owner.FindSession(m_ID); - if (session) + break; + } + case eSAMSocketTypeAcceptor: + { + if (Session) { - if (m_IsAccepting && session->GetLocalDestination ()) - session->GetLocalDestination ()->StopAcceptingStreams (); + if (m_IsAccepting && Session->localDestination) + Session->localDestination->StopAcceptingStreams (); } break; } - default: ; + default: + ; } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open ()) @@ -74,7 +68,7 @@ namespace client } void SAMSocket::ReceiveHandshake () - { + { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -99,7 +93,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: Handshake read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake read error"); } @@ -109,7 +103,7 @@ namespace client char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; - LogPrint (eLogDebug, "SAM: Handshake ", m_Buffer); + LogPrint (eLogDebug, "SAM: handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { @@ -158,7 +152,7 @@ namespace client 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::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else @@ -166,7 +160,7 @@ namespace client } else { - LogPrint (eLogError, "SAM: Handshake mismatch"); + LogPrint (eLogError, "SAM: handshake mismatch"); Terminate ("SAM: handshake mismatch"); } } @@ -176,12 +170,12 @@ namespace client { return id == m_ID; } - + void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { - LogPrint (eLogError, "SAM: Handshake reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake reply send error"); } @@ -197,7 +191,7 @@ namespace client { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) + if (!m_IsSilent) 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)); @@ -214,7 +208,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: Reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: reply send error"); } @@ -231,7 +225,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } @@ -245,7 +239,6 @@ namespace client char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) { - if (eol > m_Buffer && eol[-1] == '\r') eol--; *eol = 0; char * separator = strchr (m_Buffer, ' '); if (separator) @@ -262,17 +255,11 @@ namespace client ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); - else if (!strcmp (m_Buffer, SAM_STREAM_FORWARD)) - ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); - else if (!strcmp (m_Buffer, SAM_SESSION_ADD)) - ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); - else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE)) - ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); - else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) + else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1); @@ -293,20 +280,20 @@ namespace client } else { - LogPrint (eLogError, "SAM: Unexpected message ", m_Buffer); + LogPrint (eLogError, "SAM: unexpected message ", m_Buffer); Terminate ("SAM: unexpected message"); } } else { - LogPrint (eLogError, "SAM: Malformed message ", m_Buffer); + LogPrint (eLogError, "SAM: malformed message ", m_Buffer); Terminate ("malformed message"); } } else { - LogPrint (eLogWarning, "SAM: Incomplete message ", bytes_transferred); + LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); @@ -329,7 +316,7 @@ namespace client void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: Session create: ", buf); + LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; @@ -350,42 +337,29 @@ namespace client return; } - SAMSessionType type = eSAMSessionTypeUnknown; - if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; - else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; - else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; - else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster; - if (type == eSAMSessionTypeUnknown) - { - // unknown style - SendSessionI2PError("Unknown STYLE"); - return; - } - std::shared_ptr forward = nullptr; - if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && - params.find(SAM_PARAM_HOST) != params.end() && params.find(SAM_PARAM_PORT) != params.end()) + if (style == SAM_VALUE_DATAGRAM && params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_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_VALUE_HOST], e); if (e) { // not an ip address - SendSessionI2PError("Invalid IP Address in HOST"); + SendI2PError("Invalid IP Address in HOST"); return; } - auto port = std::stoi(params[SAM_PARAM_PORT]); + auto port = std::stoi(params[SAM_VALUE_PORT]); if (port == -1) { - SendSessionI2PError("Invalid port"); + SendI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); } - + //ensure we actually received a destination if (destination.empty()) { @@ -394,7 +368,7 @@ namespace client } if (destination != SAM_VALUE_TRANSIENT) - { + { //ensure it's a base64 string i2p::data::PrivateKeys keys; if (!keys.FromBase64(destination)) @@ -402,31 +376,22 @@ namespace client 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, ¶ms); + auto session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); if (session) { m_SocketType = eSAMSocketTypeSession; - if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) + if (style == SAM_VALUE_DATAGRAM) { session->UDPEndpoint = forward; - auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); - auto port = forward ? std::stoi(params[SAM_PARAM_PORT]) : 0; - if (type == eSAMSessionTypeDatagram) - dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - port - ); - else // raw - dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - port - ); + auto dest = session->localDestination->CreateDatagramDestination (); + dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } - if (session->GetLocalDestination ()->IsReady ()) + if (session->localDestination->IsReady ()) SendSessionCreateReplyOk (); else { @@ -443,23 +408,18 @@ namespace client { 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->localDestination->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"); } } @@ -468,11 +428,15 @@ 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->localDestination->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); } @@ -480,12 +444,7 @@ namespace client void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { - LogPrint (eLogDebug, "SAM: Stream connect: ", buf); - if ( m_SocketType != eSAMSocketTypeUnknown) - { - SendSessionI2PError ("Socket already in use"); - return; - } + LogPrint (eLogDebug, "SAM: stream connect: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -504,43 +463,20 @@ namespace client else m_BufferOffset = 0; - std::shared_ptr addr; - if (destination.find(".i2p") != std::string::npos) - addr = context.GetAddressBook().GetAddress (destination); - else + auto dest = std::make_shared (); + size_t l = dest->FromBase64(destination); + if (l > 0) { - auto dest = std::make_shared (); - size_t l = dest->FromBase64(destination); - if (l > 0) + context.GetAddressBook().InsertFullAddress(dest); + auto leaseSet = session->localDestination->FindLeaseSet(dest->GetIdentHash()); + if (leaseSet) + Connect(leaseSet); + else { - context.GetAddressBook().InsertFullAddress(dest); - addr = std::make_shared
(dest->GetIdentHash ()); - } - } - - if (addr && addr->IsValid ()) - { - 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)); - } - } - else - SendStreamCantReachPeer ("Can't connect to myself"); - } - else // B33 - session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, + session->localDestination->RequestDestination(dest->GetIdentHash(), std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); + } } else SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_STATUS_INVALID_KEY), true); @@ -549,30 +485,18 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::Connect (std::shared_ptr remote, std::shared_ptr session) + void SAMSocket::Connect (std::shared_ptr remote) { - if (!session) session = m_Owner.FindSession(m_ID); - if (session) + auto 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_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send - m_BufferOffset = 0; - I2PReceive (); - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); - } - else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); - } - else - SendStreamCantReachPeer ("Incompatible crypto"); + m_SocketType = eSAMSocketTypeStream; + m_Stream = session->localDestination->CreateStream (remote); + m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send + m_BufferOffset = 0; + I2PReceive (); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } - else - SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) @@ -581,19 +505,14 @@ namespace client Connect (leaseSet); else { - LogPrint (eLogError, "SAM: Destination to connect not found"); - SendStreamCantReachPeer ("LeaseSet not found"); + LogPrint (eLogError, "SAM: destination to connect not found"); + SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: Stream accept: ", buf); - if ( m_SocketType != eSAMSocketTypeUnknown) - { - SendSessionI2PError ("Socket already in use"); - return; - } + LogPrint (eLogDebug, "SAM: stream accept: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -604,89 +523,20 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeAcceptor; - if (!session->GetLocalDestination ()->IsAcceptingStreams ()) + if (!session->localDestination->IsAcceptingStreams ()) { - m_IsAccepting = true; - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); - session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); - } - else - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts) - { - auto socket = session->acceptQueue.front ().first; - session->acceptQueue.pop_front (); - if (socket) - boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket)); - } - if (session->acceptQueue.size () < SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE) - { - // already accepting, queue up - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); - session->acceptQueue.push_back (std::make_pair(shared_from_this(), ts)); - } - else - { - LogPrint (eLogInfo, "SAM: Session ", m_ID, " accept queue is full ", session->acceptQueue.size ()); - SendStreamI2PError ("Already accepting"); - } + m_IsAccepting = true; + session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::ProcessStreamForward (char * buf, size_t len) - { - 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, strlen(SAM_STREAM_STATUS_INVALID_ID), true); - return; - } - if (session->GetLocalDestination ()->IsAcceptingStreams ()) - { - SendSessionI2PError ("Already accepting"); - return; - } - auto it = params.find (SAM_PARAM_PORT); - if (it == params.end ()) - { - SendSessionI2PError ("PORT is missing"); - return; - } - auto port = std::stoi (it->second); - if (port <= 0 || port >= 0xFFFF) - { - SendSessionI2PError ("Invalid PORT"); - return; - } - boost::system::error_code ec; - auto ep = m_Socket.remote_endpoint (ec); - if (ec) - { - SendSessionI2PError ("Socket error"); - return; - } - ep.port (port); - m_SocketType = eSAMSocketTypeForward; - m_ID = id; - m_IsAccepting = true; - std::string& silent = params[SAM_PARAM_SILENT]; - if (silent == SAM_VALUE_TRUE) m_IsSilent = true; - session->GetLocalDestination ()->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, - shared_from_this (), std::placeholders::_1, ep)); - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); - } - size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { - LogPrint (eLogDebug, "SAM: Datagram send: ", buf, " ", len); + LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; @@ -695,25 +545,22 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - auto d = session->GetLocalDestination ()->GetDatagramDestination (); + auto d = session->localDestination->GetDatagramDestination (); if (d) { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); - if (session->Type == eSAMSessionTypeDatagram) - d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); - else // raw - d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); + d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); } else - LogPrint (eLogError, "SAM: Missing datagram destination"); + LogPrint (eLogError, "SAM: missing datagram destination"); } else - LogPrint (eLogError, "SAM: Session is not created from DATAGRAM SEND"); + LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND"); } else { - LogPrint (eLogWarning, "SAM: Sent datagram size ", size, " exceeds buffer ", len - offset); + LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; @@ -721,7 +568,7 @@ namespace client void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: Dest generate"); + LogPrint (eLogDebug, "SAM: dest generate"); std::map params; ExtractParams (buf, params); // extract signature type @@ -731,21 +578,21 @@ namespace client if (it != params.end ()) { if (!m_Owner.ResolveSignatureType (it->second, signatureType)) - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } it = params.find (SAM_PARAM_CRYPTO_TYPE); if (it != params.end ()) { try - { + { cryptoType = std::stoi(it->second); } - catch (const std::exception& ex) + catch (const std::exception& ex) { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); + } + } + 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 ()); @@ -758,38 +605,38 @@ namespace client void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: Naming lookup: ", buf); + LogPrint (eLogDebug, "SAM: naming lookup: ", buf); 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); - auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->GetLocalDestination (); + auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->localDestination; if (name == "ME") - SendNamingLookupReply (name, dest->GetIdentity ()); + SendNamingLookupReply (dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) - SendNamingLookupReply (name, identity); + SendNamingLookupReply (identity); else if ((addr = context.GetAddressBook ().GetAddress (name))) { if (addr->IsIdentHash ()) { auto leaseSet = dest->FindLeaseSet (addr->identHash); if (leaseSet) - SendNamingLookupReply (name, leaseSet->GetIdentity ()); + SendNamingLookupReply (leaseSet->GetIdentity ()); else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); + shared_from_this (), std::placeholders::_1, name)); } else dest->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); + shared_from_this (), std::placeholders::_1, name)); } else { - LogPrint (eLogError, "SAM: Naming failed, unknown address ", name); + LogPrint (eLogError, "SAM: naming failed, unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -799,110 +646,27 @@ namespace client } } - void SAMSocket::ProcessSessionAdd (char * buf, size_t len) - { - auto session = m_Owner.FindSession(m_ID); - if (session && session->Type == eSAMSessionTypeMaster) - { - LogPrint (eLogDebug, "SAM: Subsession add: ", buf); - auto masterSession = std::static_pointer_cast(session); - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; - if (masterSession->subsessions.count (id) > 1) - { - // session exists - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); - return; - } - std::string& style = params[SAM_PARAM_STYLE]; - SAMSessionType type = eSAMSessionTypeUnknown; - if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; - // TODO: implement other styles - if (type == eSAMSessionTypeUnknown) - { - // unknown style - SendSessionI2PError("Unsupported STYLE"); - return; - } - auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); - if (fromPort == -1) - { - SendSessionI2PError("Invalid from port"); - return; - } - auto subsession = std::make_shared(masterSession, id, type, fromPort); - if (m_Owner.AddSession (subsession)) - { - masterSession->subsessions.insert (id); - SendSessionCreateReplyOk (); - } - else - SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); - } - else - SendSessionI2PError ("Wrong session type"); - } - - void SAMSocket::ProcessSessionRemove (char * buf, size_t len) - { - auto session = m_Owner.FindSession(m_ID); - if (session && session->Type == eSAMSessionTypeMaster) - { - LogPrint (eLogDebug, "SAM: Subsession remove: ", buf); - auto masterSession = std::static_pointer_cast(session); - std::map params; - ExtractParams (buf, params); - std::string& id = params[SAM_PARAM_ID]; - if (!masterSession->subsessions.erase (id)) - { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), false); - return; - } - m_Owner.CloseSession (id); - SendSessionCreateReplyOk (); - } - else - SendSessionI2PError ("Wrong session type"); - } - - 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); - } - void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name) { if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); - SendNamingLookupReply (name, leaseSet->GetIdentity ()); + SendNamingLookupReply (leaseSet->GetIdentity ()); } else { - LogPrint (eLogError, "SAM: Naming lookup failed. LeaseSet for ", name, " not found"); + LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", name, " not found"); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -912,13 +676,13 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (const std::string& name, std::shared_ptr identity) + void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) { auto base64 = identity->ToBase64 (); #ifdef _MSC_VER - size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); + size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); #else - size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); + size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } @@ -953,7 +717,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("read error"); } @@ -978,7 +742,7 @@ namespace client if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || - m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular + m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), @@ -988,7 +752,7 @@ namespace client else // closed by peer { uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; - // get remaining data + // get remaning data auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { @@ -1016,7 +780,7 @@ namespace client { delete [] buff; } - + void SAMSocket::WriteI2PData(size_t sz) { boost::asio::async_write ( @@ -1025,12 +789,12 @@ namespace client boost::asio::transfer_all(), std::bind(&SAMSocket::HandleWriteI2PData, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - + void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { - LogPrint (eLogError, "SAM: Stream read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { if (bytes_transferred > 0) @@ -1040,13 +804,13 @@ 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 @@ -1067,7 +831,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: Socket write error: ", ecode.message ()); + LogPrint (eLogError, "SAM: socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("socket write error at HandleWriteI2PData"); } @@ -1081,48 +845,36 @@ namespace client { if (stream) { - LogPrint (eLogDebug, "SAM: Incoming I2P connection for session ", m_ID); + LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID); 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 () == 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->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); + break; } - } } if (!m_IsSilent) - { - if (m_SocketType != eSAMSocketTypeTerminated) - { - // get remote peer address - auto ident = std::make_shared(stream->GetRemoteIdentity()->ToBase64 ()); // we need to keep it until sent - ident->push_back ('\n'); - // send remote peer address back to client like received from stream - boost::asio::async_write (m_Socket, boost::asio::buffer (ident->data (), ident->size ()), boost::asio::transfer_all(), - [ident, s = shared_from_this ()](const boost::system::error_code& ecode, size_t bytes_transferred) - { - s->HandleWriteI2PData (ecode, bytes_transferred); - }); - } + { + // 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 (); @@ -1131,46 +883,9 @@ namespace client LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } - void SAMSocket::HandleI2PForward (std::shared_ptr stream, - boost::asio::ip::tcp::endpoint ep) - { - if (stream) - { - LogPrint (eLogDebug, "SAM: Incoming forward I2P connection for session ", m_ID); - auto newSocket = std::make_shared(m_Owner); - newSocket->SetSocketType (eSAMSocketTypeStream); - auto s = shared_from_this (); - newSocket->GetSocket ().async_connect (ep, - [s, newSocket, stream](const boost::system::error_code& ecode) - { - if (!ecode) - { - s->m_Owner.AddSocket (newSocket); - newSocket->Receive (); - newSocket->m_Stream = stream; - newSocket->m_ID = s->m_ID; - if (!s->m_IsSilent) - { - // get remote peer address - auto dest = stream->GetRemoteIdentity()->ToBase64 (); - memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); - newSocket->m_StreamBuffer[dest.length ()] = '\n'; - newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream - } - else - newSocket->I2PReceive (); - } - else - stream->AsyncClose (); - }); - } - else - LogPrint (eLogWarning, "SAM: I2P forward acceptor has been reset"); - } - void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "SAM: Datagram received ", len); + LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); auto session = m_Owner.FindSession(m_ID); if(session) @@ -1179,9 +894,19 @@ namespace client if (ep) { // udp forward enabled - const char lf = '\n'; - // send to remote endpoint, { destination, linefeed, payload } - m_Owner.SendTo({ {(const uint8_t *)base64.c_str(), base64.size()}, {(const uint8_t *)&lf, 1}, {buf, len} }, *ep); + size_t bsz = base64.size(); + size_t sz = bsz + 1 + len; + // build datagram body + uint8_t * data = new uint8_t[sz]; + // Destination + memcpy(data, base64.c_str(), bsz); + // linefeed + data[bsz] = '\n'; + // Payload + memcpy(data+bsz+1, buf, len); + // send to remote endpoint + m_Owner.SendTo(data, sz, ep); + delete [] data; } else { @@ -1196,48 +921,28 @@ namespace client WriteI2PData(len + l); } else - LogPrint (eLogWarning, "SAM: Received datagram size ", len," exceeds buffer"); - } - } - } - - void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SAM: Raw datagram received ", len); - auto session = m_Owner.FindSession(m_ID); - if(session) - { - auto ep = session->UDPEndpoint; - if (ep) - // udp forward enabled - m_Owner.SendTo({ {buf, len} }, *ep); - else - { -#ifdef _MSC_VER - 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_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); -#endif - if (len < SAM_SOCKET_BUFFER_SIZE - l) - { - memcpy (m_StreamBuffer + l, buf, len); - WriteI2PData(len + l); - } - else - LogPrint (eLogWarning, "SAM: Received raw datagram size ", len," exceeds buffer"); + LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer"); } } } 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, const std::string & id, SAMSessionType type): - m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) + + SAMSession::SAMSession (SAMBridge & parent, const std::string & id, std::shared_ptr dest): + m_Bridge(parent), + localDestination (dest), + UDPEndpoint(nullptr), + Name(id) { } + + SAMSession::~SAMSession () + { + i2p::client::context.DeleteLocalDestination (localDestination); + } void SAMSession::CloseStreams () { @@ -1247,83 +952,26 @@ namespace client } } - SAMSingleSession::SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest): - SAMSession (parent, name, type), - localDestination (dest) - { - } - - SAMSingleSession::~SAMSingleSession () - { - i2p::client::context.DeleteLocalDestination (localDestination); - } - - void SAMSingleSession::StopLocalDestination () - { - localDestination->Release (); - // stop accepting new streams - localDestination->StopAcceptingStreams (); - // terminate existing streams - auto s = localDestination->GetStreamingDestination (); // TODO: take care about datagrams - if (s) s->Stop (); - } - - void SAMMasterSession::Close () - { - SAMSingleSession::Close (); - for (const auto& it: subsessions) - m_Bridge.CloseSession (it); - subsessions.clear (); - } - - SAMSubSession::SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, uint16_t port): - SAMSession (master->m_Bridge, name, type), masterSession (master), inPort (port) - { - if (Type == eSAMSessionTypeStream) - { - auto d = masterSession->GetLocalDestination ()->CreateStreamingDestination (inPort); - if (d) d->Start (); - } - // TODO: implement datagrams - } - - std::shared_ptr SAMSubSession::GetLocalDestination () - { - return masterSession ? masterSession->GetLocalDestination () : nullptr; - } - - void SAMSubSession::StopLocalDestination () - { - auto dest = GetLocalDestination (); - if (dest && Type == eSAMSessionTypeStream) - { - auto d = dest->RemoveStreamingDestination (inPort); - if (d) d->Stop (); - } - // TODO: implement datagrams - } - - SAMBridge::SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread): - RunnableService ("SAM"), m_IsSingleThread (singleThread), - m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), portTCP)), - m_DatagramEndpoint (boost::asio::ip::make_address(address), (!portUDP) ? portTCP-1 : portUDP), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), + SAMBridge::SAMBridge (const std::string& address, int port): + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), + m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint), m_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, {"ECDSA_SHA256_P256", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256}, - {"ECDSA_SHA384_P384", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384}, - {"ECDSA_SHA512_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521}, + {"ECDSA_SHA256_P384", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384}, + {"ECDSA_SHA256_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521}, {"EdDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519}, {"GOST_GOSTR3411256_GOSTR3410CRYPTOPROA", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256}, - {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512}, - {"RedDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519}, + {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512} } { } SAMBridge::~SAMBridge () { - if (IsRunning ()) + if (m_IsRunning) Stop (); } @@ -1331,29 +979,48 @@ namespace client { Accept (); ReceiveDatagram (); - StartIOService (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&SAMBridge::Run, this)); } void SAMBridge::Stop () { + m_IsRunning = false; + try { m_Acceptor.cancel (); } catch (const std::exception& ex) { - LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); } - decltype(m_Sessions) sessions; + for (auto& it: m_Sessions) + it.second->CloseStreams (); + m_Sessions.clear (); + m_Service.stop (); + if (m_Thread) { - std::unique_lock l(m_SessionsMutex); - m_Sessions.swap (sessions); - } - for (auto& it: sessions) - it.second->Close (); - - StopIOService (); + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + + void SAMBridge::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); + } + } } void SAMBridge::Accept () @@ -1363,18 +1030,12 @@ namespace client std::placeholders::_1, newSocket)); } - void SAMBridge::AddSocket(std::shared_ptr socket) - { - std::unique_lock lock(m_OpenSocketsMutex); - m_OpenSockets.push_back(socket); - } - void SAMBridge::RemoveSocket(const std::shared_ptr & socket) { std::unique_lock lock(m_OpenSocketsMutex); m_OpenSockets.remove_if([socket](const std::shared_ptr & item) -> bool { return item == socket; }); } - + void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) @@ -1383,31 +1044,32 @@ namespace client auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { - LogPrint (eLogDebug, "SAM: New connection from ", ep); - AddSocket (socket); + LogPrint (eLogDebug, "SAM: new connection from ", ep); + { + std::unique_lock l(m_OpenSocketsMutex); + m_OpenSockets.push_back(socket); + } socket->ReceiveHandshake (); } else - LogPrint (eLogError, "SAM: Incoming connection error: ", ec.message ()); + LogPrint (eLogError, "SAM: incoming connection error ", ec.message ()); } else - LogPrint (eLogError, "SAM: Accept error: ", ecode.message ()); + LogPrint (eLogError, "SAM: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } - std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, - const std::string& destination, const std::map * params) + std::shared_ptr SAMBridge::CreateSession (const std::string& id, const std::string& destination, + const std::map * params) { std::shared_ptr localDestination = nullptr; if (destination != "") { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; - localDestination = m_IsSingleThread ? - i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : - i2p::client::context.CreateNewLocalDestination (keys, true, params); + localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params); } else // transient { @@ -1418,32 +1080,29 @@ namespace client { 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); - } + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); + } it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) - { + { try - { + { cryptoType = std::stoi(it->second); } - catch (const std::exception& ex) + catch (const std::exception& ex) { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); + } + } } - localDestination = m_IsSingleThread ? - i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : - i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); + localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { localDestination->Acquire (); - auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : - std::make_shared(*this, id, type, localDestination); + auto session = std::make_shared(*this, id, localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) @@ -1453,13 +1112,6 @@ namespace client return nullptr; } - bool SAMBridge::AddSession (std::shared_ptr session) - { - if (!session) return false; - auto ret = m_Sessions.emplace (session->Name, session); - return ret.second; - } - void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; @@ -1474,42 +1126,12 @@ namespace client } if (session) { - session->StopLocalDestination (); - session->Close (); - if (m_IsSingleThread) - ScheduleSessionCleanupTimer (session); // let all session's streams close + session->localDestination->Release (); + session->localDestination->StopAcceptingStreams (); + session->CloseStreams (); } } - void SAMBridge::ScheduleSessionCleanupTimer (std::shared_ptr session) - { - auto timer = std::make_shared(GetService ()); - timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds - timer->async_wait (std::bind (&SAMBridge::HandleSessionCleanupTimer, this, std::placeholders::_1, session, timer)); - } - - void SAMBridge::HandleSessionCleanupTimer (const boost::system::error_code& ecode, - std::shared_ptr session, std::shared_ptr timer) - { - if (ecode != boost::asio::error::operation_aborted && session) - { - auto dest = session->GetLocalDestination (); - if (dest) - { - auto streamingDest = dest->GetStreamingDestination (); - auto numStreams = streamingDest->GetNumStreams (); - if (numStreams > 0) - { - LogPrint (eLogInfo, "SAM: Session ", session->Name, " still has ", numStreams, " streams"); - ScheduleSessionCleanupTimer (session); - } - else - LogPrint (eLogDebug, "SAM: Session ", session->Name, " terminated"); - } - } - // session's destructor is called here unless rescheduled - } - std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); @@ -1530,10 +1152,13 @@ namespace client } return list; } - - void SAMBridge::SendTo (const std::vector& bufs, const boost::asio::ip::udp::endpoint& ep) + + void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) { - m_DatagramSocket.send_to (bufs, ep); + if(remote) + { + m_DatagramSocket.send_to(boost::asio::buffer(buf, len), *remote); + } } void SAMBridge::ReceiveDatagram () @@ -1554,7 +1179,7 @@ namespace client { *eol = 0; eol++; size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); - LogPrint (eLogDebug, "SAM: Datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); + LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); if (sessionID) { @@ -1566,21 +1191,10 @@ 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 == eSAMSessionTypeDatagram) - datagramDest->SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); - else if (session->Type == 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); + session->localDestination->GetDatagramDestination ()-> + SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); @@ -1592,17 +1206,17 @@ namespace client LogPrint (eLogError, "SAM: Missing sessionID"); } else - LogPrint(eLogError, "SAM: Invalid datagram"); + LogPrint(eLogError, "SAM: invalid datagram"); ReceiveDatagram (); } else - LogPrint (eLogError, "SAM: Datagram receive error: ", ecode.message ()); + LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); } bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const { try - { + { type = std::stoi (name); } catch (const std::invalid_argument& ex) @@ -1614,12 +1228,12 @@ namespace client else return false; } - catch (const std::exception& ex) + catch (const std::exception& ex) { - return false; - } - // name has been resolved - return true; + return false; + } + // name has been resolved + return true; } } } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 1886324a..7cd3fd4c 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,11 +1,3 @@ -/* -* 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 SAM_H__ #define SAM_H__ @@ -13,12 +5,10 @@ #include #include #include -#include #include #include #include #include -#include "util.h" #include "Identity.h" #include "LeaseSet.h" #include "Streaming.h" @@ -30,10 +20,7 @@ namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds - const int SAM_SESSION_READINESS_CHECK_INTERVAL = 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"; const char SAM_HANDSHAKE_NOVERSION[] = "HELLO REPLY RESULT=NOVERSION\n"; @@ -44,26 +31,21 @@ namespace client 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_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n"; const char SAM_STREAM_CONNECT[] = "STREAM CONNECT"; const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; const char SAM_STREAM_STATUS_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\n"; - const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"%s\"\n"; - const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"%s\"\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"; - const char SAM_RAW_SEND[] = "RAW SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; - const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=%s VALUE=%s\n"; + const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; - const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; @@ -76,16 +58,14 @@ namespace client const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; const char SAM_PARAM_CRYPTO_TYPE[] = "CRYPTO_TYPE"; const char SAM_PARAM_SIZE[] = "SIZE"; - const char SAM_PARAM_HOST[] = "HOST"; - 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"; + const char SAM_VALUE_HOST[] = "HOST"; + const char SAM_VALUE_PORT[] = "PORT"; enum SAMSocketType { @@ -93,7 +73,6 @@ namespace client eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, - eSAMSocketTypeForward, eSAMSocketTypeTerminated }; @@ -105,7 +84,7 @@ namespace client typedef boost::asio::ip::tcp::socket Socket_t; SAMSocket (SAMBridge& owner); - ~SAMSocket (); + ~SAMSocket (); Socket_t& GetSocket () { return m_Socket; }; void ReceiveHandshake (); @@ -115,11 +94,10 @@ namespace client void Terminate (const char* reason); bool IsSession(const std::string & id) const; - - private: - + + private: void TerminateClose() { Terminate(nullptr); } - + 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); @@ -131,29 +109,21 @@ namespace client void I2PReceive (); void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); - void HandleI2PForward (std::shared_ptr stream, boost::asio::ip::tcp::endpoint ep); void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz); void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len, size_t rem); void ProcessStreamAccept (char * buf, size_t len); - void ProcessStreamForward (char * buf, size_t len); void ProcessDestGenerate (char * buf, size_t len); void ProcessNamingLookup (char * buf, size_t len); - void ProcessSessionAdd (char * buf, size_t len); - void ProcessSessionRemove (char * buf, size_t len); - void 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 SendI2PError(const std::string & msg); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); - void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); + void Connect (std::shared_ptr remote); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); - void SendNamingLookupReply (const std::string& name, std::shared_ptr identity); + void SendNamingLookupReply (std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); @@ -163,7 +133,7 @@ namespace client void HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff); void HandleStreamSend(const boost::system::error_code & ec); - + private: SAMBridge& m_Owner; @@ -179,105 +149,59 @@ namespace client std::shared_ptr m_Stream; }; - enum SAMSessionType - { - eSAMSessionTypeUnknown, - eSAMSessionTypeStream, - eSAMSessionTypeDatagram, - eSAMSessionTypeRaw, - eSAMSessionTypeMaster - }; - struct SAMSession { SAMBridge & m_Bridge; + std::shared_ptr localDestination; + std::shared_ptr UDPEndpoint; std::string Name; - SAMSessionType Type; - std::shared_ptr UDPEndpoint; // TODO: move - std::list, uint64_t> > acceptQueue; // socket, receive time in seconds - - SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type); - virtual ~SAMSession () {}; - virtual std::shared_ptr GetLocalDestination () = 0; - virtual void StopLocalDestination () = 0; - virtual void Close () { CloseStreams (); }; + SAMSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest); + ~SAMSession (); void CloseStreams (); }; - struct SAMSingleSession: public SAMSession - { - std::shared_ptr localDestination; - - SAMSingleSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); - ~SAMSingleSession (); - - std::shared_ptr GetLocalDestination () { return localDestination; }; - void StopLocalDestination (); - }; - - struct SAMMasterSession: public SAMSingleSession - { - 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; - - SAMSubSession (std::shared_ptr master, const std::string& name, SAMSessionType type, uint16_t port); - // implements SAMSession - std::shared_ptr GetLocalDestination (); - void StopLocalDestination (); - }; - - class SAMBridge: private i2p::util::RunnableService + class SAMBridge { public: - SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread); + SAMBridge (const std::string& address, int port); ~SAMBridge (); void Start (); void Stop (); - auto& GetService () { return GetIOService (); }; - std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient + boost::asio::io_service& GetService () { return m_Service; }; + std::shared_ptr CreateSession (const std::string& id, const std::string& destination, // empty string means transient const std::map * params); - bool AddSession (std::shared_ptr session); void CloseSession (const std::string& id); std::shared_ptr FindSession (const std::string& id) const; 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); + void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); - void AddSocket(std::shared_ptr socket); void RemoveSocket(const std::shared_ptr & socket); - - bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; + + bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; private: + void Run (); + void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); 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; + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 27df33c8..8a67901c 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -19,7 +11,6 @@ #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" -#include "Socks5.h" namespace i2p { @@ -28,6 +19,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; @@ -46,7 +41,6 @@ namespace proxy class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: - enum state { GET_SOCKSV, @@ -63,11 +57,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 +115,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 +128,27 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); - template - void SocksUpstreamSuccess(std::shared_ptr& upstreamSock); - void AsyncUpstreamSockRead(); - template - void SendUpstreamRequest(std::shared_ptr& upstreamSock); - void HandleUpstreamConnected(const boost::system::error_code & ecode, - const boost::asio::ip::tcp::endpoint& ep); - void HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::results_type endpoints); + void SocksUpstreamSuccess(); + void AsyncUpstreamSockRead(); + void SendUpstreamRequest(); + void HandleUpstreamData(uint8_t * buff, std::size_t len); + void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamConnected(const boost::system::error_code & ecode, + boost::asio::ip::tcp::resolver::iterator itr); + void HandleUpstreamResolved(const boost::system::error_code & ecode, + 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 + boost::asio::ip::tcp::resolver m_proxy_resolver; + uint8_t m_sock_buff[socks_buffer_size]; + std::shared_ptr m_sock, m_upstreamSock; 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 @@ -170,12 +161,11 @@ namespace proxy cmdTypes m_cmd; // Command requested state m_state; const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses? - const std::string m_UpstreamProxyAddress; - const uint16_t m_UpstreamProxyPort; + const std::string m_UpstreamProxyAddress; + const uint16_t m_UpstreamProxyPort; public: - - SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : + SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : I2PServiceHandler(parent), m_proxy_resolver(parent->GetService()), m_sock(sock), m_stream(nullptr), @@ -191,13 +181,13 @@ namespace proxy void SOCKSHandler::AsyncSockRead() { - LogPrint(eLogDebug, "SOCKS: Async sock read"); + LogPrint(eLogDebug, "SOCKS: async sock read"); if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), - std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError,"SOCKS: No socket for read"); + LogPrint(eLogError,"SOCKS: no socket for read"); } } @@ -206,120 +196,124 @@ namespace proxy if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug, "SOCKS: Closing socket"); + LogPrint(eLogDebug, "SOCKS: closing socket"); m_sock->close(); m_sock = nullptr; } if (m_upstreamSock) { - LogPrint(eLogDebug, "SOCKS: Closing upstream socket"); + LogPrint(eLogDebug, "SOCKS: closing upstream socket"); 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"); + LogPrint(eLogDebug, "SOCKS: closing stream"); m_stream.reset (); } 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); + m_response[0] = '\x00'; //Version + m_response[1] = error; //Response code + htobe16buf(m_response+2,port); //Port + htobe32buf(m_response+4,ip); //IP + return boost::asio::const_buffers_1(m_response,8); } - 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 + size_t size = 6; assert(error <= SOCKS5_ADDR_UNSUP); - m_response[0] = '\x05'; // version - m_response[1] = error; // response code - m_response[2] = '\x00'; // reserved - m_response[3] = type; // address type + m_response[0] = '\x05'; //Version + m_response[1] = error; //Response code + m_response[2] = '\x00'; //RSV + m_response[3] = type; //Address type switch (type) { case ADDR_IPV4: - size += 4; - htobe32buf(m_response + 4, addr.ip); - htobe16buf(m_response + size - 2, port); + size = 10; + htobe32buf(m_response+4,addr.ip); break; case ADDR_IPV6: - size += 16; - memcpy(m_response + 4, addr.ipv6, 16); - htobe16buf(m_response + size - 2, port); + size = 22; + memcpy(m_response+4,addr.ipv6, 16); break; case ADDR_DNS: - std::string address(addr.dns.value, addr.dns.size); - if(address.substr(addr.dns.size - 4, 4) == ".i2p") // overwrite if requested address inside I2P - { - m_response[3] = ADDR_IPV4; - size += 4; - memset(m_response + 4, 0, 6); // six HEX zeros - } - else - { - size += (1 + addr.dns.size); /* name length + resolved address */ - m_response[4] = addr.dns.size; - memcpy(m_response + 5, addr.dns.value, addr.dns.size); - htobe16buf(m_response + size - 2, port); - } + size = 7+addr.dns.size; + m_response[4] = addr.dns.size; + memcpy(m_response+5,addr.dns.value, addr.dns.size); break; } - return boost::asio::const_buffer (m_response, size); + htobe16buf(m_response+size-2,port); //Port + 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); + m_response[0] = '\x05'; //Version + m_response[1] = m_authchosen; //Response code + boost::asio::const_buffers_1 response(m_response,2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, + shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, + shared_from_this(), std::placeholders::_1)); return true; } } - 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) { case SOCKS4: LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error); - if (error < SOCKS4_OK) error = SOCKS4_FAIL; // Transparently map SOCKS5 errors + if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: @@ -328,13 +322,13 @@ namespace proxy break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() { - boost::asio::const_buffer response(nullptr,0); - // TODO: this should depend on things like the command type and callbacks may change + 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) { case SOCKS4: @@ -345,11 +339,12 @@ namespace proxy LogPrint(eLogInfo, "SOCKS: v5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); - // HACK only 16 bits passed in port as SOCKS5 doesn't allow for more + //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); break; } - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { @@ -371,12 +366,12 @@ namespace proxy { if ( m_cmd != CMD_CONNECT ) { - // TODO: we need to support binds and other shit! - LogPrint(eLogError, "SOCKS: Unsupported command: ", m_cmd); + //TODO: we need to support binds and other shit! + LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } - // TODO: we may want to support other address types! + //TODO: we may want to support other address types! if ( m_addrtype != ADDR_DNS ) { switch (m_socksv) @@ -385,7 +380,7 @@ namespace proxy LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: - LogPrint(eLogError, "SOCKS: Request with v4a rejected because it's actually SOCKS4"); + LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); @@ -412,7 +407,7 @@ namespace proxy EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: - LogPrint(eLogError, "SOCKS: Rejected invalid version: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } @@ -424,15 +419,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,9 +433,8 @@ namespace proxy break; case CMD_UDP: if (m_socksv == SOCKS5) break; - [[fallthrough]]; default: - LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -546,46 +535,8 @@ 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); + LogPrint(eLogError, "SOCKS: parse state?? ", m_state); Terminate(); return false; } @@ -603,10 +554,10 @@ namespace proxy void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "SOCKS: Received ", len, " bytes"); + LogPrint(eLogDebug, "SOCKS: received ", len, " bytes"); if(ecode) { - LogPrint(eLogWarning, "SOCKS: Recv got error: ", ecode); + LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode); Terminate(); return; } @@ -616,7 +567,7 @@ namespace proxy if (m_state == READY) { const std::string addr = m_address.dns.ToString(); - LogPrint(eLogInfo, "SOCKS: Requested ", addr, ":" , m_port); + LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port); const size_t addrlen = addr.size(); // does it end with .i2p? if ( addr.rfind(".i2p") == addrlen - 4) { @@ -639,7 +590,7 @@ namespace proxy void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { if (ecode) - LogPrint (eLogError, "SOCKS: Closing socket after sending failure because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ()); Terminate(); } @@ -648,7 +599,7 @@ namespace proxy if (!ecode) { if (Kill()) return; - LogPrint (eLogInfo, "SOCKS: New I2PTunnel connection"); + LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); @@ -656,7 +607,7 @@ namespace proxy } else { - LogPrint (eLogError, "SOCKS: Closing socket after completion reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ()); Terminate(); } } @@ -665,7 +616,7 @@ namespace proxy { if (ecode) { - LogPrint (eLogError, "SOCKS: Closing socket after sending reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ()); Terminate(); } } @@ -679,55 +630,51 @@ namespace proxy } else { - LogPrint (eLogError, "SOCKS: Error when creating the stream, check the previous warnings for more info"); + LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info"); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } 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); - } + LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); + EnterState(UPSTREAM_RESOLVE); + boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort)); + m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } - template - void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr& upstreamSock) + void SOCKSHandler::AsyncUpstreamSockRead() { - LogPrint(eLogInfo, "SOCKS: Upstream success"); - boost::asio::const_buffer response(nullptr, 0); + 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_buffers_1 response(nullptr, 0); switch (m_socksv) { case SOCKS4: @@ -741,72 +688,90 @@ 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) { - 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 - LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to"); + 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::HandleUpstreamConnected(const boost::system::error_code & ecode, - const boost::asio::ip::tcp::endpoint& ep) + void SOCKSHandler::SendUpstreamRequest() + { + LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); + EnterState(UPSTREAM_HANDSHAKE); + 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, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { - LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message()); + LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } - LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy"); - SendUpstreamRequest(m_upstreamSock); + LogPrint(eLogInfo, "SOCKS: connected to upstream proxy"); + 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 - LogPrint(eLogWarning, "SOCKS: Upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); + LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } - LogPrint(eLogInfo, "SOCKS: Upstream proxy resolved"); + LogPrint(eLogInfo, "SOCKS: upstream proxy resolved"); 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) + TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) { m_UseUpstreamProxy = false; if (outAddress.length() > 0 && outEnable) diff --git a/libi2pd_client/SOCKS.h b/libi2pd_client/SOCKS.h index bd88d6e6..87f08de4 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.h @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-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 SOCKS_H__ #define SOCKS_H__ @@ -22,15 +14,13 @@ namespace proxy class SOCKSServer: public i2p::client::TCPIPAcceptor { 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() {}; void SetUpstreamProxy(const std::string & addr, const uint16_t port); protected: - // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp deleted file mode 100644 index b173fc0f..00000000 --- a/libi2pd_client/UDPTunnel.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include "Log.h" -#include "util.h" -#include "ClientContext.h" -#include "I2PTunnel.h" // for GetLoopbackAddressFor -#include "UDPTunnel.h" - -namespace i2p -{ -namespace client -{ - void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) - m_LastSession = ObtainUDPSession(from, toPort, fromPort); - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - - void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (m_LastSession && (fromPort != m_LastSession->RemotePort || toPort != m_LastSession->LocalPort)) - { - std::lock_guard lock(m_SessionsMutex); - auto it = m_Sessions.find (GetSessionIndex (fromPort, toPort)); - if (it != m_Sessions.end ()) - m_LastSession = it->second; - else - m_LastSession = nullptr; - } - if (m_LastSession) - { - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - } - - void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - auto itr = m_Sessions.begin(); - while(itr != m_Sessions.end()) - { - if(now - itr->second->LastActivity >= delta ) - itr = m_Sessions.erase(itr); - else - itr++; - } - } - - void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) - { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - std::vector removePorts; - for (const auto & s : m_Sessions) { - if (now - s.second->second >= delta) - removePorts.push_back(s.first); - } - for(auto port : removePorts) { - m_Sessions.erase(port); - } - } - - UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) - { - auto ih = from.GetIdentHash(); - auto idx = GetSessionIndex (remotePort, localPort); - { - std::lock_guard lock(m_SessionsMutex); - auto it = m_Sessions.find (idx); - if (it != m_Sessions.end ()) - { - if (it->second->Identity.GetLL()[0] == ih.GetLL()[0]) - { - LogPrint(eLogDebug, "UDPServer: Found session ", it->second->IPSocket.local_endpoint(), " ", ih.ToBase32()); - return it->second; - } - else - { - LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from different address. Removed"); - m_Sessions.erase (it); - } - } - } - - boost::asio::ip::address addr; - /** create new udp session */ - if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) - { - auto ident = from.GetIdentHash(); - addr = GetLoopbackAddressFor(ident); - } - else - addr = m_LocalAddress; - - auto s = std::make_shared(boost::asio::ip::udp::endpoint(addr, 0), - m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort); - std::lock_guard lock(m_SessionsMutex); - m_Sessions.emplace (idx, s); - return s; - } - - UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - const boost::asio::ip::udp::endpoint& endpoint, const i2p::data::IdentHash& to, - uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination->GetDatagramDestination()), - IPSocket(localDestination->GetService(), localEndpoint), - Identity (to), SendEndpoint(endpoint), - LastActivity(i2p::util::GetMillisecondsSinceEpoch()), - LocalPort(ourPort), - RemotePort(theirPort) - { - IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); - Receive(); - } - - void UDPSession::Receive() - { - LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), - FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); - } - - void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) - { - if(!ecode) - { - LogPrint(eLogDebug, "UDPSession: Forward ", len, "B from ", FromEndpoint); - auto ts = i2p::util::GetMillisecondsSinceEpoch(); - auto session = m_Destination->GetSession (Identity); - if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); - else - m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = IPSocket.available(ec); - if (ec || !moreBytes) break; - len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); - m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); - numPackets++; - } - if (numPackets > 0) - LogPrint(eLogDebug, "UDPSession: Forward more ", numPackets, "packets B from ", FromEndpoint); - m_Destination->FlushSendQueue (session); - LastActivity = ts; - Receive(); - } - else - LogPrint(eLogError, "UDPSession: ", ecode.message()); - } - - I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr localDestination, - const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t inPort, bool gzip) : - m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), - m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip) - { - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel () - { - Stop (); - } - - void I2PUDPServerTunnel::Start () - { - m_LocalDest->Start (); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver ( - std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - m_inPort - ); - dgram->SetRawReceiver ( - std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - m_inPort - ); - } - - void I2PUDPServerTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) { - dgram->ResetReceiver (m_inPort); - dgram->ResetRawReceiver (m_inPort); - } - } - - std::vector > I2PUDPServerTunnel::GetSessions () - { - std::vector > sessions; - std::lock_guard lock (m_SessionsMutex); - - for (const auto &it: m_Sessions) - { - auto s = it.second; - if (!s->m_Destination) continue; - auto info = s->m_Destination->GetInfoForRemote (s->Identity); - if (!info) continue; - - auto sinfo = std::make_shared (); - sinfo->Name = m_Name; - sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); - sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); - sinfo->CurrentIBGW = info->IBGW; - sinfo->CurrentOBEP = info->OBEP; - sessions.push_back (sinfo); - } - return sessions; - } - - I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - const boost::asio::ip::udp::endpoint& localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort, bool gzip) : - m_Name (name), m_RemoteDest (remoteDest), m_LocalDest (localDestination), m_LocalEndpoint (localEndpoint), - m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), - m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip) - { - } - - I2PUDPClientTunnel::~I2PUDPClientTunnel () - { - Stop (); - } - - void I2PUDPClientTunnel::Start () - { - // Reset flag in case of tunnel reload - if (m_cancel_resolve) m_cancel_resolve = false; - - m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); - m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); - m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5), - RemotePort - ); - dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - RemotePort - ); - - m_LocalDest->Start (); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); - RecvFromLocal (); - } - - void I2PUDPClientTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) { - dgram->ResetReceiver (RemotePort); - dgram->ResetRawReceiver (RemotePort); - } - m_cancel_resolve = true; - - m_Sessions.clear(); - - if(m_LocalSocket && m_LocalSocket->is_open ()) - m_LocalSocket->close (); - - if(m_ResolveThread) - { - m_ResolveThread->join (); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - m_RemoteAddr = nullptr; - } - - void I2PUDPClientTunnel::RecvFromLocal () - { - m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), - m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); - } - - void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) - { - if (m_cancel_resolve) { - LogPrint (eLogDebug, "UDP Client: Ignoring incoming data: stopping"); - return; - } - if (ec) { - LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); - RecvFromLocal (); // Restart listener and continue work - return; - } - if (!m_RemoteAddr || !m_RemoteAddr->IsIdentHash ()) // TODO: handle B33 - { - LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); - RecvFromLocal (); - return; // drop, remote not resolved - } - auto remotePort = m_RecvEndpoint.port (); - if (!m_LastPort || m_LastPort != remotePort) - { - auto itr = m_Sessions.find (remotePort); - if (itr != m_Sessions.end ()) - m_LastSession = itr->second; - else - { - m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); - m_Sessions.emplace (remotePort, m_LastSession); - } - m_LastPort = remotePort; - } - // send off to remote i2p destination - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteAddr->identHash.ToBase32 (), ":", RemotePort); - auto session = m_LocalDest->GetDatagramDestination ()->GetSession (m_RemoteAddr->identHash); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - else - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = m_LocalSocket->available (ec); - if (ec || !moreBytes) break; - transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); - remotePort = m_RecvEndpoint.port (); - // TODO: check remotePort - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - numPackets++; - } - if (numPackets) - LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteAddr->identHash.ToBase32 ()); - m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); - - // mark convo as active - if (m_LastSession) - m_LastSession->second = ts; - RecvFromLocal (); - } - - std::vector > I2PUDPClientTunnel::GetSessions () - { - // TODO: implement - std::vector > infos; - return infos; - } - - void I2PUDPClientTunnel::TryResolving () - { - i2p::util::SetThreadName ("UDP Resolver"); - LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - - while (!(m_RemoteAddr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) - { - LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for (std::chrono::seconds (1)); - } - if (m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); - return; - } - if (!m_RemoteAddr) - { - LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); - return; - } - LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteAddr->identHash.ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (m_RemoteAddr && from.GetIdentHash() == m_RemoteAddr->identHash) - HandleRecvFromI2PRaw (fromPort, toPort, buf, len); - else - LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - auto itr = m_Sessions.find (toPort); - // found convo ? - if (itr != m_Sessions.end ()) - { - // found convo - if (len > 0) - { - LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteAddr ? m_RemoteAddr->identHash.ToBase32 () : ""); - m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); - // mark convo as active - itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); - } - } - else - LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); - } - -} -} diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h deleted file mode 100644 index 5650124c..00000000 --- a/libi2pd_client/UDPTunnel.h +++ /dev/null @@ -1,189 +0,0 @@ -/* -* Copyright (c) 2013-2024, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef UDPTUNNEL_H__ -#define UDPTUNNEL_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "Identity.h" -#include "Destination.h" -#include "Datagram.h" -#include "AddressBook.h" - -namespace i2p -{ -namespace client -{ - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds - - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 64*1024; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; - - uint16_t LocalPort; - uint16_t RemotePort; - - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - const boost::asio::ip::udp::endpoint& remote, const i2p::data::IdentHash& ident, - uint16_t ourPort, uint16_t theirPort); - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; - - - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - std::string Name; - /** ident hash of local destination */ - std::shared_ptr LocalIdent; - /** ident hash of remote destination */ - std::shared_ptr RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this conversation been idle in ms */ - uint64_t idle; - }; - - typedef std::shared_ptr UDPSessionPtr; - - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - - I2PUDPServerTunnel (const std::string & name, - std::shared_ptr localDestination, - const boost::asio::ip::address& localAddress, - const boost::asio::ip::udp::endpoint& forwardTo, uint16_t port, bool gzip); - ~I2PUDPServerTunnel (); - - /** expire stale udp conversations */ - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - - void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } - - private: - - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - uint32_t GetSessionIndex (uint16_t fromPort, uint16_t toPort) const { return ((uint32_t)fromPort << 16) + toPort; } - - private: - - bool m_IsUniqueLocal; - const std::string m_Name; - boost::asio::ip::address m_LocalAddress; - boost::asio::ip::udp::endpoint m_RemoteEndpoint; - std::mutex m_SessionsMutex; - std::unordered_map m_Sessions; // (from port, to port)->session - std::shared_ptr m_LocalDest; - UDPSessionPtr m_LastSession; - uint16_t m_inPort; - bool m_Gzip; - - public: - - bool isUpdated; // transient, used during reload only - }; - - class I2PUDPClientTunnel - { - public: - - I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - const boost::asio::ip::udp::endpoint& localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort, bool gzip); - ~I2PUDPClientTunnel (); - - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - - bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } - - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - inline void SetLocalDestination (std::shared_ptr dest) - { - if (m_LocalDest) m_LocalDest->Release (); - if (dest) dest->Acquire (); - m_LocalDest = dest; - } - - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - - private: - - typedef std::pair UDPConvo; - void RecvFromLocal (); - void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving (); - - private: - - const std::string m_Name; - std::mutex m_SessionsMutex; - std::unordered_map > m_Sessions; // maps i2p port -> local udp convo - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - std::shared_ptr m_RemoteAddr; - std::thread * m_ResolveThread; - std::unique_ptr m_LocalSocket; - boost::asio::ip::udp::endpoint m_RecvEndpoint; - uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort, m_LastPort; - bool m_cancel_resolve; - bool m_Gzip; - std::shared_ptr m_LastSession; - - public: - - bool isUpdated; // transient, used during reload only - }; - - -} -} - -#endif diff --git a/libi2pd_client/WebSocks.cpp b/libi2pd_client/WebSocks.cpp new file mode 100644 index 00000000..ee64001d --- /dev/null +++ b/libi2pd_client/WebSocks.cpp @@ -0,0 +1,556 @@ +#include "WebSocks.h" +#include "Log.h" +#include + +#ifdef WITH_EVENTS +#include "ClientContext.h" +#include "Identity.h" +#include "Destination.h" +#include "Streaming.h" +#include + +#include +#include + +#include +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) +#if !GCC47_BOOST149 +#include +#endif + +namespace i2p +{ +namespace client +{ + typedef websocketpp::server WebSocksServerImpl; + + typedef std::function)> StreamConnectFunc; + + + struct IWebSocksConn : public I2PServiceHandler + { + IWebSocksConn(I2PService * parent) : I2PServiceHandler(parent) {} + virtual void Close() = 0; + virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) = 0; + }; + + typedef std::shared_ptr WebSocksConn_ptr; + + WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent); + + class WebSocksImpl + { + + typedef std::mutex mutex_t; + typedef std::unique_lock lock_t; + + typedef std::shared_ptr Destination_t; + public: + + typedef WebSocksServerImpl ServerImpl; + typedef ServerImpl::message_ptr MessagePtr; + + WebSocksImpl(const std::string & addr, int port) : + Parent(nullptr), + m_Run(false), + m_Addr(addr), + m_Port(port), + m_Thread(nullptr) + { + m_Server.init_asio(); + m_Server.set_open_handler(std::bind(&WebSocksImpl::ConnOpened, this, std::placeholders::_1)); + } + + void InitializeDestination(WebSocks * parent) + { + Parent = parent; + m_Dest = Parent->GetLocalDestination(); + } + + ServerImpl::connection_ptr GetConn(const websocketpp::connection_hdl & conn) + { + return m_Server.get_con_from_hdl(conn); + } + + void CloseConn(const websocketpp::connection_hdl & conn) + { + auto c = GetConn(conn); + if(c) c->close(websocketpp::close::status::normal, "closed"); + } + + void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete) + { + auto & addressbook = i2p::client::context.GetAddressBook(); + i2p::data::IdentHash ident; + if(addressbook.GetIdentHash(addr, ident)) { + // address found + m_Dest->CreateStream(complete, ident, port); + } else { + // not found + complete(nullptr); + } + } + + void ConnOpened(websocketpp::connection_hdl conn) + { + auto ptr = CreateWebSocksConn(conn, this); + Parent->AddHandler(ptr); + m_Conns.push_back(ptr); + } + + void Start() + { + if(m_Run) return; // already started + m_Server.listen(boost::asio::ip::address::from_string(m_Addr), m_Port); + m_Server.start_accept(); + m_Run = true; + m_Thread = new std::thread([&] (){ + while(m_Run) { + try { + m_Server.run(); + } catch( std::exception & ex) { + LogPrint(eLogError, "Websocks runtime exception: ", ex.what()); + } + } + }); + m_Dest->Start(); + } + + void Stop() + { + for(const auto & conn : m_Conns) + conn->Close(); + + m_Dest->Stop(); + m_Run = false; + m_Server.stop(); + if(m_Thread) { + m_Thread->join(); + delete m_Thread; + } + m_Thread = nullptr; + } + + boost::asio::ip::tcp::endpoint GetLocalEndpoint() + { + return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port); + } + + i2p::datagram::DatagramDestination * GetDatagramDest() const + { + auto dgram = m_Dest->GetDatagramDestination(); + if(!dgram) dgram = m_Dest->CreateDatagramDestination(); + return dgram; + } + + WebSocks * Parent; + + private: + std::vector m_Conns; + bool m_Run; + ServerImpl m_Server; + std::string m_Addr; + int m_Port; + std::thread * m_Thread; + Destination_t m_Dest; + }; + + struct WebSocksConn : public IWebSocksConn , public std::enable_shared_from_this + { + enum ConnState + { + eWSCInitial, + eWSCTryConnect, + eWSCFailConnect, + eWSCOkayConnect, + eWSCDatagram, + eWSCClose, + eWSCEnd + }; + + typedef WebSocksServerImpl ServerImpl; + typedef ServerImpl::message_ptr Message_t; + typedef websocketpp::connection_hdl ServerConn; + typedef std::shared_ptr Destination_t; + typedef std::shared_ptr StreamDest_t; + typedef std::shared_ptr Stream_t; + + ServerConn m_Conn; + Stream_t m_Stream; + ConnState m_State; + WebSocksImpl * m_Parent; + std::string m_RemoteAddr; + int m_RemotePort; + uint8_t m_RecvBuf[2048]; + bool m_IsDatagram; + i2p::datagram::DatagramDestination * m_Datagram; + + WebSocksConn(const ServerConn & conn, WebSocksImpl * parent) : + IWebSocksConn(parent->Parent), + m_Conn(conn), + m_Stream(nullptr), + m_State(eWSCInitial), + m_Parent(parent), + m_IsDatagram(false), + m_Datagram(nullptr) + { + + } + + ~WebSocksConn() + { + Close(); + } + + void HandleDatagram(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + auto conn = m_Parent->GetConn(m_Conn); + if(conn) + { + std::stringstream ss; + ss << from.GetIdentHash().ToBase32(); + ss << ".b32.i2p:"; + ss << std::to_string(fromPort); + ss << "\n"; + ss.write((char *)buf, len); + conn->send(ss.str()); + } + } + + void BeginDatagram() + { + m_Datagram = m_Parent->GetDatagramDest(); + m_Datagram->SetReceiver( + std::bind( + &WebSocksConn::HandleDatagram, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4, + std::placeholders::_5), m_RemotePort); + } + + void EnterState(ConnState state) + { + LogPrint(eLogDebug, "websocks: state ", m_State, " -> ", state); + switch(m_State) + { + case eWSCInitial: + if (state == eWSCClose) { + m_State = eWSCClose; + // connection was opened but never used + LogPrint(eLogInfo, "websocks: connection closed but never used"); + Close(); + return; + } else if (state == eWSCTryConnect) { + // we will try to connect + m_State = eWSCTryConnect; + m_Parent->CreateStreamTo(m_RemoteAddr, m_RemotePort, std::bind(&WebSocksConn::ConnectResult, this, std::placeholders::_1)); + } else if (state == eWSCDatagram) { + if (m_RemotePort >= 0 && m_RemotePort <= 65535) + { + LogPrint(eLogDebug, "websocks: datagram mode initiated"); + m_State = eWSCDatagram; + BeginDatagram(); + SendResponse(""); + } + else + SendResponse("invalid port"); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCTryConnect: + if(state == eWSCOkayConnect) { + // we connected okay + LogPrint(eLogDebug, "websocks: connected to ", m_RemoteAddr, ":", m_RemotePort); + SendResponse(""); + m_State = eWSCOkayConnect; + } else if(state == eWSCFailConnect) { + // we did not connect okay + LogPrint(eLogDebug, "websocks: failed to connect to ", m_RemoteAddr, ":", m_RemotePort); + SendResponse("failed to connect"); + m_State = eWSCFailConnect; + EnterState(eWSCInitial); + } else if(state == eWSCClose) { + // premature close + LogPrint(eLogWarning, "websocks: websocket connection closed prematurely"); + m_State = eWSCClose; + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCFailConnect: + if (state == eWSCInitial) { + // reset to initial state so we can try connecting again + m_RemoteAddr = ""; + m_RemotePort = 0; + LogPrint(eLogDebug, "websocks: reset websocket conn to initial state"); + m_State = eWSCInitial; + } else if (state == eWSCClose) { + // we are going to close the connection + m_State = eWSCClose; + Close(); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCDatagram: + if(state != eWSCClose) { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + m_State = eWSCClose; + Close(); + return; + case eWSCOkayConnect: + if(state == eWSCClose) { + // graceful close + m_State = eWSCClose; + Close(); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCClose: + if(state == eWSCEnd) { + LogPrint(eLogDebug, "websocks: socket ended"); + Kill(); + auto me = shared_from_this(); + Done(me); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + default: + LogPrint(eLogError, "websocks: bad state ", m_State); + } + } + + void StartForwarding() + { + LogPrint(eLogDebug, "websocks: begin forwarding data"); + uint8_t b[1]; + m_Stream->Send(b, 0); + AsyncRecv(); + } + + void HandleAsyncRecv(const boost::system::error_code &ec, std::size_t n) + { + if(ec) { + // error + LogPrint(eLogWarning, "websocks: connection error ", ec.message()); + EnterState(eWSCClose); + } else { + // forward data + LogPrint(eLogDebug, "websocks recv ", n); + + std::string str((char*)m_RecvBuf, n); + auto conn = m_Parent->GetConn(m_Conn); + if(!conn) { + LogPrint(eLogWarning, "websocks: connection is gone"); + EnterState(eWSCClose); + return; + } + conn->send(str); + AsyncRecv(); + + } + } + + void AsyncRecv() + { + m_Stream->AsyncReceive( + boost::asio::buffer(m_RecvBuf, sizeof(m_RecvBuf)), + std::bind(&WebSocksConn::HandleAsyncRecv, this, std::placeholders::_1, std::placeholders::_2), 60); + } + + /** @brief send error message or empty string for success */ + void SendResponse(const std::string & errormsg) + { + boost::property_tree::ptree resp; + if(errormsg.size()) { + resp.put("error", errormsg); + resp.put("success", 0); + } else { + resp.put("success", 1); + } + std::ostringstream ss; + write_json(ss, resp); + auto conn = m_Parent->GetConn(m_Conn); + if(conn) conn->send(ss.str()); + } + + void ConnectResult(Stream_t stream) + { + m_Stream = stream; + if(m_State == eWSCClose) { + // premature close of websocket + Close(); + return; + } + if(m_Stream) { + // connect good + EnterState(eWSCOkayConnect); + StartForwarding(); + } else { + // connect failed + EnterState(eWSCFailConnect); + } + } + + virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) + { + (void) conn; + std::string payload = msg->get_payload(); + if(m_State == eWSCOkayConnect) + { + // forward to server + LogPrint(eLogDebug, "websocks: forward ", payload.size()); + m_Stream->Send((uint8_t*)payload.c_str(), payload.size()); + } else if (m_State == eWSCInitial) { + // recv connect request + auto itr = payload.find(":"); + if(itr == std::string::npos) { + // no port + m_RemotePort = 0; + m_RemoteAddr = payload; + } else { + // includes port + m_RemotePort = std::stoi(payload.substr(itr+1)); + m_RemoteAddr = payload.substr(0, itr); + } + m_IsDatagram = m_RemoteAddr == "DATAGRAM"; + if(m_IsDatagram) + EnterState(eWSCDatagram); + else + EnterState(eWSCTryConnect); + } else if (m_State == eWSCDatagram) { + // send datagram + // format is "host:port\npayload" + auto idx = payload.find("\n"); + std::string line = payload.substr(0, idx); + auto itr = line.find(":"); + auto & addressbook = i2p::client::context.GetAddressBook(); + std::string addr; + int port = 0; + if (itr == std::string::npos) + { + addr = line; + } + else + { + addr = line.substr(0, itr); + port = std::atoi(line.substr(itr+1).c_str()); + } + i2p::data::IdentHash ident; + if(addressbook.GetIdentHash(addr, ident)) + { + const char * data = payload.c_str() + idx + 1; + size_t len = payload.size() - (1 + line.size()); + m_Datagram->SendDatagramTo((const uint8_t*)data, len, ident, m_RemotePort, port); + } + } else { + // wtf? + LogPrint(eLogWarning, "websocks: got message in invalid state ", m_State); + } + } + + virtual void Close() + { + if(m_State == eWSCClose) { + LogPrint(eLogDebug, "websocks: closing connection"); + if(m_Stream) m_Stream->Close(); + if(m_Datagram) m_Datagram->ResetReceiver(m_RemotePort); + m_Parent->CloseConn(m_Conn); + EnterState(eWSCEnd); + } else { + EnterState(eWSCClose); + } + } + }; + + WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent) + { + auto ptr = std::make_shared(conn, parent); + auto c = parent->GetConn(conn); + c->set_message_handler(std::bind(&WebSocksConn::GotMessage, ptr.get(), std::placeholders::_1, std::placeholders::_2)); + return ptr; + } + +} +} +#else + +// no websocket support + +namespace i2p +{ +namespace client +{ + class WebSocksImpl + { + public: + WebSocksImpl(const std::string & addr, int port) : m_Addr(addr), m_Port(port) + { + } + + ~WebSocksImpl() + { + } + + void Start() + { + LogPrint(eLogInfo, "WebSockets not enabled on compile time"); + } + + void Stop() + { + } + + void InitializeDestination(WebSocks * parent) + { + } + + boost::asio::ip::tcp::endpoint GetLocalEndpoint() + { + return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port); + } + + std::string m_Addr; + int m_Port; + + }; +} +} + +#endif +namespace i2p +{ +namespace client +{ + WebSocks::WebSocks(const std::string & addr, int port, std::shared_ptr localDestination) : m_Impl(new WebSocksImpl(addr, port)) + { + m_Impl->InitializeDestination(this); + } + WebSocks::~WebSocks() { delete m_Impl; } + + void WebSocks::Start() + { + m_Impl->Start(); + GetLocalDestination()->Start(); + } + + boost::asio::ip::tcp::endpoint WebSocks::GetLocalEndpoint() const + { + return m_Impl->GetLocalEndpoint(); + } + + void WebSocks::Stop() + { + m_Impl->Stop(); + GetLocalDestination()->Stop(); + } +} +} + diff --git a/libi2pd_client/WebSocks.h b/libi2pd_client/WebSocks.h new file mode 100644 index 00000000..2314659f --- /dev/null +++ b/libi2pd_client/WebSocks.h @@ -0,0 +1,34 @@ +#ifndef WEBSOCKS_H_ +#define WEBSOCKS_H_ +#include +#include +#include "I2PService.h" +#include "Destination.h" + +namespace i2p +{ +namespace client +{ + + class WebSocksImpl; + + /** @brief websocket socks proxy server */ + class WebSocks : public i2p::client::I2PService + { + public: + WebSocks(const std::string & addr, int port, std::shared_ptr localDestination); + ~WebSocks(); + + void Start(); + void Stop(); + + boost::asio::ip::tcp::endpoint GetLocalEndpoint() const; + + const char * GetName() { return "WebSOCKS Proxy"; } + + private: + WebSocksImpl * m_Impl; + }; +} +} +#endif diff --git a/libi2pd_client/Websocket.cpp b/libi2pd_client/Websocket.cpp new file mode 100644 index 00000000..3d456655 --- /dev/null +++ b/libi2pd_client/Websocket.cpp @@ -0,0 +1,195 @@ +#ifdef WITH_EVENTS +#include "Websocket.h" +#include "Log.h" + +#include +#include + +#include +#include +#include +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) +#if !GCC47_BOOST149 +#include +#endif + +#include + +namespace i2p +{ + namespace event + { + + typedef websocketpp::server ServerImpl; + typedef websocketpp::connection_hdl ServerConn; + + class WebsocketServerImpl : public EventListener + { + private: + typedef ServerImpl::message_ptr MessagePtr; + public: + + WebsocketServerImpl(const std::string & addr, int port) : + m_run(false), + m_ws_thread(nullptr), + m_ev_thread(nullptr), + m_WebsocketTicker(m_Service) + { + m_server.init_asio(); + m_server.set_open_handler(std::bind(&WebsocketServerImpl::ConnOpened, this, std::placeholders::_1)); + m_server.set_close_handler(std::bind(&WebsocketServerImpl::ConnClosed, this, std::placeholders::_1)); + m_server.set_message_handler(std::bind(&WebsocketServerImpl::OnConnMessage, this, std::placeholders::_1, std::placeholders::_2)); + + m_server.listen(boost::asio::ip::address::from_string(addr), port); + } + + ~WebsocketServerImpl() + { + } + + void Start() { + m_run = true; + m_server.start_accept(); + m_ws_thread = new std::thread([&] () { + while(m_run) { + try { + m_server.run(); + } catch (std::exception & e ) { + LogPrint(eLogError, "Websocket server: ", e.what()); + } + } + }); + m_ev_thread = new std::thread([&] () { + while(m_run) { + try { + m_Service.run(); + break; + } catch (std::exception & e ) { + LogPrint(eLogError, "Websocket service: ", e.what()); + } + } + }); + ScheduleTick(); + } + + void Stop() { + m_run = false; + m_Service.stop(); + m_server.stop(); + + if(m_ev_thread) { + m_ev_thread->join(); + delete m_ev_thread; + } + m_ev_thread = nullptr; + + if(m_ws_thread) { + m_ws_thread->join(); + delete m_ws_thread; + } + m_ws_thread = nullptr; + } + + void ConnOpened(ServerConn c) + { + std::lock_guard lock(m_connsMutex); + m_conns.insert(c); + } + + void ConnClosed(ServerConn c) + { + std::lock_guard lock(m_connsMutex); + m_conns.erase(c); + } + + void OnConnMessage(ServerConn conn, ServerImpl::message_ptr msg) + { + (void) conn; + (void) msg; + } + + void HandleTick(const boost::system::error_code & ec) + { + + if(ec != boost::asio::error::operation_aborted) + LogPrint(eLogError, "Websocket ticker: ", ec.message()); + // pump collected events to us + i2p::event::core.PumpCollected(this); + ScheduleTick(); + } + + void ScheduleTick() + { + LogPrint(eLogDebug, "Websocket schedule tick"); + boost::posix_time::seconds dlt(1); + m_WebsocketTicker.expires_from_now(dlt); + m_WebsocketTicker.async_wait(std::bind(&WebsocketServerImpl::HandleTick, this, std::placeholders::_1)); + } + + /** @brief called from m_ev_thread */ + void HandlePumpEvent(const EventType & ev, const uint64_t & val) + { + EventType e; + for (const auto & i : ev) + e[i.first] = i.second; + + e["number"] = std::to_string(val); + HandleEvent(e); + } + + /** @brief called from m_ws_thread */ + void HandleEvent(const EventType & ev) + { + std::lock_guard lock(m_connsMutex); + boost::property_tree::ptree event; + for (const auto & item : ev) { + event.put(item.first, item.second); + } + std::ostringstream ss; + write_json(ss, event); + std::string s = ss.str(); + + ConnList::iterator it; + for (it = m_conns.begin(); it != m_conns.end(); ++it) { + ServerImpl::connection_ptr con = m_server.get_con_from_hdl(*it); + con->send(s); + } + } + + private: + typedef std::set > ConnList; + bool m_run; + std::thread * m_ws_thread; + std::thread * m_ev_thread; + std::mutex m_connsMutex; + ConnList m_conns; + ServerImpl m_server; + boost::asio::io_service m_Service; + boost::asio::deadline_timer m_WebsocketTicker; + }; + + + WebsocketServer::WebsocketServer(const std::string & addr, int port) : m_impl(new WebsocketServerImpl(addr, port)) {} + WebsocketServer::~WebsocketServer() + { + delete m_impl; + } + + + void WebsocketServer::Start() + { + m_impl->Start(); + } + + void WebsocketServer::Stop() + { + m_impl->Stop(); + } + + EventListener * WebsocketServer::ToListener() + { + return m_impl; + } + } +} +#endif diff --git a/libi2pd_client/Websocket.h b/libi2pd_client/Websocket.h new file mode 100644 index 00000000..3a754e49 --- /dev/null +++ b/libi2pd_client/Websocket.h @@ -0,0 +1,28 @@ +#ifndef WEBSOCKET_H__ +#define WEBSOCKET_H__ +#include "Event.h" +namespace i2p +{ + namespace event + { + + class WebsocketServerImpl; + + class WebsocketServer + { + public: + WebsocketServer(const std::string & addr, int port); + ~WebsocketServer(); + + void Start(); + void Stop(); + + EventListener * ToListener(); + + private: + WebsocketServerImpl * m_impl; + }; + + } +} +#endif diff --git a/libi2pd_wrapper/api.go b/libi2pd_wrapper/api.go deleted file mode 100644 index 8c215f13..00000000 --- a/libi2pd_wrapper/api.go +++ /dev/null @@ -1,15 +0,0 @@ -package api - -/* -* 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 - */ - -/* -#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes -#cgo LDFLAGS: -L${SRCDIR}/ -l:../libi2pdwrapper.a -l:../libi2pd.a -l:../libi2pdlang.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ -*/ -import "C" diff --git a/libi2pd_wrapper/api.swigcxx b/libi2pd_wrapper/api.swigcxx deleted file mode 100644 index e1d18eef..00000000 --- a/libi2pd_wrapper/api.swigcxx +++ /dev/null @@ -1,8 +0,0 @@ -// See swig.org for more interface options, -// e.g. map std::string to Go string - -%{ -#include "capi.h" -%} - -%include "capi.h" diff --git a/libi2pd_wrapper/capi.cpp b/libi2pd_wrapper/capi.cpp deleted file mode 100644 index af4765da..00000000 --- a/libi2pd_wrapper/capi.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* -* 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 -*/ - -#include "../libi2pd/api.h" -#include "capi.h" -#include -#include -#include -#include - - -#ifdef __cplusplus -extern "C" { -#endif - -void C_InitI2P (int argc, char *argv[], const char * appName) -{ - std::cout << argv; - return i2p::api::InitI2P(argc, argv, appName); -} - -void C_TerminateI2P () -{ - return i2p::api::TerminateI2P(); -} - -void C_StartI2P () -{ - std::shared_ptr logStream; - return i2p::api::StartI2P(logStream); -} - -void C_StopI2P () -{ - return i2p::api::StopI2P(); -} - -void C_RunPeerTest () -{ - return i2p::api::RunPeerTest(); -} - -#ifdef __cplusplus -} -#endif diff --git a/libi2pd_wrapper/capi.h b/libi2pd_wrapper/capi.h deleted file mode 100644 index aefd89f3..00000000 --- a/libi2pd_wrapper/capi.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -* 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 -*/ - -#ifndef CAPI_H__ -#define CAPI_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -// initialization start and stop -void C_InitI2P (int argc, char *argv[], const char * appName); -//void C_InitI2P (int argc, char** argv, const char * appName); -void C_TerminateI2P (); -void C_StartI2P (); -// write system log to logStream, if not specified to .log in application's folder -void C_StopI2P (); -void C_RunPeerTest (); // should be called after UPnP - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/qt/.gitignore b/qt/.gitignore new file mode 100644 index 00000000..a0155cb2 --- /dev/null +++ b/qt/.gitignore @@ -0,0 +1 @@ +/build*/ diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore new file mode 100644 index 00000000..3abca1bd --- /dev/null +++ b/qt/i2pd_qt/.gitignore @@ -0,0 +1,9 @@ +i2pd_qt.pro.user* +moc_* +ui_* +qrc_* +i2pd_qt +Makefile* +*.stash +object_script.* +i2pd_qt_plugin_import.cpp \ No newline at end of file diff --git a/qt/i2pd_qt/ClientTunnelPane.cpp b/qt/i2pd_qt/ClientTunnelPane.cpp new file mode 100644 index 00000000..256d0510 --- /dev/null +++ b/qt/i2pd_qt/ClientTunnelPane.cpp @@ -0,0 +1,203 @@ +#include "ClientTunnelPane.h" +#include "ClientContext.h" +#include "SignatureTypeComboboxFactory.h" +#include "QVBoxLayout" + +ClientTunnelPane::ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): + TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} + +void ClientTunnelPane::setGroupBoxTitle(const QString & title) { + clientTunnelNameGroupBox->setTitle(title); +} + +void ClientTunnelPane::deleteClientTunnelForm() { + TunnelPane::deleteTunnelForm(); + delete clientTunnelNameGroupBox; + clientTunnelNameGroupBox=nullptr; + + //gridLayoutWidget_2->deleteLater(); + //gridLayoutWidget_2=nullptr; +} + +int ClientTunnelPane::appendClientTunnelForm( + ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { + + ClientTunnelPane& ui = *this; + + clientTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); + clientTunnelNameGroupBox->setObjectName(QStringLiteral("clientTunnelNameGroupBox")); + + //tunnel + gridLayoutWidget_2 = new QWidget(clientTunnelNameGroupBox); + + QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); + tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); + tunnelTypeComboBox->addItem("Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT); + tunnelTypeComboBox->addItem("Socks", i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS); + tunnelTypeComboBox->addItem("Websocks", i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS); + tunnelTypeComboBox->addItem("HTTP Proxy", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY); + tunnelTypeComboBox->addItem("UDP Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT); + + int h=(7+4)*60; + gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); + clientTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); + + { + const QString& type = tunnelConfig->getType(); + int index=0; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + } + + setupTunnelPane(tunnelConfig, + clientTunnelNameGroupBox, + gridLayoutWidget_2, tunnelTypeComboBox, + tunnelsFormGridLayoutWidget, tunnelsRow, height, h); + //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, (7+5)*40+10)); + + /* + std::string destination; + */ + + //host + ui.horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.destinationLabel = new QLabel(gridLayoutWidget_2); + destinationLabel->setObjectName(QStringLiteral("destinationLabel")); + horizontalLayout_2->addWidget(destinationLabel); + ui.destinationLineEdit = new QLineEdit(gridLayoutWidget_2); + destinationLineEdit->setObjectName(QStringLiteral("destinationLineEdit")); + destinationLineEdit->setText(tunnelConfig->getdest().c_str()); + QObject::connect(destinationLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(destinationLineEdit); + ui.destinationHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(destinationHorizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + + /* + * int port; + */ + int gridIndex = 2; + { + int port = tunnelConfig->getport(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.portLabel = new QLabel(gridLayoutWidget_2); + portLabel->setObjectName(QStringLiteral("portLabel")); + horizontalLayout_2->addWidget(portLabel); + ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); + portLineEdit->setObjectName(QStringLiteral("portLineEdit")); + portLineEdit->setText(QString::number(port)); + portLineEdit->setMaximumWidth(80); + QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(portLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + /* + * std::string keys; +*/ + { + std::string keys = tunnelConfig->getkeys(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.keysLabel = new QLabel(gridLayoutWidget_2); + keysLabel->setObjectName(QStringLiteral("keysLabel")); + horizontalLayout_2->addWidget(keysLabel); + ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); + keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); + keysLineEdit->setText(keys.c_str()); + QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(keysLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + /* + * std::string address; + */ + { + std::string address = tunnelConfig->getaddress(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.addressLabel = new QLabel(gridLayoutWidget_2); + addressLabel->setObjectName(QStringLiteral("addressLabel")); + horizontalLayout_2->addWidget(addressLabel); + ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); + addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); + addressLineEdit->setText(address.c_str()); + QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(addressLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + + /* + int destinationPort; + i2p::data::SigningKeyType sigType; +*/ + { + int destinationPort = tunnelConfig->getdestinationPort(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.destinationPortLabel = new QLabel(gridLayoutWidget_2); + destinationPortLabel->setObjectName(QStringLiteral("destinationPortLabel")); + horizontalLayout_2->addWidget(destinationPortLabel); + ui.destinationPortLineEdit = new QLineEdit(gridLayoutWidget_2); + destinationPortLineEdit->setObjectName(QStringLiteral("destinationPortLineEdit")); + destinationPortLineEdit->setText(QString::number(destinationPort)); + destinationPortLineEdit->setMaximumWidth(80); + QObject::connect(destinationPortLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(destinationPortLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); + sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); + horizontalLayout_2->addWidget(sigTypeLabel); + ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); + sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); + QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(sigTypeComboBox); + QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); + horizontalLayout_2->addWidget(lockButton2); + widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); + appendControlsForI2CPParameters(i2cpParameters, gridIndex); + } + + retranslateClientTunnelForm(ui); + + tunnelGridLayout->invalidate(); + + return h; +} + +ServerTunnelPane* ClientTunnelPane::asServerTunnelPane(){return nullptr;} +ClientTunnelPane* ClientTunnelPane::asClientTunnelPane(){return this;} diff --git a/qt/i2pd_qt/ClientTunnelPane.h b/qt/i2pd_qt/ClientTunnelPane.h new file mode 100644 index 00000000..c2e076b7 --- /dev/null +++ b/qt/i2pd_qt/ClientTunnelPane.h @@ -0,0 +1,106 @@ +#ifndef CLIENTTUNNELPANE_H +#define CLIENTTUNNELPANE_H + +#include "QGridLayout" +#include "QVBoxLayout" + +#include "TunnelPane.h" + +class ClientTunnelConfig; + +class ServerTunnelPane; +class TunnelPane; + +class ClientTunnelPane : public TunnelPane { + Q_OBJECT +public: + ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); + virtual ~ClientTunnelPane(){} + virtual ServerTunnelPane* asServerTunnelPane(); + virtual ClientTunnelPane* asClientTunnelPane(); + int appendClientTunnelForm(ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, + int tunnelsRow, int height); + void deleteClientTunnelForm(); +private: + QGroupBox *clientTunnelNameGroupBox; + + //tunnel + QWidget *gridLayoutWidget_2; + + //destination + QHBoxLayout *horizontalLayout_2; + QLabel *destinationLabel; + QLineEdit *destinationLineEdit; + QSpacerItem *destinationHorizontalSpacer; + + //port + QLabel * portLabel; + QLineEdit * portLineEdit; + + //keys + QLabel * keysLabel; + QLineEdit * keysLineEdit; + + //address + QLabel * addressLabel; + QLineEdit * addressLineEdit; + + //destinationPort + QLabel * destinationPortLabel; + QLineEdit * destinationPortLineEdit; + + //sigType + QLabel * sigTypeLabel; + QComboBox * sigTypeComboBox; + +protected slots: + virtual void setGroupBoxTitle(const QString & title); + +private: + void retranslateClientTunnelForm(ClientTunnelPane& /*ui*/) { + typeLabel->setText(QApplication::translate("cltTunForm", "Client tunnel type:", 0)); + destinationLabel->setText(QApplication::translate("cltTunForm", "Destination:", 0)); + portLabel->setText(QApplication::translate("cltTunForm", "Port:", 0)); + keysLabel->setText(QApplication::translate("cltTunForm", "Keys:", 0)); + destinationPortLabel->setText(QApplication::translate("cltTunForm", "Destination port:", 0)); + addressLabel->setText(QApplication::translate("cltTunForm", "Address:", 0)); + sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); + } +protected: + virtual bool applyDataFromUIToTunnelConfig() { + QString cannotSaveSettings = QApplication::tr("Cannot save settings."); + bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); + if(!ok)return false; + ClientTunnelConfig* ctc=tunnelConfig->asClientTunnelConfig(); + assert(ctc!=nullptr); + + //destination + ctc->setdest(destinationLineEdit->text().toStdString()); + + auto portStr=portLineEdit->text(); + int portInt=portStr.toInt(&ok); + + if(!ok){ + highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); + return false; + } + ctc->setport(portInt); + + ctc->setkeys(keysLineEdit->text().toStdString()); + + ctc->setaddress(addressLineEdit->text().toStdString()); + + auto dportStr=destinationPortLineEdit->text(); + int dportInt=dportStr.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad destinationPort, must be int.")+" "+cannotSaveSettings,destinationPortLineEdit); + return false; + } + ctc->setdestinationPort(dportInt); + + ctc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); + return true; + } +}; + +#endif // CLIENTTUNNELPANE_H diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp new file mode 100644 index 00000000..f5e6d62b --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -0,0 +1,187 @@ +#include + +#include "DaemonQT.h" +#include "Daemon.h" +#include "mainwindow.h" + +#include "Log.h" + +#include +#include +#include +#include + +namespace i2p +{ +namespace qt +{ + Worker::Worker (DaemonQTImpl& daemon): + m_Daemon (daemon) + { + } + + void Worker::startDaemon() + { + qDebug("Performing daemon start..."); + //try{ + m_Daemon.start(); + qDebug("Daemon started."); + emit resultReady(false, ""); + /*}catch(std::exception ex){ + emit resultReady(true, ex.what()); + }catch(...){ + emit resultReady(true, QObject::tr("Error: unknown exception")); + }*/ + } + void Worker::restartDaemon() + { + qDebug("Performing daemon restart..."); + //try{ + m_Daemon.restart(); + qDebug("Daemon restarted."); + emit resultReady(false, ""); + /*}catch(std::exception ex){ + emit resultReady(true, ex.what()); + }catch(...){ + emit resultReady(true, QObject::tr("Error: unknown exception")); + }*/ + } + void Worker::stopDaemon() { + qDebug("Performing daemon stop..."); + //try{ + m_Daemon.stop(); + qDebug("Daemon stopped."); + emit resultReady(false, ""); + /*}catch(std::exception ex){ + emit resultReady(true, ex.what()); + }catch(...){ + emit resultReady(true, QObject::tr("Error: unknown exception")); + }*/ + } + + Controller::Controller(DaemonQTImpl& daemon): + m_Daemon (daemon) + { + Worker *worker = new Worker (m_Daemon); + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); + connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); + connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); + connect(worker, &Worker::resultReady, this, &Controller::handleResults); + workerThread.start(); + } + Controller::~Controller() + { + qDebug("Closing and waiting for daemon worker thread..."); + workerThread.quit(); + workerThread.wait(); + qDebug("Waiting for daemon worker thread finished."); + if(m_Daemon.isRunning()) + { + qDebug("Stopping the daemon..."); + m_Daemon.stop(); + qDebug("Stopped the daemon."); + } + } + + DaemonQTImpl::DaemonQTImpl (): + mutex(nullptr), m_IsRunning(nullptr), m_RunningChangedCallback(nullptr) + { + } + + DaemonQTImpl::~DaemonQTImpl () + { + delete mutex; + } + + bool DaemonQTImpl::init(int argc, char* argv[], std::shared_ptr logstream) + { + mutex=new QMutex(QMutex::Recursive); + setRunningCallback(0); + m_IsRunning=false; + return Daemon.init(argc,argv,logstream); + } + + void DaemonQTImpl::start() + { + QMutexLocker locker(mutex); + setRunning(true); + Daemon.start(); + } + + void DaemonQTImpl::stop() + { + QMutexLocker locker(mutex); + Daemon.stop(); + setRunning(false); + } + + void DaemonQTImpl::restart() + { + QMutexLocker locker(mutex); + stop(); + start(); + } + + void DaemonQTImpl::setRunningCallback(runningChangedCallback cb) + { + m_RunningChangedCallback = cb; + } + + bool DaemonQTImpl::isRunning() + { + return m_IsRunning; + } + + void DaemonQTImpl::setRunning(bool newValue) + { + bool oldValue = m_IsRunning; + if(oldValue!=newValue) + { + m_IsRunning = newValue; + if(m_RunningChangedCallback) + m_RunningChangedCallback(); + } + } + + int RunQT (int argc, char* argv[]) + { + QApplication app(argc, argv); + int result; + + { + std::shared_ptr logstreamptr=std::make_shared(); + //TODO move daemon init deinit to a bg thread + DaemonQTImpl daemon; + (*logstreamptr) << "Initialising the daemon..." << std::endl; + bool daemonInitSuccess = daemon.init(argc, argv, logstreamptr); + if(!daemonInitSuccess) + { + QMessageBox::critical(0, "Error", "Daemon init failed"); + return 1; + } + LogPrint(eLogDebug, "Initialised, creating the main window..."); + MainWindow w(logstreamptr); + LogPrint(eLogDebug, "Before main window.show()..."); + w.show (); + + { + i2p::qt::Controller daemonQtController(daemon); + w.setI2PController(&daemonQtController); + LogPrint(eLogDebug, "Starting the daemon..."); + emit daemonQtController.startDaemon(); + //daemon.start (); + LogPrint(eLogDebug, "Starting GUI event loop..."); + result = app.exec(); + //daemon.stop (); + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + LogPrint(eLogDebug, "Exiting the application"); + return result; + } +} +} + diff --git a/qt/i2pd_qt/DaemonQT.h b/qt/i2pd_qt/DaemonQT.h new file mode 100644 index 00000000..aa329f56 --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.h @@ -0,0 +1,88 @@ +#ifndef DAEMONQT_H +#define DAEMONQT_H + +#include +#include +#include +#include +#include + +namespace i2p +{ +namespace qt +{ + class DaemonQTImpl + { + public: + + DaemonQTImpl (); + ~DaemonQTImpl (); + + typedef void (*runningChangedCallback)(); + + /** + * @brief init + * @param argc + * @param argv + * @return success + */ + bool init(int argc, char* argv[], std::shared_ptr logstream); + void start(); + void stop(); + void restart(); + void setRunningCallback(runningChangedCallback cb); + bool isRunning(); + private: + void setRunning(bool running); + void showError(std::string errorMsg); + private: + QMutex* mutex; + bool m_IsRunning; + runningChangedCallback m_RunningChangedCallback; + }; + + class Worker : public QObject + { + Q_OBJECT + public: + + Worker (DaemonQTImpl& daemon); + + private: + + DaemonQTImpl& m_Daemon; + + public slots: + void startDaemon(); + void restartDaemon(); + void stopDaemon(); + + signals: + void resultReady(bool failed, QString failureMessage); + }; + + class Controller : public QObject + { + Q_OBJECT + QThread workerThread; + public: + Controller(DaemonQTImpl& daemon); + ~Controller(); + private: + DaemonQTImpl& m_Daemon; + + public slots: + void handleResults(bool failed, QString failureMessage){ + if(failed){ + QMessageBox::critical(0, QObject::tr("Error"), failureMessage); + } + } + signals: + void startDaemon(); + void stopDaemon(); + void restartDaemon(); + }; +} +} + +#endif // DAEMONQT_H diff --git a/qt/i2pd_qt/MainWindowItems.cpp b/qt/i2pd_qt/MainWindowItems.cpp new file mode 100644 index 00000000..c1e1ab0a --- /dev/null +++ b/qt/i2pd_qt/MainWindowItems.cpp @@ -0,0 +1,2 @@ +#include "MainWindowItems.h" + diff --git a/qt/i2pd_qt/MainWindowItems.h b/qt/i2pd_qt/MainWindowItems.h new file mode 100644 index 00000000..a4be5fe0 --- /dev/null +++ b/qt/i2pd_qt/MainWindowItems.h @@ -0,0 +1,17 @@ +#ifndef MAINWINDOWITEMS_H +#define MAINWINDOWITEMS_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "mainwindow.h" + +class MainWindow; + +#endif // MAINWINDOWITEMS_H diff --git a/qt/i2pd_qt/README.md b/qt/i2pd_qt/README.md new file mode 100644 index 00000000..94186ecf --- /dev/null +++ b/qt/i2pd_qt/README.md @@ -0,0 +1,3 @@ +# Build Requirements + + * Qt 5 is necessary (because Qt4 lacks QtWidgets/ folder) diff --git a/qt/i2pd_qt/ServerTunnelPane.cpp b/qt/i2pd_qt/ServerTunnelPane.cpp new file mode 100644 index 00000000..bc6389a9 --- /dev/null +++ b/qt/i2pd_qt/ServerTunnelPane.cpp @@ -0,0 +1,261 @@ +#include "ServerTunnelPane.h" +#include "ClientContext.h" +#include "SignatureTypeComboboxFactory.h" + +ServerTunnelPane::ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): + TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} + +void ServerTunnelPane::setGroupBoxTitle(const QString & title) { + serverTunnelNameGroupBox->setTitle(title); +} + +int ServerTunnelPane::appendServerTunnelForm( + ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { + + ServerTunnelPane& ui = *this; + + serverTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); + serverTunnelNameGroupBox->setObjectName(QStringLiteral("serverTunnelNameGroupBox")); + + //tunnel + gridLayoutWidget_2 = new QWidget(serverTunnelNameGroupBox); + + QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); + tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); + tunnelTypeComboBox->addItem("Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER); + tunnelTypeComboBox->addItem("HTTP", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP); + tunnelTypeComboBox->addItem("IRC", i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC); + tunnelTypeComboBox->addItem("UDP Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER); + + int h=19*60; + gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); + serverTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); + + { + const QString& type = tunnelConfig->getType(); + int index=0; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER)tunnelTypeComboBox->setCurrentIndex(index); + ++index; + } + + setupTunnelPane(tunnelConfig, + serverTunnelNameGroupBox, + gridLayoutWidget_2, tunnelTypeComboBox, + tunnelsFormGridLayoutWidget, tunnelsRow, height, h); + //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, 18*40+10)); + + //host + ui.horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.hostLabel = new QLabel(gridLayoutWidget_2); + hostLabel->setObjectName(QStringLiteral("hostLabel")); + horizontalLayout_2->addWidget(hostLabel); + ui.hostLineEdit = new QLineEdit(gridLayoutWidget_2); + hostLineEdit->setObjectName(QStringLiteral("hostLineEdit")); + hostLineEdit->setText(tunnelConfig->gethost().c_str()); + QObject::connect(hostLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(hostLineEdit); + ui.hostHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(hostHorizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + + int gridIndex = 2; + { + int port = tunnelConfig->getport(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.portLabel = new QLabel(gridLayoutWidget_2); + portLabel->setObjectName(QStringLiteral("portLabel")); + horizontalLayout_2->addWidget(portLabel); + ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); + portLineEdit->setObjectName(QStringLiteral("portLineEdit")); + portLineEdit->setText(QString::number(port)); + portLineEdit->setMaximumWidth(80); + QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(portLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + std::string keys = tunnelConfig->getkeys(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.keysLabel = new QLabel(gridLayoutWidget_2); + keysLabel->setObjectName(QStringLiteral("keysLabel")); + horizontalLayout_2->addWidget(keysLabel); + ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); + keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); + keysLineEdit->setText(keys.c_str()); + QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(keysLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + int inPort = tunnelConfig->getinPort(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.inPortLabel = new QLabel(gridLayoutWidget_2); + inPortLabel->setObjectName(QStringLiteral("inPortLabel")); + horizontalLayout_2->addWidget(inPortLabel); + ui.inPortLineEdit = new QLineEdit(gridLayoutWidget_2); + inPortLineEdit->setObjectName(QStringLiteral("inPortLineEdit")); + inPortLineEdit->setText(QString::number(inPort)); + inPortLineEdit->setMaximumWidth(80); + QObject::connect(inPortLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(inPortLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + std::string accessList = tunnelConfig->getaccessList(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.accessListLabel = new QLabel(gridLayoutWidget_2); + accessListLabel->setObjectName(QStringLiteral("accessListLabel")); + horizontalLayout_2->addWidget(accessListLabel); + ui.accessListLineEdit = new QLineEdit(gridLayoutWidget_2); + accessListLineEdit->setObjectName(QStringLiteral("accessListLineEdit")); + accessListLineEdit->setText(accessList.c_str()); + QObject::connect(accessListLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(accessListLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + std::string hostOverride = tunnelConfig->gethostOverride(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.hostOverrideLabel = new QLabel(gridLayoutWidget_2); + hostOverrideLabel->setObjectName(QStringLiteral("hostOverrideLabel")); + horizontalLayout_2->addWidget(hostOverrideLabel); + ui.hostOverrideLineEdit = new QLineEdit(gridLayoutWidget_2); + hostOverrideLineEdit->setObjectName(QStringLiteral("hostOverrideLineEdit")); + hostOverrideLineEdit->setText(hostOverride.c_str()); + QObject::connect(hostOverrideLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(hostOverrideLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + std::string webIRCPass = tunnelConfig->getwebircpass(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.webIRCPassLabel = new QLabel(gridLayoutWidget_2); + webIRCPassLabel->setObjectName(QStringLiteral("webIRCPassLabel")); + horizontalLayout_2->addWidget(webIRCPassLabel); + ui.webIRCPassLineEdit = new QLineEdit(gridLayoutWidget_2); + webIRCPassLineEdit->setObjectName(QStringLiteral("webIRCPassLineEdit")); + webIRCPassLineEdit->setText(webIRCPass.c_str()); + QObject::connect(webIRCPassLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(webIRCPassLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + bool gzip = tunnelConfig->getgzip(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.gzipCheckBox = new QCheckBox(gridLayoutWidget_2); + gzipCheckBox->setObjectName(QStringLiteral("gzipCheckBox")); + gzipCheckBox->setChecked(gzip); + QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(gzipCheckBox); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); + sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); + horizontalLayout_2->addWidget(sigTypeLabel); + ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); + sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); + QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(sigTypeComboBox); + QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); + horizontalLayout_2->addWidget(lockButton2); + widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + std::string address = tunnelConfig->getaddress(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.addressLabel = new QLabel(gridLayoutWidget_2); + addressLabel->setObjectName(QStringLiteral("addressLabel")); + horizontalLayout_2->addWidget(addressLabel); + ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); + addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); + addressLineEdit->setText(address.c_str()); + QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(addressLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + bool isUniqueLocal = tunnelConfig->getisUniqueLocal(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.isUniqueLocalCheckBox = new QCheckBox(gridLayoutWidget_2); + isUniqueLocalCheckBox->setObjectName(QStringLiteral("isUniqueLocalCheckBox")); + isUniqueLocalCheckBox->setChecked(isUniqueLocal); + QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(isUniqueLocalCheckBox); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); + appendControlsForI2CPParameters(i2cpParameters, gridIndex); + } + + retranslateServerTunnelForm(ui); + + tunnelGridLayout->invalidate(); + + return h; +} + +void ServerTunnelPane::deleteServerTunnelForm() { + TunnelPane::deleteTunnelForm(); + delete serverTunnelNameGroupBox;//->deleteLater(); + serverTunnelNameGroupBox=nullptr; + + //gridLayoutWidget_2->deleteLater(); + //gridLayoutWidget_2=nullptr; +} + + +ServerTunnelPane* ServerTunnelPane::asServerTunnelPane(){return this;} +ClientTunnelPane* ServerTunnelPane::asClientTunnelPane(){return nullptr;} diff --git a/qt/i2pd_qt/ServerTunnelPane.h b/qt/i2pd_qt/ServerTunnelPane.h new file mode 100644 index 00000000..0a07267b --- /dev/null +++ b/qt/i2pd_qt/ServerTunnelPane.h @@ -0,0 +1,159 @@ +#ifndef SERVERTUNNELPANE_H +#define SERVERTUNNELPANE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "QVBoxLayout" +#include "QCheckBox" + +#include "assert.h" + +#include "TunnelPane.h" +#include "TunnelsPageUpdateListener.h" + +class ServerTunnelConfig; + +class ClientTunnelPane; + +class ServerTunnelPane : public TunnelPane { + Q_OBJECT + +public: + ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); + virtual ~ServerTunnelPane(){} + + virtual ServerTunnelPane* asServerTunnelPane(); + virtual ClientTunnelPane* asClientTunnelPane(); + + int appendServerTunnelForm(ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, + int tunnelsRow, int height); + void deleteServerTunnelForm(); + +private: + QGroupBox *serverTunnelNameGroupBox; + + //tunnel + QWidget *gridLayoutWidget_2; + + //host + QHBoxLayout *horizontalLayout_2; + QLabel *hostLabel; + QLineEdit *hostLineEdit; + QSpacerItem *hostHorizontalSpacer; + + //port + QLabel * portLabel; + QLineEdit * portLineEdit; + + //keys + QLabel * keysLabel; + QLineEdit * keysLineEdit; + + //inPort + QLabel * inPortLabel; + QLineEdit * inPortLineEdit; + + //accessList + QLabel * accessListLabel; + QLineEdit * accessListLineEdit; + + //hostOverride + QLabel * hostOverrideLabel; + QLineEdit * hostOverrideLineEdit; + + //webIRCPass + QLabel * webIRCPassLabel; + QLineEdit * webIRCPassLineEdit; + + //address + QLabel * addressLabel; + QLineEdit * addressLineEdit; + + //gzip + QCheckBox * gzipCheckBox; + + //isUniqueLocal + QCheckBox * isUniqueLocalCheckBox; + + //sigType + QLabel * sigTypeLabel; + QComboBox * sigTypeComboBox; + +protected slots: + virtual void setGroupBoxTitle(const QString & title); + +private: + void retranslateServerTunnelForm(ServerTunnelPane& /*ui*/) { + typeLabel->setText(QApplication::translate("srvTunForm", "Server tunnel type:", 0)); + hostLabel->setText(QApplication::translate("srvTunForm", "Host:", 0)); + portLabel->setText(QApplication::translate("srvTunForm", "Port:", 0)); + keysLabel->setText(QApplication::translate("srvTunForm", "Keys:", 0)); + inPortLabel->setText(QApplication::translate("srvTunForm", "InPort:", 0)); + accessListLabel->setText(QApplication::translate("srvTunForm", "Access list:", 0)); + hostOverrideLabel->setText(QApplication::translate("srvTunForm", "Host override:", 0)); + webIRCPassLabel->setText(QApplication::translate("srvTunForm", "WebIRC password:", 0)); + addressLabel->setText(QApplication::translate("srvTunForm", "Address:", 0)); + + gzipCheckBox->setText(QApplication::translate("srvTunForm", "GZip", 0)); + isUniqueLocalCheckBox->setText(QApplication::translate("srvTunForm", "Is unique local", 0)); + + sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); + } + +protected: + virtual bool applyDataFromUIToTunnelConfig() { + QString cannotSaveSettings = QApplication::tr("Cannot save settings."); + bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); + if(!ok)return false; + ServerTunnelConfig* stc=tunnelConfig->asServerTunnelConfig(); + assert(stc!=nullptr); + stc->sethost(hostLineEdit->text().toStdString()); + + auto portStr=portLineEdit->text(); + int portInt=portStr.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); + return false; + } + stc->setport(portInt); + + stc->setkeys(keysLineEdit->text().toStdString()); + + auto str=inPortLineEdit->text(); + int inPortInt=str.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad inPort, must be int.")+" "+cannotSaveSettings,inPortLineEdit); + return false; + } + stc->setinPort(inPortInt); + + stc->setaccessList(accessListLineEdit->text().toStdString()); + + stc->sethostOverride(hostOverrideLineEdit->text().toStdString()); + + stc->setwebircpass(webIRCPassLineEdit->text().toStdString()); + + stc->setaddress(addressLineEdit->text().toStdString()); + + stc->setgzip(gzipCheckBox->isChecked()); + + stc->setisUniqueLocal(isUniqueLocalCheckBox->isChecked()); + + stc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); + return true; + } +}; + +#endif // SERVERTUNNELPANE_H diff --git a/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp b/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp new file mode 100644 index 00000000..9313741a --- /dev/null +++ b/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp @@ -0,0 +1,2 @@ +#include "SignatureTypeComboboxFactory.h" + diff --git a/qt/i2pd_qt/SignatureTypeComboboxFactory.h b/qt/i2pd_qt/SignatureTypeComboboxFactory.h new file mode 100644 index 00000000..f7cac658 --- /dev/null +++ b/qt/i2pd_qt/SignatureTypeComboboxFactory.h @@ -0,0 +1,86 @@ +#ifndef SIGNATURETYPECOMBOBOXFACTORY_H +#define SIGNATURETYPECOMBOBOXFACTORY_H + +#include +#include +#include +#include "Identity.h" + +class SignatureTypeComboBoxFactory +{ + static const QVariant createUserData(const uint16_t sigType) { + return QVariant::fromValue((uint)sigType); + } + + static void addItem(QComboBox* signatureTypeCombobox, QString text, const uint16_t sigType) { + const QVariant userData = createUserData(sigType); + signatureTypeCombobox->addItem(text, userData); + } + +public: + static uint16_t getSigType(const QVariant& var) { + return (uint16_t)var.toInt(); + } + + static void fillComboBox(QComboBox* signatureTypeCombobox, uint16_t selectedSigType) { + /* + https://geti2p.net/spec/common-structures#certificate + все коды перечислены + это таблица "The defined Signing Public Key types are:" ? + да + + see also: Identity.h line 55 + */ + int index=0; + bool foundSelected=false; + + using namespace i2p::data; + + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "DSA_SHA1", 0), SIGNING_KEY_TYPE_DSA_SHA1); //0 + if(selectedSigType==SIGNING_KEY_TYPE_DSA_SHA1){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA256_P256", 0), SIGNING_KEY_TYPE_ECDSA_SHA256_P256); //1 + if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA256_P256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA384_P384", 0), SIGNING_KEY_TYPE_ECDSA_SHA384_P384); //2 + if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA384_P384){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA512_P521", 0), SIGNING_KEY_TYPE_ECDSA_SHA512_P521); //3 + if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA512_P521){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA256_2048", 0), SIGNING_KEY_TYPE_RSA_SHA256_2048); //4 + if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA256_2048){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA384_3072", 0), SIGNING_KEY_TYPE_RSA_SHA384_3072); //5 + if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA384_3072){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA512_4096", 0), SIGNING_KEY_TYPE_RSA_SHA512_4096); //6 + if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA512_4096){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); //7 + if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519PH", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph); //8 + if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + // the following signature type should never appear in netid=2 + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256", 0), SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256); //9 + if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_TC26_A_512_GOSTR3411_512", 0), SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512); //10 + if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} + ++index; + if(!foundSelected){ + addItem(signatureTypeCombobox, QString::number(selectedSigType), selectedSigType); //unknown sigtype + signatureTypeCombobox->setCurrentIndex(index); + } + } + + static QComboBox* createSignatureTypeComboBox(QWidget* parent, uint16_t selectedSigType) { + QComboBox* signatureTypeCombobox = new QComboBox(parent); + fillComboBox(signatureTypeCombobox, selectedSigType); + return signatureTypeCombobox; + } +}; + +#endif // SIGNATURETYPECOMBOBOXFACTORY_H diff --git a/qt/i2pd_qt/TunnelConfig.cpp b/qt/i2pd_qt/TunnelConfig.cpp new file mode 100644 index 00000000..8ed72930 --- /dev/null +++ b/qt/i2pd_qt/TunnelConfig.cpp @@ -0,0 +1,53 @@ +#include "TunnelConfig.h" + +void TunnelConfig::saveHeaderToStringStream(std::stringstream& out) { + out << "[" << name << "]\n" + << "type=" << type.toStdString() << "\n"; +} + +void TunnelConfig::saveI2CPParametersToStringStream(std::stringstream& out) { + if (i2cpParameters.getInbound_length().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNEL_LENGTH) + out << i2p::client::I2CP_PARAM_INBOUND_TUNNEL_LENGTH << "=" + << i2cpParameters.getInbound_length().toStdString() << "\n"; + if (i2cpParameters.getOutbound_length().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNEL_LENGTH) + out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH << "=" + << i2cpParameters.getOutbound_length().toStdString() << "\n"; + if (i2cpParameters.getInbound_quantity().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNELS_QUANTITY) + out << i2p::client::I2CP_PARAM_INBOUND_TUNNELS_QUANTITY << "=" + << i2cpParameters.getInbound_quantity().toStdString() << "\n"; + if (i2cpParameters.getOutbound_quantity().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNELS_QUANTITY) + out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY << "=" + << i2cpParameters.getOutbound_quantity().toStdString() << "\n"; + if (i2cpParameters.getCrypto_tagsToSend().toUShort() != i2p::client::DEFAULT_TAGS_TO_SEND) + out << i2p::client::I2CP_PARAM_TAGS_TO_SEND << "=" + << i2cpParameters.getCrypto_tagsToSend().toStdString() << "\n"; + if (!i2cpParameters.getExplicitPeers().isEmpty()) //todo #947 + out << i2p::client::I2CP_PARAM_EXPLICIT_PEERS << "=" + << i2cpParameters.getExplicitPeers().toStdString() << "\n"; + out << "\n"; +} + +void ClientTunnelConfig::saveToStringStream(std::stringstream& out) { + out << "address=" << address << "\n" + << "port=" << port << "\n" + << "destination=" << dest << "\n" + << "destinationport=" << destinationPort << "\n" + << "signaturetype=" << sigType << "\n"; + if(!keys.empty()) out << "keys=" << keys << "\n"; +} + + +void ServerTunnelConfig::saveToStringStream(std::stringstream& out) { + out << "host=" << host << "\n" + << "port=" << port << "\n" + << "signaturetype=" << sigType << "\n" + << "inport=" << inPort << "\n" + << "accesslist=" << accessList << "\n" + << "gzip=" << (gzip?"true":"false") << "\n" + << "enableuniquelocal=" << (isUniqueLocal?"true":"false") << "\n" + << "address=" << address << "\n" + << "hostoverride=" << hostOverride << "\n" + << "webircpassword=" << webircpass << "\n"; + if(!keys.empty()) out << "keys=" << keys << "\n"; +} + diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h new file mode 100644 index 00000000..58a1fa0b --- /dev/null +++ b/qt/i2pd_qt/TunnelConfig.h @@ -0,0 +1,226 @@ +#ifndef TUNNELCONFIG_H +#define TUNNELCONFIG_H + +#include "QString" +#include + +#include "../../libi2pd_client/ClientContext.h" +#include "../../libi2pd/Destination.h" +#include "TunnelsPageUpdateListener.h" + + +class I2CPParameters{ + QString inbound_length;//number of hops of an inbound tunnel. 3 by default; lower value is faster but dangerous + QString outbound_length;//number of hops of an outbound tunnel. 3 by default; lower value is faster but dangerous + QString inbound_quantity; //number of inbound tunnels. 5 by default + QString outbound_quantity; //number of outbound tunnels. 5 by default + QString crypto_tagsToSend; //number of ElGamal/AES tags to send. 40 by default; too low value may cause problems with tunnel building + QString explicitPeers; //list of comma-separated b64 addresses of peers to use, default: unset +public: + I2CPParameters(): inbound_length(), + outbound_length(), + inbound_quantity(), + outbound_quantity(), + crypto_tagsToSend(), + explicitPeers() {} + const QString& getInbound_length(){return inbound_length;} + const QString& getOutbound_length(){return outbound_length;} + const QString& getInbound_quantity(){return inbound_quantity;} + const QString& getOutbound_quantity(){return outbound_quantity;} + const QString& getCrypto_tagsToSend(){return crypto_tagsToSend;} + const QString& getExplicitPeers(){return explicitPeers;} + void setInbound_length(QString inbound_length_){inbound_length=inbound_length_;} + void setOutbound_length(QString outbound_length_){outbound_length=outbound_length_;} + void setInbound_quantity(QString inbound_quantity_){inbound_quantity=inbound_quantity_;} + void setOutbound_quantity(QString outbound_quantity_){outbound_quantity=outbound_quantity_;} + void setCrypto_tagsToSend(QString crypto_tagsToSend_){crypto_tagsToSend=crypto_tagsToSend_;} + void setExplicitPeers(QString explicitPeers_){explicitPeers=explicitPeers_;} +}; + + +class ClientTunnelConfig; +class ServerTunnelConfig; +class TunnelConfig { + /* + const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; + const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; + const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; + const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; + const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; + const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; + const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; + const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; + const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; + */ + QString type; + std::string name; +public: + TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_): + type(type_), name(name_), i2cpParameters(i2cpParameters_) {} + virtual ~TunnelConfig(){} + const QString& getType(){return type;} + const std::string& getName(){return name;} + void setType(const QString& type_){type=type_;} + void setName(const std::string& name_){name=name_;} + I2CPParameters& getI2cpParameters(){return i2cpParameters;} + void saveHeaderToStringStream(std::stringstream& out); + void saveI2CPParametersToStringStream(std::stringstream& out); + virtual void saveToStringStream(std::stringstream& out)=0; + virtual ClientTunnelConfig* asClientTunnelConfig()=0; + virtual ServerTunnelConfig* asServerTunnelConfig()=0; + +private: + I2CPParameters i2cpParameters; +}; + +/* +# mandatory parameters: + std::string dest; + if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) + dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); + int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); +# optional parameters (may be omitted) + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); + 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_ECDSA_SHA256_P256); +# * keys -- our identity, if unset, will be generated on every startup, +# if set and file missing, keys will be generated and placed to this file +# * address -- local interface to bind +# * signaturetype -- signature type for new destination. 0 (DSA/SHA1), 1 (EcDSA/SHA256) or 7 (EdDSA/SHA512) +[somelabel] +type = client +address = 127.0.0.1 +port = 6668 +destination = irc.postman.i2p +keys = irc-keys.dat +*/ +class ClientTunnelConfig : public TunnelConfig { + std::string dest; + int port; + std::string keys; + std::string address; + int destinationPort; + i2p::data::SigningKeyType sigType; +public: + ClientTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, + std::string dest_, + int port_, + std::string keys_, + std::string address_, + int destinationPort_, + i2p::data::SigningKeyType sigType_): TunnelConfig(name_, type_, i2cpParameters_), + dest(dest_), + port(port_), + keys(keys_), + address(address_), + destinationPort(destinationPort_), + sigType(sigType_){} + std::string& getdest(){return dest;} + int getport(){return port;} + std::string & getkeys(){return keys;} + std::string & getaddress(){return address;} + int getdestinationPort(){return destinationPort;} + i2p::data::SigningKeyType getsigType(){return sigType;} + void setdest(const std::string& dest_){dest=dest_;} + void setport(int port_){port=port_;} + void setkeys(const std::string & keys_){keys=keys_;} + void setaddress(const std::string & address_){address=address_;} + void setdestinationPort(int destinationPort_){destinationPort=destinationPort_;} + void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} + virtual void saveToStringStream(std::stringstream& out); + virtual ClientTunnelConfig* asClientTunnelConfig(){return this;} + virtual ServerTunnelConfig* asServerTunnelConfig(){return nullptr;} +}; + +/* +# mandatory parameters: +# * host -- ip address of our service +# * port -- port of our service +# * keys -- file with LeaseSet of address in i2p + std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); + int port = section.second.get (I2P_SERVER_TUNNEL_PORT); + std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); +# optional parameters (may be omitted) + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_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, true); + i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); + bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); +# * inport -- optional, i2p service port, if unset - the same as 'port' +# * accesslist -- comma-separated list of i2p addresses, allowed to connect +# every address is b32 without '.b32.i2p' part +[somelabel] +type = server +host = 127.0.0.1 +port = 6667 +keys = irc.dat +*/ +class ServerTunnelConfig : public TunnelConfig { + std::string host; + int port; + std::string keys; + int inPort; + std::string accessList; + std::string hostOverride; + std::string webircpass; + bool gzip; + i2p::data::SigningKeyType sigType; + std::string address; + bool isUniqueLocal; +public: + ServerTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, + std::string host_, + int port_, + std::string keys_, + int inPort_, + std::string accessList_, + std::string hostOverride_, + std::string webircpass_, + bool gzip_, + i2p::data::SigningKeyType sigType_, + std::string address_, + bool isUniqueLocal_): TunnelConfig(name_, type_, i2cpParameters_), + host(host_), + port(port_), + keys(keys_), + inPort(inPort_), + accessList(accessList_), + hostOverride(hostOverride_), + webircpass(webircpass_), + gzip(gzip_), + sigType(sigType_), + address(address_), + isUniqueLocal(isUniqueLocal_) {} + std::string& gethost(){return host;} + int getport(){return port;} + std::string& getkeys(){return keys;} + int getinPort(){return inPort;} + std::string& getaccessList(){return accessList;} + std::string& gethostOverride(){return hostOverride;} + std::string& getwebircpass(){return webircpass;} + bool getgzip(){return gzip;} + i2p::data::SigningKeyType getsigType(){return sigType;} + std::string& getaddress(){return address;} + bool getisUniqueLocal(){return isUniqueLocal;} + void sethost(const std::string& host_){host=host_;} + void setport(int port_){port=port_;} + void setkeys(const std::string& keys_){keys=keys_;} + void setinPort(int inPort_){inPort=inPort_;} + void setaccessList(const std::string& accessList_){accessList=accessList_;} + void sethostOverride(const std::string& hostOverride_){hostOverride=hostOverride_;} + void setwebircpass(const std::string& webircpass_){webircpass=webircpass_;} + void setgzip(bool gzip_){gzip=gzip_;} + void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} + void setaddress(const std::string& address_){address=address_;} + void setisUniqueLocal(bool isUniqueLocal_){isUniqueLocal=isUniqueLocal_;} + virtual void saveToStringStream(std::stringstream& out); + virtual ClientTunnelConfig* asClientTunnelConfig(){return nullptr;} + virtual ServerTunnelConfig* asServerTunnelConfig(){return this;} +}; + + +#endif // TUNNELCONFIG_H diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp new file mode 100644 index 00000000..fb840276 --- /dev/null +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -0,0 +1,249 @@ +#include "TunnelPane.h" + +#include "QMessageBox" +#include "mainwindow.h" +#include "ui_mainwindow.h" + +TunnelPane::TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunnelConfig_, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_): + QObject(), + mainWindow(mainWindow_), + wrongInputPane(wrongInputPane_), + wrongInputLabel(wrongInputLabel_), + tunnelConfig(tunnelConfig_), + tunnelsPageUpdateListener(tunnelsPageUpdateListener_), + gridLayoutWidget_2(nullptr) {} + +void TunnelPane::setupTunnelPane( + TunnelConfig* tunnelConfig, + QGroupBox *tunnelGroupBox, + QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, + QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h) { + tunnelGroupBox->setGeometry(0, tunnelsFormGridLayoutWidget->height(), gridLayoutWidget_2->width(), h); + tunnelsFormGridLayoutWidget->resize(527, tunnelsFormGridLayoutWidget->height()+h); + + QObject::connect(tunnelTypeComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(updated())); + + + this->tunnelGroupBox=tunnelGroupBox; + + gridLayoutWidget_2->setObjectName(QStringLiteral("gridLayoutWidget_2")); + this->gridLayoutWidget_2=gridLayoutWidget_2; + tunnelGridLayout = new QVBoxLayout(gridLayoutWidget_2); + tunnelGridLayout->setObjectName(QStringLiteral("tunnelGridLayout")); + tunnelGridLayout->setContentsMargins(5, 5, 5, 5); + tunnelGridLayout->setSpacing(5); + + //header + QHBoxLayout *headerHorizontalLayout = new QHBoxLayout(); + headerHorizontalLayout->setObjectName(QStringLiteral("headerHorizontalLayout")); + + nameLabel = new QLabel(gridLayoutWidget_2); + nameLabel->setObjectName(QStringLiteral("nameLabel")); + headerHorizontalLayout->addWidget(nameLabel); + nameLineEdit = new QLineEdit(gridLayoutWidget_2); + nameLineEdit->setObjectName(QStringLiteral("nameLineEdit")); + const QString& tunnelName=tunnelConfig->getName().c_str(); + nameLineEdit->setText(tunnelName); + setGroupBoxTitle(tunnelName); + + QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(setGroupBoxTitle(const QString &))); + QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + + headerHorizontalLayout->addWidget(nameLineEdit); + headerHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + headerHorizontalLayout->addItem(headerHorizontalSpacer); + deletePushButton = new QPushButton(gridLayoutWidget_2); + deletePushButton->setObjectName(QStringLiteral("deletePushButton")); + QObject::connect(deletePushButton, SIGNAL(released()), + this, SLOT(deleteButtonReleased()));//MainWindow::DeleteTunnelNamed(std::string name) { + headerHorizontalLayout->addWidget(deletePushButton); + tunnelGridLayout->addLayout(headerHorizontalLayout); + + //type + { + const QString& type = tunnelConfig->getType(); + QHBoxLayout * horizontalLayout_ = new QHBoxLayout(); + horizontalLayout_->setObjectName(QStringLiteral("horizontalLayout_")); + typeLabel = new QLabel(gridLayoutWidget_2); + typeLabel->setObjectName(QStringLiteral("typeLabel")); + horizontalLayout_->addWidget(typeLabel); + horizontalLayout_->addWidget(tunnelTypeComboBox); + QPushButton * lockButton1 = new QPushButton(gridLayoutWidget_2); + horizontalLayout_->addWidget(lockButton1); + widgetlocks.add(new widgetlock(tunnelTypeComboBox, lockButton1)); + this->tunnelTypeComboBox=tunnelTypeComboBox; + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_); + } + + retranslateTunnelForm(*this); +} + +void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex) { + { + //number of hops of an inbound tunnel + const QString& inbound_length=i2cpParameters.getInbound_length(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + inbound_lengthLabel = new QLabel(gridLayoutWidget_2); + inbound_lengthLabel->setObjectName(QStringLiteral("inbound_lengthLabel")); + horizontalLayout_2->addWidget(inbound_lengthLabel); + inbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); + inbound_lengthLineEdit->setObjectName(QStringLiteral("inbound_lengthLineEdit")); + inbound_lengthLineEdit->setText(inbound_length); + inbound_lengthLineEdit->setMaximumWidth(80); + QObject::connect(inbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(inbound_lengthLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + //number of hops of an outbound tunnel + const QString& outbound_length=i2cpParameters.getOutbound_length(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + outbound_lengthLabel = new QLabel(gridLayoutWidget_2); + outbound_lengthLabel->setObjectName(QStringLiteral("outbound_lengthLabel")); + horizontalLayout_2->addWidget(outbound_lengthLabel); + outbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); + outbound_lengthLineEdit->setObjectName(QStringLiteral("outbound_lengthLineEdit")); + outbound_lengthLineEdit->setText(outbound_length); + outbound_lengthLineEdit->setMaximumWidth(80); + QObject::connect(outbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(outbound_lengthLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + //number of inbound tunnels + const QString& inbound_quantity=i2cpParameters.getInbound_quantity(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + inbound_quantityLabel = new QLabel(gridLayoutWidget_2); + inbound_quantityLabel->setObjectName(QStringLiteral("inbound_quantityLabel")); + horizontalLayout_2->addWidget(inbound_quantityLabel); + inbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); + inbound_quantityLineEdit->setObjectName(QStringLiteral("inbound_quantityLineEdit")); + inbound_quantityLineEdit->setText(inbound_quantity); + inbound_quantityLineEdit->setMaximumWidth(80); + QObject::connect(inbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(inbound_quantityLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + //number of outbound tunnels + const QString& outbound_quantity=i2cpParameters.getOutbound_quantity(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + outbound_quantityLabel = new QLabel(gridLayoutWidget_2); + outbound_quantityLabel->setObjectName(QStringLiteral("outbound_quantityLabel")); + horizontalLayout_2->addWidget(outbound_quantityLabel); + outbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); + outbound_quantityLineEdit->setObjectName(QStringLiteral("outbound_quantityLineEdit")); + outbound_quantityLineEdit->setText(outbound_quantity); + outbound_quantityLineEdit->setMaximumWidth(80); + QObject::connect(outbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(outbound_quantityLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + { + //number of ElGamal/AES tags to send + const QString& crypto_tagsToSend=i2cpParameters.getCrypto_tagsToSend(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + crypto_tagsToSendLabel = new QLabel(gridLayoutWidget_2); + crypto_tagsToSendLabel->setObjectName(QStringLiteral("crypto_tagsToSendLabel")); + horizontalLayout_2->addWidget(crypto_tagsToSendLabel); + crypto_tagsToSendLineEdit = new QLineEdit(gridLayoutWidget_2); + crypto_tagsToSendLineEdit->setObjectName(QStringLiteral("crypto_tagsToSendLineEdit")); + crypto_tagsToSendLineEdit->setText(crypto_tagsToSend); + crypto_tagsToSendLineEdit->setMaximumWidth(80); + QObject::connect(crypto_tagsToSendLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(crypto_tagsToSendLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } + + retranslateI2CPParameters(); +} + +void TunnelPane::updated() { + std::string oldName=tunnelConfig->getName(); + //validate and show red if invalid + hideWrongInputLabel(); + if(!mainWindow->applyTunnelsUiToConfigs())return; + tunnelsPageUpdateListener->updated(oldName, tunnelConfig); +} + +void TunnelPane::deleteButtonReleased() { + QMessageBox msgBox; + msgBox.setText(QApplication::tr("Are you sure to delete this tunnel?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + switch (ret) { + case QMessageBox::Ok: + // OK was clicked + hideWrongInputLabel(); + tunnelsPageUpdateListener->needsDeleting(tunnelConfig->getName()); + break; + case QMessageBox::Cancel: + // Cancel was clicked + return; + } +} + +/* +const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; +const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; +const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; +const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; +const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; +const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; +const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; +const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; +const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; +*/ +QString TunnelPane::readTunnelTypeComboboxData() { + return tunnelTypeComboBox->currentData().toString(); +} + +i2p::data::SigningKeyType TunnelPane::readSigTypeComboboxUI(QComboBox* sigTypeComboBox) { + return (i2p::data::SigningKeyType) sigTypeComboBox->currentData().toInt(); +} + +void TunnelPane::deleteTunnelForm() { + widgetlocks.deleteListeners(); +} + +void TunnelPane::highlightWrongInput(QString warningText, QWidget* controlWithWrongInput) { + wrongInputPane->setVisible(true); + wrongInputLabel->setText(warningText); + mainWindow->adjustSizesAccordingToWrongLabel(); + if(controlWithWrongInput){ + mainWindow->ui->tunnelsScrollArea->ensureWidgetVisible(controlWithWrongInput); + controlWithWrongInput->setFocus(); + } + mainWindow->showTunnelsPage(); +} + +void TunnelPane::hideWrongInputLabel() const { + wrongInputPane->setVisible(false); + mainWindow->adjustSizesAccordingToWrongLabel(); +} diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h new file mode 100644 index 00000000..a7012810 --- /dev/null +++ b/qt/i2pd_qt/TunnelPane.h @@ -0,0 +1,137 @@ +#ifndef TUNNELPANE_H +#define TUNNELPANE_H + +#include "QObject" +#include "QWidget" +#include "QComboBox" +#include "QGridLayout" +#include "QLabel" +#include "QPushButton" +#include "QApplication" +#include "QLineEdit" +#include "QGroupBox" +#include "QVBoxLayout" + +#include "TunnelConfig.h" + +#include +#include + +class ServerTunnelPane; +class ClientTunnelPane; + +class TunnelConfig; +class I2CPParameters; + +class MainWindow; + +class TunnelPane : public QObject { + + Q_OBJECT + +public: + TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_); + virtual ~TunnelPane(){} + + void deleteTunnelForm(); + + void hideWrongInputLabel() const; + void highlightWrongInput(QString warningText, QWidget* controlWithWrongInput); + + virtual ServerTunnelPane* asServerTunnelPane()=0; + virtual ClientTunnelPane* asClientTunnelPane()=0; + +protected: + MainWindow* mainWindow; + QWidget * wrongInputPane; + QLabel* wrongInputLabel; + TunnelConfig* tunnelConfig; + widgetlockregistry widgetlocks; + TunnelsPageUpdateListener* tunnelsPageUpdateListener; + QVBoxLayout *tunnelGridLayout; + QGroupBox *tunnelGroupBox; + QWidget* gridLayoutWidget_2; + + //header + QLabel *nameLabel; + QLineEdit *nameLineEdit; +public: + QLineEdit * getNameLineEdit() { return nameLineEdit; } + +public slots: + void updated(); + void deleteButtonReleased(); + +protected: + QSpacerItem *headerHorizontalSpacer; + QPushButton *deletePushButton; + + //type + QComboBox *tunnelTypeComboBox; + QLabel *typeLabel; + + //i2cp + + QLabel * inbound_lengthLabel; + QLineEdit * inbound_lengthLineEdit; + + QLabel * outbound_lengthLabel; + QLineEdit * outbound_lengthLineEdit; + + QLabel * inbound_quantityLabel; + QLineEdit * inbound_quantityLineEdit; + + QLabel * outbound_quantityLabel; + QLineEdit * outbound_quantityLineEdit; + + QLabel * crypto_tagsToSendLabel; + QLineEdit * crypto_tagsToSendLineEdit; + + QString readTunnelTypeComboboxData(); + + //should be created by factory + i2p::data::SigningKeyType readSigTypeComboboxUI(QComboBox* sigTypeComboBox); + +public: + //returns false when invalid data at UI + virtual bool applyDataFromUIToTunnelConfig() { + tunnelConfig->setName(nameLineEdit->text().toStdString()); + tunnelConfig->setType(readTunnelTypeComboboxData()); + I2CPParameters& i2cpParams=tunnelConfig->getI2cpParameters(); + i2cpParams.setInbound_length(inbound_lengthLineEdit->text()); + i2cpParams.setInbound_quantity(inbound_quantityLineEdit->text()); + i2cpParams.setOutbound_length(outbound_lengthLineEdit->text()); + i2cpParams.setOutbound_quantity(outbound_quantityLineEdit->text()); + i2cpParams.setCrypto_tagsToSend(crypto_tagsToSendLineEdit->text()); + return true; + } +protected: + void setupTunnelPane( + TunnelConfig* tunnelConfig, + QGroupBox *tunnelGroupBox, + QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, + QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h); + void appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex); +public: + int height() { + return gridLayoutWidget_2?gridLayoutWidget_2->height():0; + } + +protected slots: + virtual void setGroupBoxTitle(const QString & title)=0; +private: + void retranslateTunnelForm(TunnelPane& ui) { + ui.deletePushButton->setText(QApplication::translate("tunForm", "Delete Tunnel", 0)); + ui.nameLabel->setText(QApplication::translate("tunForm", "Tunnel name:", 0)); + } + + void retranslateI2CPParameters() { + inbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an inbound tunnel:", 0));; + outbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an outbound tunnel:", 0));; + inbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of inbound tunnels:", 0));; + outbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of outbound tunnels:", 0));; + crypto_tagsToSendLabel->setText(QApplication::translate("tunForm", "Number of ElGamal/AES tags to send:", 0));; + } +}; + +#endif // TUNNELPANE_H diff --git a/qt/i2pd_qt/TunnelsPageUpdateListener.h b/qt/i2pd_qt/TunnelsPageUpdateListener.h new file mode 100644 index 00000000..83b4e9fe --- /dev/null +++ b/qt/i2pd_qt/TunnelsPageUpdateListener.h @@ -0,0 +1,12 @@ +#ifndef TUNNELSPAGEUPDATELISTENER_H +#define TUNNELSPAGEUPDATELISTENER_H + +class TunnelConfig; + +class TunnelsPageUpdateListener { +public: + virtual void updated(std::string oldName, TunnelConfig* tunConf)=0; + virtual void needsDeleting(std::string oldName)=0; +}; + +#endif // TUNNELSPAGEUPDATELISTENER_H diff --git a/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png new file mode 100644 index 00000000..ee5400c4 Binary files /dev/null and b/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png new file mode 100644 index 00000000..bc020282 Binary files /dev/null and b/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png new file mode 100644 index 00000000..5aed761e Binary files /dev/null and b/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png new file mode 100644 index 00000000..996e1296 Binary files /dev/null and b/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png new file mode 100644 index 00000000..3ed9b518 Binary files /dev/null and b/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png new file mode 100644 index 00000000..922846c6 Binary files /dev/null and b/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png new file mode 100644 index 00000000..4b7c81ea Binary files /dev/null and b/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png new file mode 100644 index 00000000..6336627c Binary files /dev/null and b/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png new file mode 100644 index 00000000..a291cab5 Binary files /dev/null and b/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png differ diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml new file mode 100644 index 00000000..e9f35676 --- /dev/null +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -0,0 +1,46 @@ + + + + website.i2pd.i2pd + website.i2pd.i2pd.desktop + CC0-1.0 + BSD-3-Clause + i2pd + Invisible Internet + +

i2pd (I2P Daemon) is a 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.

+

I2P allows people from all around the world to communicate and share information + without restrictions.

+

Features:

+
    +
  • Distributed anonymous networking framework
  • +
  • End-to-end encrypted communications
  • +
  • Small footprint, simple dependencies, fast performance
  • +
  • Rich set of APIs for developers of secure applications
  • +
+
+ + + https://i2pd.website/images/i2pd_qt.png + + + https://i2pd.website/ + https://github.com/PurpleI2P/i2pd/issues + https://i2pd.readthedocs.io/en/latest/ + supervillain@riseup.net + PurpleI2P Team + + + + + + + + + + + +
diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.desktop b/qt/i2pd_qt/data/website.i2pd.i2pd.desktop new file mode 100644 index 00000000..33cb3214 --- /dev/null +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Categories=Network;P2P;Qt; +Exec=i2pd_qt +GenericName=Invisible Internet +Comment=A universal anonymous network layer +Icon=website.i2pd.i2pd +Name=i2pd +Terminal=false +Type=Application +StartupNotify=false +Keywords=i2p;i2pd;vpn;p2p; diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui new file mode 100644 index 00000000..a7f07e5d --- /dev/null +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -0,0 +1,2875 @@ + + + GeneralSettingsContentsForm + + + + 0 + 0 + 679 + 3033 + + + + + 0 + 0 + + + + GeneralSettingsContentsForm + + + + + 0 + 0 + 679 + 3052 + + + + + QLayout::SetMinAndMaxSize + + + + + + 0 + 0 + + + + + 0 + 51 + + + + + 16777215 + 51 + + + + Configuration file: + + + + + 0 + 18 + 661 + 31 + + + + + QLayout::SetMaximumSize + + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + + 16777215 + 27 + + + + Browse… + + + + + + + + + + + + 0 + 0 + + + + + 0 + 51 + + + + + 16777215 + 51 + + + + Tunnels configuration file: + + + + + 0 + 18 + 661 + 31 + + + + + QLayout::SetMaximumSize + + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + + 16777215 + 27 + + + + Browse… + + + + + + + + + + + + 0 + 0 + + + + + 0 + 51 + + + + + 16777215 + 51 + + + + Pid file: + + + + + 0 + 18 + 661 + 31 + + + + + QLayout::SetMaximumSize + + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + + 16777215 + 27 + + + + Browse… + + + + + + + + + + + + 0 + 98 + + + + + 16777215 + 98 + + + + SAM interface + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + Windows-specific options + + + + + + + + 0 + 44 + + + + + 16777215 + 44 + + + + Cryptography + + + + + 0 + 20 + 661 + 22 + + + + Use ElGamal precomputed tables + + + + + + + + + 0 + 107 + + + + + 16777215 + 107 + + + + Logging + + + Qt::AlignJustify|Qt::AlignTop + + + + + -1 + 19 + 661 + 91 + + + + + QLayout::SetMinimumSize + + + + + QLayout::SetMaximumSize + + + + + Destination: + + + + + + + + + + Edit + + + + + + + Log file: + + + + + + + + + + Browse… + + + + + + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + Log level: + + + + + + + + Error + + + + + Warn + + + + + Info + + + + + Debug + + + + + + + + Edit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 68 + + + + + 16777215 + 68 + + + + UPnP + + + + + 0 + 20 + 97 + 22 + + + + Enable + + + + + + 0 + 40 + 661 + 31 + + + + + + + Name i2pd appears in UPnP forwardings list: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 98 + + + + + 16777215 + 98 + + + + I2CP interface + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 98 + + + + + 16777215 + 98 + + + + BOB interface + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + General options + + + + + + + + 0 + 0 + + + + + 0 + 98 + + + + + 16777215 + 98 + + + + Router external address (for incoming connections) + + + Qt::AlignJustify|Qt::AlignTop + + + + + 0 + 20 + 661 + 81 + + + + + QLayout::SetMinAndMaxSize + + + + + QLayout::SetMinAndMaxSize + + + + + Host: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetMinAndMaxSize + + + + + Port (leave 0 to auto-assign): + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 78 + + + + + 16777215 + 78 + + + + Addressbook settings + + + + + 0 + 20 + 661 + 31 + + + + + + + Addressbook default subscription URL for initial setup: + + + + + + + + + + + + 0 + 50 + 661 + 31 + + + + + + + Addressbook subscriptions URLs, separated by comma: + + + + + + + + + + + + + + + 0 + 280 + + + + + 16777215 + 280 + + + + HTTP proxy + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 661 + 31 + + + + + + + Keys file: + + + + + + + + + + Browse… + + + + + + + + + 0 + 160 + 661 + 31 + + + + + + + Inbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 190 + 661 + 31 + + + + + + + Inbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 220 + 661 + 31 + + + + + + + Outbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 250 + 661 + 31 + + + + + + + Outbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 130 + 661 + 31 + + + + + + + Signature type: + + + + + + + + + + Edit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + Various options + + + + + + + + 0 + 51 + + + + + 16777215 + 51 + + + + Data folder (for storage of i2pd data — RI, keys, peer profiles, …): + + + + + 0 + 20 + 661 + 31 + + + + + QLayout::SetMaximumSize + + + + + + + + Browse… + + + + + + + + + + + + 0 + 0 + + + + + 0 + 215 + + + + + 16777215 + 215 + + + + Router options + + + + + 0 + 20 + 661 + 188 + + + + + + + Enable communication through ipv6 + + + + + + + Router will not accept transit tunnels at startup + + + + + + + Router will be floodfill + + + + + + + + + Bandwidth limit (integer or a letter): + + + + + + + + + + KBps + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Family (name of a family router belongs to): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetMaximumSize + + + + + NetID (network ID router belongs to. The main I2P ID is 2): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 108 + + + + + 16777215 + 108 + + + + Limits + + + + + 0 + 20 + 661 + 31 + + + + + + + Maximum number of transit tunnels: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 50 + 661 + 31 + + + + + + + Maximum number of open files (0 — use system limit): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 80 + 661 + 31 + + + + + + + Maximum size of core file in Kb (0 — use system limit): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 98 + + + + + 16777215 + 98 + + + + Reseeding + + + + + 0 + 20 + 661 + 22 + + + + Request SU3 signature verification + + + + + + 0 + 40 + 661 + 31 + + + + + + + SU3 file to reseed from: + + + + + + + + + + Browse… + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Reseed URLs, separated by comma: + + + + + + + + + + + + + + + 0 + 170 + + + + + 16777215 + 170 + + + + Trust options + + + + + 0 + 20 + 661 + 21 + + + + Enable explicit trust options + + + + + + 390 + 40 + 271 + 23 + + + + + + + 0 + 40 + 391 + 42 + + + + Make direct I2P connections only to +routers in specified Family: + + + + + + 0 + 82 + 661 + 42 + + + + Make direct I2P connections only to routers specified here. +Comma separated list of base64 identities: + + + + + + 0 + 124 + 661 + 23 + + + + + + + 0 + 147 + 661 + 21 + + + + Should we hide our router from other routers? + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + Ports + + + + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + Insomnia (prevent system from sleeping) + + + + + + + + 0 + 189 + + + + + 16777215 + 189 + + + + I2PControl interface + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 661 + 31 + + + + + + + Password: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 130 + 661 + 31 + + + + + + + Certificate file: + + + + + + + + + + Browse… + + + + + + + + + 0 + 160 + 661 + 31 + + + + + + + Key file: + + + + + + + + + + Browse… + + + + + + + + + + + + 0 + 0 + + + + + 0 + 105 + + + + + 16777215 + 105 + + + + Websockets server + + + + + 0 + 20 + 85 + 21 + + + + Enable + + + + + + 0 + 40 + 661 + 31 + + + + + + + Address to bind websocket server on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to bind websocket server on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 179 + + + + + 16777215 + 179 + + + + HTTP webconsole + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 321 + 22 + + + + Enable basic HTTP auth + + + + + + 60 + 120 + 601 + 31 + + + + + + + Username: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 60 + 150 + 601 + 31 + + + + + + + Password: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 335 + + + + + 16777215 + 335 + + + + Socks proxy + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 661 + 31 + + + + + + + Keys file: + + + + + + + + + + Browse… + + + + + + + + + 0 + 160 + 661 + 31 + + + + + + + Inbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 190 + 661 + 31 + + + + + + + Inbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 220 + 661 + 31 + + + + + + + Outbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 250 + 661 + 31 + + + + + + + Outbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 280 + 661 + 31 + + + + + + + Outproxy address: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 310 + 661 + 31 + + + + + + + Outproxy port: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 130 + 661 + 31 + + + + + + + Signature type: + + + + + + + + + + Edit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/i2pd.qrc b/qt/i2pd_qt/i2pd.qrc new file mode 100644 index 00000000..4e5523e9 --- /dev/null +++ b/qt/i2pd_qt/i2pd.qrc @@ -0,0 +1,6 @@ + + + resources/icons/mask.ico + resources/images/icon.png + + diff --git a/qt/i2pd_qt/i2pd.rc b/qt/i2pd_qt/i2pd.rc new file mode 100644 index 00000000..bebdf1d6 --- /dev/null +++ b/qt/i2pd_qt/i2pd.rc @@ -0,0 +1,32 @@ +IDI_ICON1 ICON DISCARDABLE "resources/icons/mask.ico" + +#include // needed for VERSIONINFO +#include "../../libi2pd/version.h" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH +PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // U.S. English - multilingual (hex) + BEGIN + VALUE "CompanyName", "PurpleI2P" + VALUE "FileDescription", "I2Pd Qt" + VALUE "FileVersion", I2PD_VERSION + VALUE "InternalName", "i2pd-qt" + VALUE "LegalCopyright", "Copyright (C) 2013-2018, The PurpleI2P Project" + VALUE "LegalTrademarks1", "Distributed under the BSD 3-Clause software license, see the accompanying file COPYING or https://opensource.org/licenses/BSD-3-Clause." + VALUE "OriginalFilename", "i2pd_qt.exe" + VALUE "ProductName", "i2pd-qt" + VALUE "ProductVersion", I2P_VERSION + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal) + END +END diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro new file mode 100644 index 00000000..e24a3963 --- /dev/null +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -0,0 +1,245 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = i2pd_qt +TEMPLATE = app +QMAKE_CXXFLAGS *= -std=c++11 -ggdb +DEFINES += USE_UPNP + +SOURCES += DaemonQT.cpp mainwindow.cpp \ + ../../libi2pd/api.cpp \ + ../../libi2pd/Base.cpp \ + ../../libi2pd/BloomFilter.cpp \ + ../../libi2pd/Config.cpp \ + ../../libi2pd/CPU.cpp \ + ../../libi2pd/Crypto.cpp \ + ../../libi2pd/CryptoKey.cpp \ + ../../libi2pd/Datagram.cpp \ + ../../libi2pd/Destination.cpp \ + ../../libi2pd/Event.cpp \ + ../../libi2pd/Family.cpp \ + ../../libi2pd/FS.cpp \ + ../../libi2pd/Garlic.cpp \ + ../../libi2pd/Gost.cpp \ + ../../libi2pd/Gzip.cpp \ + ../../libi2pd/HTTP.cpp \ + ../../libi2pd/I2NPProtocol.cpp \ + ../../libi2pd/I2PEndian.cpp \ + ../../libi2pd/Identity.cpp \ + ../../libi2pd/LeaseSet.cpp \ + ../../libi2pd/Log.cpp \ + ../../libi2pd/NetDb.cpp \ + ../../libi2pd/NetDbRequests.cpp \ + ../../libi2pd/NTCPSession.cpp \ + ../../libi2pd/Profiling.cpp \ + ../../libi2pd/Reseed.cpp \ + ../../libi2pd/RouterContext.cpp \ + ../../libi2pd/RouterInfo.cpp \ + ../../libi2pd/Signature.cpp \ + ../../libi2pd/SSU.cpp \ + ../../libi2pd/SSUData.cpp \ + ../../libi2pd/SSUSession.cpp \ + ../../libi2pd/Streaming.cpp \ + ../../libi2pd/Timestamp.cpp \ + ../../libi2pd/TransitTunnel.cpp \ + ../../libi2pd/Transports.cpp \ + ../../libi2pd/Tunnel.cpp \ + ../../libi2pd/TunnelEndpoint.cpp \ + ../../libi2pd/TunnelGateway.cpp \ + ../../libi2pd/TunnelPool.cpp \ + ../../libi2pd/util.cpp \ + ../../libi2pd/Ed25519.cpp \ + ../../libi2pd/Chacha20.cpp \ + ../../libi2pd/Poly1305.cpp \ + ../../libi2pd_client/AddressBook.cpp \ + ../../libi2pd_client/BOB.cpp \ + ../../libi2pd_client/ClientContext.cpp \ + ../../libi2pd_client/HTTPProxy.cpp \ + ../../libi2pd_client/I2CP.cpp \ + ../../libi2pd_client/I2PService.cpp \ + ../../libi2pd_client/I2PTunnel.cpp \ + ../../libi2pd_client/MatchedDestination.cpp \ + ../../libi2pd_client/SAM.cpp \ + ../../libi2pd_client/SOCKS.cpp \ + ../../libi2pd_client/Websocket.cpp \ + ../../libi2pd_client/WebSocks.cpp \ + ClientTunnelPane.cpp \ + MainWindowItems.cpp \ + ServerTunnelPane.cpp \ + SignatureTypeComboboxFactory.cpp \ + TunnelConfig.cpp \ + TunnelPane.cpp \ + ../../daemon/Daemon.cpp \ + ../../daemon/HTTPServer.cpp \ + ../../daemon/i2pd.cpp \ + ../../daemon/I2PControl.cpp \ + ../../daemon/UnixDaemon.cpp \ + ../../daemon/UPnP.cpp \ + textbrowsertweaked1.cpp \ + pagewithbackbutton.cpp \ + widgetlock.cpp \ + widgetlockregistry.cpp \ + logviewermanager.cpp \ + ../../libi2pd/NTCP2.cpp + +#qt creator does not handle this well +#SOURCES += $$files(../../libi2pd/*.cpp) +#SOURCES += $$files(../../libi2pd_client/*.cpp) +#SOURCES += $$files(../../daemon/*.cpp) +#SOURCES += $$files(./*.cpp) + +SOURCES -= ../../daemon/UnixDaemon.cpp + +HEADERS += DaemonQT.h mainwindow.h \ + ../../libi2pd/api.h \ + ../../libi2pd/Base.h \ + ../../libi2pd/BloomFilter.h \ + ../../libi2pd/Config.h \ + ../../libi2pd/Crypto.h \ + ../../libi2pd/CryptoKey.h \ + ../../libi2pd/Datagram.h \ + ../../libi2pd/Destination.h \ + ../../libi2pd/Event.h \ + ../../libi2pd/Family.h \ + ../../libi2pd/FS.h \ + ../../libi2pd/Garlic.h \ + ../../libi2pd/Gost.h \ + ../../libi2pd/Gzip.h \ + ../../libi2pd/HTTP.h \ + ../../libi2pd/I2NPProtocol.h \ + ../../libi2pd/I2PEndian.h \ + ../../libi2pd/Identity.h \ + ../../libi2pd/LeaseSet.h \ + ../../libi2pd/LittleBigEndian.h \ + ../../libi2pd/Log.h \ + ../../libi2pd/NetDb.hpp \ + ../../libi2pd/NetDbRequests.h \ + ../../libi2pd/NTCPSession.h \ + ../../libi2pd/Profiling.h \ + ../../libi2pd/Queue.h \ + ../../libi2pd/Reseed.h \ + ../../libi2pd/RouterContext.h \ + ../../libi2pd/RouterInfo.h \ + ../../libi2pd/Signature.h \ + ../../libi2pd/SSU.h \ + ../../libi2pd/SSUData.h \ + ../../libi2pd/SSUSession.h \ + ../../libi2pd/Streaming.h \ + ../../libi2pd/Tag.h \ + ../../libi2pd/Timestamp.h \ + ../../libi2pd/TransitTunnel.h \ + ../../libi2pd/Transports.h \ + ../../libi2pd/TransportSession.h \ + ../../libi2pd/Tunnel.h \ + ../../libi2pd/TunnelBase.h \ + ../../libi2pd/TunnelConfig.h \ + ../../libi2pd/TunnelEndpoint.h \ + ../../libi2pd/TunnelGateway.h \ + ../../libi2pd/TunnelPool.h \ + ../../libi2pd/util.h \ + ../../libi2pd/version.h \ + ../../libi2pd_client/AddressBook.h \ + ../../libi2pd_client/BOB.h \ + ../../libi2pd_client/ClientContext.h \ + ../../libi2pd_client/HTTPProxy.h \ + ../../libi2pd_client/I2CP.h \ + ../../libi2pd_client/I2PService.h \ + ../../libi2pd_client/I2PTunnel.h \ + ../../libi2pd_client/MatchedDestination.h \ + ../../libi2pd_client/SAM.h \ + ../../libi2pd_client/SOCKS.h \ + ../../libi2pd_client/Websocket.h \ + ../../libi2pd_client/WebSocks.h \ + ClientTunnelPane.h \ + MainWindowItems.h \ + ServerTunnelPane.h \ + SignatureTypeComboboxFactory.h \ + TunnelConfig.h \ + TunnelPane.h \ + TunnelsPageUpdateListener.h \ + ../../daemon/Daemon.h \ + ../../daemon/HTTPServer.h \ + ../../daemon/I2PControl.h \ + ../../daemon/UPnP.h \ + textbrowsertweaked1.h \ + pagewithbackbutton.h \ + widgetlock.h \ + widgetlockregistry.h \ + i2pd.rc \ + i2pd.rc \ + logviewermanager.h + +INCLUDEPATH += ../../libi2pd +INCLUDEPATH += ../../libi2pd_client +INCLUDEPATH += ../../daemon +INCLUDEPATH += . + +FORMS += mainwindow.ui \ + tunnelform.ui \ + statusbuttons.ui \ + routercommandswidget.ui \ + generalsettingswidget.ui + +LIBS += -lz + +macx { + message("using mac os x target") + BREWROOT=/usr/local + BOOSTROOT=$$BREWROOT/opt/boost + SSLROOT=$$BREWROOT/opt/libressl + UPNPROOT=$$BREWROOT/opt/miniupnpc + INCLUDEPATH += $$BOOSTROOT/include + INCLUDEPATH += $$SSLROOT/include + INCLUDEPATH += $$UPNPROOT/include + LIBS += $$SSLROOT/lib/libcrypto.a + LIBS += $$SSLROOT/lib/libssl.a + LIBS += $$BOOSTROOT/lib/libboost_system.a + LIBS += $$BOOSTROOT/lib/libboost_date_time.a + LIBS += $$BOOSTROOT/lib/libboost_filesystem.a + LIBS += $$BOOSTROOT/lib/libboost_program_options.a + LIBS += $$UPNPROOT/lib/libminiupnpc.a +} + +linux:!android { + message("Using Linux settings") + LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc +} + +windows { + message("Using Windows settings") + RC_FILE = i2pd.rc + DEFINES += BOOST_USE_WINDOWS_H WINDOWS _WINDOWS WIN32_LEAN_AND_MEAN MINIUPNP_STATICLIB + DEFINES -= UNICODE _UNICODE + BOOST_SUFFIX = -mt + QMAKE_CXXFLAGS_RELEASE = -Os + QMAKE_LFLAGS = -Wl,-Bstatic -static-libgcc -static-libstdc++ -mwindows + + #linker's -s means "strip" + QMAKE_LFLAGS_RELEASE += -s + + LIBS = -lminiupnpc \ + -lboost_system$$BOOST_SUFFIX \ + -lboost_date_time$$BOOST_SUFFIX \ + -lboost_filesystem$$BOOST_SUFFIX \ + -lboost_program_options$$BOOST_SUFFIX \ + -lssl \ + -lcrypto \ + -lz \ + -lwsock32 \ + -lws2_32 \ + -lgdi32 \ + -liphlpapi \ + -lstdc++ \ + -lpthread +} + +!android:!symbian:!maemo5:!simulator { + message("Build with a system tray icon") + # see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* + #sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images + RESOURCES = i2pd.qrc + QT += xml + #INSTALLS += sources +} + diff --git a/qt/i2pd_qt/logviewermanager.cpp b/qt/i2pd_qt/logviewermanager.cpp new file mode 100644 index 00000000..30fc904a --- /dev/null +++ b/qt/i2pd_qt/logviewermanager.cpp @@ -0,0 +1,45 @@ +#include "logviewermanager.h" + +LogViewerManager::LogViewerManager(std::shared_ptr logStream_, + QPlainTextEdit* logTextEdit_, + QObject *parent) : + QObject(parent), + logStream(logStream_), + logTextEdit(logTextEdit_), + controllerForBgThread(nullptr) +{ + assert(logTextEdit!=nullptr); + controllerForBgThread=new i2pd::qt::logviewer::Controller(*this); +} + +namespace i2pd { +namespace qt { +namespace logviewer { + +QString Worker::pollAndShootATimerForInfiniteRetries() { + std::shared_ptr logStream=logViewerManager.getLogStream(); + assert(logStream!=nullptr); + std::streamsize MAX_SZ=64*1024; + char*buf=(char*)malloc(MAX_SZ*sizeof(char)); + if(buf==nullptr)return ""; + std::streamsize read=logStream->readsome(buf, MAX_SZ); + if(read<0)read=0; + QString ret=QString::fromUtf8(buf, read); + free(buf); + return ret; +} + +Controller::Controller(LogViewerManager ¶meter1):logViewerManager(parameter1) { + Worker *worker = new Worker(parameter1); + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(this, &Controller::operate1, worker, &Worker::doWork1); + connect(worker, &Worker::resultReady, + ¶meter1, &LogViewerManager::appendPlainText_atGuiThread); + workerThread.start(); + timerId=startTimer(100/*millis*/); +} + +} +} +} diff --git a/qt/i2pd_qt/logviewermanager.h b/qt/i2pd_qt/logviewermanager.h new file mode 100644 index 00000000..e9ede79f --- /dev/null +++ b/qt/i2pd_qt/logviewermanager.h @@ -0,0 +1,130 @@ +#ifndef LOGVIEWERMANAGER_H +#define LOGVIEWERMANAGER_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "FS.h" +#include "Log.h" + +class LogViewerManager; + +namespace i2pd { +namespace qt { +namespace logviewer { + +class Worker : public QObject +{ + Q_OBJECT +private: + LogViewerManager &logViewerManager; +public: + Worker(LogViewerManager ¶meter1):logViewerManager(parameter1){} +private: + QString pollAndShootATimerForInfiniteRetries(); + +public slots: + void doWork1() { + /* ... here is the expensive or blocking operation ... */ + QString read=pollAndShootATimerForInfiniteRetries(); + emit resultReady(read); + } + +signals: + void resultReady(QString read); +}; + +class Controller : public QObject +{ + Q_OBJECT + QThread workerThread; + LogViewerManager& logViewerManager; + int timerId; +public: + Controller(LogViewerManager ¶meter1); + ~Controller() { + if(timerId!=0)killTimer(timerId); + workerThread.quit(); + workerThread.wait(); + } +signals: + void operate1(); +protected: + void timerEvent(QTimerEvent */*event*/) { + emit operate1(); + } +}; + +} +} +} + +class LogViewerManager : public QObject +{ + Q_OBJECT +private: + std::shared_ptr logStream; + QPlainTextEdit* logTextEdit; + i2pd::qt::logviewer::Controller* controllerForBgThread; +public: + //also starts a bg thread (QTimer) polling logStream->readsome(buf, n) + explicit LogViewerManager(std::shared_ptr logStream_, + QPlainTextEdit* logTextEdit_, + QObject *parent); + //also deallocs the bg thread (QTimer) + virtual ~LogViewerManager(){} + const i2pd::qt::logviewer::Controller& getControllerForBgThread() { + assert(controllerForBgThread!=nullptr); + return *controllerForBgThread; + } + const QPlainTextEdit* getLogTextEdit(){ return logTextEdit; } + const std::shared_ptr getLogStream(){ return logStream; } +signals: + +public slots: + //void appendFromNonGuiThread(std::string read) { + //} +public slots: + void appendPlainText_atGuiThread(QString plainText) { + if(plainText.length()==0)return; + assert(logTextEdit!=nullptr); + int scrollPosVert =logTextEdit->verticalScrollBar()->value(); + int scrollPosHoriz=logTextEdit->horizontalScrollBar()->value(); + int scrollPosVertMax =logTextEdit->verticalScrollBar()->maximum(); + const int MAX_LINES=10*1024; + logTextEdit->setMaximumBlockCount(MAX_LINES); + //logTextEdit->appendPlainText(plainText); + //navigate the window to the end + //QTextCursor cursor = logTextEdit->textCursor(); + //cursor.movePosition(QTextCursor::MoveOperation::End); + //logTextEdit->setTextCursor(cursor); + //QTextCursor prev_cursor = logTextEdit->textCursor(); + logTextEdit->moveCursor(QTextCursor::End); + logTextEdit->insertPlainText(plainText); + if(/*prev_cursor.atEnd()*/scrollPosVert==scrollPosVertMax){ + //logTextEdit->moveCursor(QTextCursor::End); + scrollPosVert =logTextEdit->verticalScrollBar()->maximum(); + scrollPosHoriz=logTextEdit->horizontalScrollBar()->minimum(); + } + //else + // logTextEdit->setTextCursor(prev_cursor); + logTextEdit->verticalScrollBar()->setValue(scrollPosVert); + logTextEdit->horizontalScrollBar()->setValue(scrollPosHoriz); + } + /* + void replaceText_atGuiThread() { + assert(logTextEdit!=nullptr); + logTextEdit->setText(QString::fromStdString(nav.getContent())); + } + */ +}; + +#endif // LOGVIEWERMANAGER_H diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp new file mode 100644 index 00000000..e4c9f2a7 --- /dev/null +++ b/qt/i2pd_qt/mainwindow.cpp @@ -0,0 +1,923 @@ +#include +#include +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "ui_statusbuttons.h" +#include "ui_routercommandswidget.h" +#include "ui_generalsettingswidget.h" +#include +#include +#include +#include +#include +#include +#include "RouterContext.h" +#include "Config.h" +#include "FS.h" +#include "Log.h" +#include "RouterContext.h" +#include "Transports.h" + +#include "HTTPServer.h" + +#ifndef ANDROID +# include +#endif + +#include "DaemonQT.h" +#include "SignatureTypeComboboxFactory.h" + +#include "logviewermanager.h" + +std::string programOptionsWriterCurrentSection; + +MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : + QMainWindow(parent) + ,logStream(logStream_) +#ifndef ANDROID + ,quitting(false) +#endif + ,wasSelectingAtStatusMainPage(false) + ,showHiddenInfoStatusMainPage(false) + ,logViewerManagerPtr(nullptr) + ,ui(new Ui::MainWindow) + ,statusButtonsUI(new Ui::StatusButtonsForm) + ,routerCommandsUI(new Ui::routerCommandsWidget) + ,uiSettings(new Ui::GeneralSettingsContentsForm) + ,routerCommandsParent(new QWidget(this)) + ,widgetlocks() + ,i2pController(nullptr) + ,configItems() + ,datadir() + ,confpath() + ,tunconfpath() + ,tunnelsPageUpdateListener(this) + +{ + ui->setupUi(this); + statusButtonsUI->setupUi(ui->statusButtonsPane); + routerCommandsUI->setupUi(routerCommandsParent); + uiSettings->setupUi(ui->settingsContents); + routerCommandsParent->hide(); + ui->verticalLayout_2->addWidget(routerCommandsParent); + //,statusHtmlUI(new Ui::StatusHtmlPaneForm) + //statusHtmlUI->setupUi(lastStatusWidgetui->statusWidget); + ui->statusButtonsPane->setFixedSize(171,300); + ui->verticalLayout->setGeometry(QRect(0,0,171,ui->verticalLayout->geometry().height())); + //ui->statusButtonsPane->adjustSize(); + //ui->centralWidget->adjustSize(); + setWindowTitle(QApplication::translate("AppTitle","I2PD")); + + //TODO handle resizes and change the below into resize() call + setFixedHeight(550); + ui->centralWidget->setFixedHeight(550); + onResize(); + + ui->stackedWidget->setCurrentIndex(0); + ui->settingsScrollArea->resize(uiSettings->settingsContentsGridLayout->sizeHint().width()+10,380); + QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); + int w = 683; + int h = 3060; + ui->settingsContents->setFixedSize(w, h); + ui->settingsContents->setGeometry(QRect(0,0,w,h)); + + /* + QPalette pal(palette()); + pal.setColor(QPalette::Background, Qt::red); + ui->settingsContents->setAutoFillBackground(true); + ui->settingsContents->setPalette(pal); + */ + QPalette pal(palette()); + pal.setColor(QPalette::Background, Qt::red); + ui->wrongInputLabel->setAutoFillBackground(true); + ui->wrongInputLabel->setPalette(pal); + ui->wrongInputLabel->setMaximumHeight(ui->wrongInputLabel->sizeHint().height()); + ui->wrongInputLabel->setVisible(false); + + settingsTitleLabelNominalHeight = ui->settingsTitleLabel->height(); +#ifndef ANDROID + createActions(); + createTrayIcon(); +#endif + + textBrowser = new TextBrowserTweaked1(this); + //textBrowser->setOpenExternalLinks(false); + textBrowser->setOpenLinks(false); + /*textBrowser->setTextInteractionFlags(textBrowser->textInteractionFlags()| + Qt::LinksAccessibleByMouse|Qt::LinksAccessibleByKeyboard| + Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);*/ + ui->verticalLayout_2->addWidget(textBrowser); + childTextBrowser = new TextBrowserTweaked1(this); + //childTextBrowser->setOpenExternalLinks(false); + childTextBrowser->setOpenLinks(false); + connect(textBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); + pageWithBackButton = new PageWithBackButton(this, childTextBrowser); + ui->verticalLayout_2->addWidget(pageWithBackButton); + pageWithBackButton->hide(); + connect(pageWithBackButton, SIGNAL(backReleased()), this, SLOT(backClickedFromChild())); + scheduleStatusPageUpdates(); + + QObject::connect(ui->statusPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); + showStatusMainPage(); + QObject::connect(statusButtonsUI->mainPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); + QObject::connect(statusButtonsUI->routerCommandsPushButton, SIGNAL(released()), this, SLOT(showStatus_commands_Page())); + QObject::connect(statusButtonsUI->localDestinationsPushButton, SIGNAL(released()), this, SLOT(showStatus_local_destinations_Page())); + QObject::connect(statusButtonsUI->leasesetsPushButton, SIGNAL(released()), this, SLOT(showStatus_leasesets_Page())); + QObject::connect(statusButtonsUI->tunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_tunnels_Page())); + QObject::connect(statusButtonsUI->transitTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_transit_tunnels_Page())); + QObject::connect(statusButtonsUI->transportsPushButton, SIGNAL(released()), this, SLOT(showStatus_transports_Page())); + QObject::connect(statusButtonsUI->i2pTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_i2p_tunnels_Page())); + QObject::connect(statusButtonsUI->samSessionsPushButton, SIGNAL(released()), this, SLOT(showStatus_sam_sessions_Page())); + + QObject::connect(textBrowser, SIGNAL(mouseReleased()), this, SLOT(statusHtmlPageMouseReleased())); + QObject::connect(textBrowser, SIGNAL(selectionChanged()), this, SLOT(statusHtmlPageSelectionChanged())); + + QObject::connect(routerCommandsUI->runPeerTestPushButton, SIGNAL(released()), this, SLOT(runPeerTest())); + QObject::connect(routerCommandsUI->acceptTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(enableTransit())); + QObject::connect(routerCommandsUI->declineTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(disableTransit())); + + QObject::connect(ui->logViewerPushButton, SIGNAL(released()), this, SLOT(showLogViewerPage())); + + QObject::connect(ui->settingsPagePushButton, SIGNAL(released()), this, SLOT(showSettingsPage())); + + QObject::connect(ui->tunnelsPagePushButton, SIGNAL(released()), this, SLOT(showTunnelsPage())); + QObject::connect(ui->restartPagePushButton, SIGNAL(released()), this, SLOT(showRestartPage())); + QObject::connect(ui->quitPagePushButton, SIGNAL(released()), this, SLOT(showQuitPage())); + + QObject::connect(ui->fastQuitPushButton, SIGNAL(released()), this, SLOT(handleQuitButton())); + QObject::connect(ui->gracefulQuitPushButton, SIGNAL(released()), this, SLOT(handleGracefulQuitButton())); + + QObject::connect(ui->doRestartI2PDPushButton, SIGNAL(released()), this, SLOT(handleDoRestartButton())); + +# define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) + + initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); + initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); + initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); + + initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); + daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); + serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); + + uiSettings->logDestinationComboBox->clear(); + uiSettings->logDestinationComboBox->insertItems(0, QStringList() + << QApplication::translate("MainWindow", "syslog", 0) + << QApplication::translate("MainWindow", "stdout", 0) + << QApplication::translate("MainWindow", "file", 0) + ); + initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); + + logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); + initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); + + initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); + initTCPPortBox( OPTION("","port",[]{return "";}), uiSettings->routerExternalPortLineEdit, tr("Router external address -> Port")); + + initCheckBox( OPTION("","ipv6",[]{return "false";}), uiSettings->ipv6CheckBox); + initCheckBox( OPTION("","notransit",[]{return "false";}), uiSettings->notransitCheckBox); + initCheckBox( OPTION("","floodfill",[]{return "false";}), uiSettings->floodfillCheckBox); + initStringBox( OPTION("","bandwidth",[]{return "";}), uiSettings->bandwidthLineEdit); + initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); + initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); + +#ifdef Q_OS_WIN + initCheckBox( OPTION("","insomnia",[]{return "";}), uiSettings->insomniaCheckBox); + initNonGUIOption( OPTION("","svcctl",[]{return "";})); + initNonGUIOption( OPTION("","close",[]{return "";})); +#else + uiSettings->insomniaCheckBox->setEnabled(false); +#endif + + initCheckBox( OPTION("http","enabled",[]{return "true";}), uiSettings->webconsoleEnabledCheckBox); + initIPAddressBox( OPTION("http","address",[]{return "";}), uiSettings->webconsoleAddrLineEdit, tr("HTTP webconsole -> IP address")); + initTCPPortBox( OPTION("http","port",[]{return "7070";}), uiSettings->webconsolePortLineEdit, tr("HTTP webconsole -> Port")); + initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); + initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); + initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); + + initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); + initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); + initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); + initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); + + initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); + initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); + initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); + initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); + initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); + + initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); + initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); + initTCPPortBox( OPTION("socksproxy","port",[]{return "4447";}), uiSettings->socksProxyPortLineEdit, tr("Socks proxy -> Port")); + initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton); + initSignatureTypeCombobox(OPTION("socksproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_socksProxySignatureType); + initStringBox( OPTION("socksproxy","inbound.length",[]{return "";}), uiSettings->socksProxyInboundTunnelsLenLineEdit); + initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); + initStringBox( OPTION("socksproxy","outbound.length",[]{return "";}), uiSettings->socksProxyOutBoundTunnLenLineEdit); + initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); + initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); + initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); + + initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); + initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); + initTCPPortBox( OPTION("sam","port",[]{return "7656";}), uiSettings->samPortLineEdit, tr("SAM -> Port")); + + initCheckBox( OPTION("bob","enabled",[]{return "false";}), uiSettings->bobEnabledCheckBox); + initIPAddressBox( OPTION("bob","address",[]{return "";}), uiSettings->bobAddressLineEdit, tr("BOB -> IP address")); + initTCPPortBox( OPTION("bob","port",[]{return "2827";}), uiSettings->bobPortLineEdit, tr("BOB -> Port")); + + initCheckBox( OPTION("i2cp","enabled",[]{return "false";}), uiSettings->i2cpEnabledCheckBox); + initIPAddressBox( OPTION("i2cp","address",[]{return "";}), uiSettings->i2cpAddressLineEdit, tr("I2CP -> IP address")); + initTCPPortBox( OPTION("i2cp","port",[]{return "7654";}), uiSettings->i2cpPortLineEdit, tr("I2CP -> Port")); + + initCheckBox( OPTION("i2pcontrol","enabled",[]{return "false";}), uiSettings->i2pControlEnabledCheckBox); + initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); + initTCPPortBox( OPTION("i2pcontrol","port",[]{return "7650";}), uiSettings->i2pControlPortLineEdit, tr("I2PControl -> Port")); + initStringBox( OPTION("i2pcontrol","password",[]{return "";}), uiSettings->i2pControlPasswordLineEdit); + initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton); + initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton); + + initCheckBox( OPTION("upnp","enabled",[]{return "true";}), uiSettings->enableUPnPCheckBox); + initStringBox( OPTION("upnp","name",[]{return "I2Pd";}), uiSettings->upnpNameLineEdit); + + initCheckBox( OPTION("precomputation","elgamal",[]{return "false";}), uiSettings->useElGamalPrecomputedTablesCheckBox); + + initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); + initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); + initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); + + initStringBox( OPTION("addressbook","defaulturl",[]{return "";}), uiSettings->addressbookDefaultURLLineEdit); + initStringBox( OPTION("addressbook","subscriptions",[]{return "";}), uiSettings->addressbookSubscriptionsURLslineEdit); + + initUInt16Box( OPTION("limits","transittunnels",[]{return "2500";}), uiSettings->maxNumOfTransitTunnelsLineEdit, tr("maxNumberOfTransitTunnels")); + initUInt16Box( OPTION("limits","openfiles",[]{return "0";}), uiSettings->maxNumOfOpenFilesLineEdit, tr("maxNumberOfOpenFiles")); + initUInt32Box( OPTION("limits","coresize",[]{return "0";}), uiSettings->coreFileMaxSizeNumberLineEdit, tr("coreFileMaxSize")); + + initCheckBox( OPTION("trust","enabled",[]{return "false";}), uiSettings->checkBoxTrustEnable); + initStringBox( OPTION("trust","family",[]{return "";}), uiSettings->lineEditTrustFamily); + initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); + initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); + + initCheckBox( OPTION("websockets","enabled",[]{return "false";}), uiSettings->checkBoxWebsocketsEnable); + initIPAddressBox( OPTION("websockets","address",[]{return "127.0.0.1";}), uiSettings->lineEdit_webSock_addr, tr("Websocket server -> IP address")); + initTCPPortBox( OPTION("websockets","port",[]{return "7666";}), uiSettings->lineEdit_webSock_port, tr("Websocket server -> Port")); + +# undef OPTION + + //widgetlocks.add(new widgetlock(widget,lockbtn)); + widgetlocks.add(new widgetlock(uiSettings->logDestinationComboBox,uiSettings->logDestComboEditPushButton)); + widgetlocks.add(new widgetlock(uiSettings->logLevelComboBox,uiSettings->logLevelComboEditPushButton)); + widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); + widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); + + loadAllConfigs(); + + QObject::connect(uiSettings->logDestinationComboBox, SIGNAL(currentIndexChanged(const QString &)), + this, SLOT(logDestinationComboBoxValueChanged(const QString &))); + logDestinationComboBoxValueChanged(uiSettings->logDestinationComboBox->currentText()); + + ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, 451)); + + appendTunnelForms(""); + + uiSettings->configFileLineEdit->setEnabled(false); + uiSettings->configFileBrowsePushButton->setEnabled(false); + uiSettings->configFileLineEdit->setText(confpath); + uiSettings->tunnelsConfigFileLineEdit->setText(tunconfpath); + + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { + MainWindowItem* item = *it; + item->installListeners(this); + } + + QObject::connect(uiSettings->tunnelsConfigFileLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(reloadTunnelsConfigAndUI())); + + QObject::connect(ui->addServerTunnelPushButton, SIGNAL(released()), this, SLOT(addServerTunnelPushButtonReleased())); + QObject::connect(ui->addClientTunnelPushButton, SIGNAL(released()), this, SLOT(addClientTunnelPushButtonReleased())); + + +#ifndef ANDROID + QObject::connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + + setIcon(); + trayIcon->show(); +#endif + + logViewerManagerPtr=new LogViewerManager(logStream_,ui->logViewerTextEdit,this); + assert(logViewerManagerPtr!=nullptr); + onLoggingOptionsChange(); + //QMetaObject::connectSlotsByName(this); +} + +void MainWindow::logDestinationComboBoxValueChanged(const QString & text) { + bool fileEnabled = text==QString("file"); + uiSettings->logFileLineEdit->setEnabled(fileEnabled); + uiSettings->logFileBrowsePushButton->setEnabled(fileEnabled); +} + + +void MainWindow::updateRouterCommandsButtons() { + bool acceptsTunnels = i2p::context.AcceptsTunnels (); + routerCommandsUI->declineTransitTunnelsPushButton->setEnabled(acceptsTunnels); + routerCommandsUI->acceptTransitTunnelsPushButton->setEnabled(!acceptsTunnels); +} + +void MainWindow::showStatusPage(StatusPage newStatusPage){ + ui->stackedWidget->setCurrentIndex(0); + setStatusButtonsVisible(true); + statusPage=newStatusPage; + showHiddenInfoStatusMainPage=false; + if(newStatusPage!=StatusPage::commands){ + textBrowser->setHtml(getStatusPageHtml(false)); + textBrowser->show(); + routerCommandsParent->hide(); + pageWithBackButton->hide(); + }else{ + routerCommandsParent->show(); + textBrowser->hide(); + pageWithBackButton->hide(); + updateRouterCommandsButtons(); + } + wasSelectingAtStatusMainPage=false; +} +void MainWindow::showLogViewerPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} +void MainWindow::showSettingsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} +void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} +void MainWindow::showRestartPage(){ui->stackedWidget->setCurrentIndex(4);setStatusButtonsVisible(false);} +void MainWindow::showQuitPage(){ui->stackedWidget->setCurrentIndex(5);setStatusButtonsVisible(false);} + +void MainWindow::setStatusButtonsVisible(bool visible) { + ui->statusButtonsPane->setVisible(visible); +} + +// see also: HTTPServer.cpp +QString MainWindow::getStatusPageHtml(bool showHiddenInfo) { + std::stringstream s; + + s << ""; + + switch (statusPage) { + case main_page: + i2p::http::ShowStatus(s, showHiddenInfo, i2p::http::OutputFormatEnum::forQtUi); + break; + case commands: break; + case local_destinations: i2p::http::ShowLocalDestinations(s);break; + case leasesets: i2p::http::ShowLeasesSets(s); break; + case tunnels: i2p::http::ShowTunnels(s); break; + case transit_tunnels: i2p::http::ShowTransitTunnels(s); break; + case transports: i2p::http::ShowTransports(s); break; + case i2p_tunnels: i2p::http::ShowI2PTunnels(s); break; + case sam_sessions: i2p::http::ShowSAMSessions(s); break; + default: assert(false); break; + } + + std::string str = s.str(); + return QString::fromStdString(str); +} + +void MainWindow::showStatusMainPage() { showStatusPage(StatusPage::main_page); } +void MainWindow::showStatus_commands_Page() { showStatusPage(StatusPage::commands); } +void MainWindow::showStatus_local_destinations_Page() { showStatusPage(StatusPage::local_destinations); } +void MainWindow::showStatus_leasesets_Page() { showStatusPage(StatusPage::leasesets); } +void MainWindow::showStatus_tunnels_Page() { showStatusPage(StatusPage::tunnels); } +void MainWindow::showStatus_transit_tunnels_Page() { showStatusPage(StatusPage::transit_tunnels); } +void MainWindow::showStatus_transports_Page() { showStatusPage(StatusPage::transports); } +void MainWindow::showStatus_i2p_tunnels_Page() { showStatusPage(StatusPage::i2p_tunnels); } +void MainWindow::showStatus_sam_sessions_Page() { showStatusPage(StatusPage::sam_sessions); } + + +void MainWindow::scheduleStatusPageUpdates() { + statusPageUpdateTimer = new QTimer(this); + connect(statusPageUpdateTimer, SIGNAL(timeout()), this, SLOT(updateStatusPage())); + statusPageUpdateTimer->start(10*1000/*millis*/); +} + +void MainWindow::statusHtmlPageMouseReleased() { + if(wasSelectingAtStatusMainPage){ + QString selection = textBrowser->textCursor().selectedText(); + if(!selection.isEmpty()&&!selection.isNull())return; + } + showHiddenInfoStatusMainPage=!showHiddenInfoStatusMainPage; + textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); +} + +void MainWindow::statusHtmlPageSelectionChanged() { + wasSelectingAtStatusMainPage=true; +} + +void MainWindow::updateStatusPage() { + showHiddenInfoStatusMainPage=false; + textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); +} + + +//TODO +void MainWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + onResize(); +} + +//TODO +void MainWindow::onResize() +{ + if(isVisible()){ + ui->horizontalLayoutWidget->resize(ui->horizontalLayoutWidget->width(), height()); + + //status + ui->statusPage->resize(ui->statusPage->width(), height()); + + //tunnels + ui->tunnelsPage->resize(ui->tunnelsPage->width(), height()); + ui->verticalLayoutWidget_6->resize(ui->verticalLayoutWidget_6->width(), height()-20); + /*ui->tunnelsScrollArea->resize(ui->tunnelsScrollArea->width(), + ui->verticalLayoutWidget_6->height()-ui->label_5->height());*/ + } +} + +#ifndef ANDROID +void MainWindow::createActions() { + toggleWindowVisibleAction = new QAction(tr("&Toggle the window"), this); + connect(toggleWindowVisibleAction, SIGNAL(triggered()), this, SLOT(toggleVisibilitySlot())); + + //quitAction = new QAction(tr("&Quit"), this); + //connect(quitAction, SIGNAL(triggered()), QApplication::instance(), SLOT(quit())); +} + +void MainWindow::toggleVisibilitySlot() { + setVisible(!isVisible()); +} + +void MainWindow::createTrayIcon() { + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(toggleWindowVisibleAction); + //trayIconMenu->addSeparator(); + //trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); +} + +void MainWindow::setIcon() { + QIcon icon(":icons/mask"); + trayIcon->setIcon(icon); + setWindowIcon(icon); + + trayIcon->setToolTip(QApplication::translate("MainWindow", "i2pd", 0)); +} + +void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::MiddleClick: + setVisible(!isVisible()); + break; + default: + qDebug() << "MainWindow::iconActivated(): unknown reason: " << reason << endl; + break; + } +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if(quitting){ QMainWindow::closeEvent(event); return; } + if (trayIcon->isVisible()) { + QMessageBox::information(this, tr("i2pd"), + tr("The program will keep running in the " + "system tray. To gracefully terminate the program, " + "choose Graceful Quit at the main i2pd window.")); + hide(); + event->ignore(); + } +} +#endif + +void MainWindow::handleQuitButton() { + qDebug("Quit pressed. Hiding the main window"); +#ifndef ANDROID + quitting=true; +#endif + close(); + QApplication::instance()->quit(); +} + +void MainWindow::handleGracefulQuitButton() { + qDebug("Graceful Quit pressed."); + ui->gracefulQuitPushButton->setText(QApplication::translate("MainWindow", "Graceful quit is in progress", 0)); + ui->gracefulQuitPushButton->setEnabled(false); + ui->gracefulQuitPushButton->adjustSize(); + ui->quitPage->adjustSize(); + i2p::context.SetAcceptsTunnels (false); // stop accpting tunnels + QTimer::singleShot(10*60*1000//millis + , this, SLOT(handleGracefulQuitTimerEvent())); +} + +void MainWindow::handleDoRestartButton() { + qDebug()<<"Do Restart pressed."; + emit i2pController->restartDaemon(); +} + + +void MainWindow::handleGracefulQuitTimerEvent() { + qDebug("Hiding the main window"); +#ifndef ANDROID + quitting=true; +#endif + close(); + qDebug("Performing quit"); + QApplication::instance()->quit(); +} + +MainWindow::~MainWindow() +{ + qDebug("Destroying main window"); + delete statusPageUpdateTimer; + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { + MainWindowItem* item = *it; + item->deleteLater(); + } + configItems.clear(); + //QMessageBox::information(0, "Debug", "mw destructor 1"); + //delete ui; + //QMessageBox::information(0, "Debug", "mw destructor 2"); +} + +FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ + FileChooserItem* retVal; + retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton); + MainWindowItem* super=retVal; + configItems.append(super); + return retVal; +} +void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ + configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton)); +} +/*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ + configItems.append(new ComboBoxItem(option, comboBox)); + QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveAllConfigs())); +}*/ +void MainWindow::initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox){ + configItems.append(new LogDestinationComboBoxItem(option, comboBox)); +} +void MainWindow::initLogLevelCombobox(ConfigOption option, QComboBox* comboBox){ + configItems.append(new LogLevelComboBoxItem(option, comboBox)); +} +void MainWindow::initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox){ + configItems.append(new SignatureTypeComboBoxItem(option, comboBox)); +} +void MainWindow::initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated){ + configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated)); +} +void MainWindow::initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated){ + configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated)); +} +void MainWindow::initCheckBox(ConfigOption option, QCheckBox* checkBox) { + configItems.append(new CheckBoxItem(option, checkBox)); +} +void MainWindow::initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ + configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated)); +} +void MainWindow::initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ + configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated)); +} +void MainWindow::initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ + configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated)); +} +void MainWindow::initStringBox(ConfigOption option, QLineEdit* lineEdit){ + configItems.append(new BaseStringItem(option, lineEdit, QString())); +} +NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { + NonGUIOptionItem * retValue; + configItems.append(retValue=new NonGUIOptionItem(option)); + return retValue; +} + +void MainWindow::loadAllConfigs(){ + + //BORROWED FROM ??? //TODO move this code into single location + std::string config; i2p::config::GetOption("conf", config); + std::string datadir; i2p::config::GetOption("datadir", datadir); + bool service = false; +#ifndef _WIN32 + i2p::config::GetOption("service", service); +#endif + i2p::fs::DetectDataDir(datadir, service); + i2p::fs::Init(); + + datadir = i2p::fs::GetDataDir(); + // TODO: drop old name detection in v2.8.0 + if (config == "") + { + config = i2p::fs::DataDirPath("i2p.conf"); + if (i2p::fs::Exists (config)) { + LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); + } else { + config = i2p::fs::DataDirPath("i2pd.conf"); + /*if (!i2p::fs::Exists (config)) {}*/ + } + } + + //BORROWED FROM ClientContext.cpp //TODO move this code into single location + std::string tunConf; i2p::config::GetOption("tunconf", tunConf); + if (tunConf == "") { + // TODO: cleanup this in 2.8.0 + tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); + if (i2p::fs::Exists(tunConf)) { + LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); + } else { + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + } + } + + this->confpath = config.c_str(); + this->datadir = datadir.c_str(); + this->tunconfpath = tunConf.c_str(); + + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { + MainWindowItem* item = *it; + item->loadFromConfigOption(); + } + + ReadTunnelsConfig(); + + onLoggingOptionsChange(); +} +/** returns false iff not valid items present and save was aborted */ +bool MainWindow::saveAllConfigs(){ + QString cannotSaveSettings = QApplication::tr("Cannot save settings."); + programOptionsWriterCurrentSection=""; + /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); + else logOption->optionValue=boost::any(std::string("stdout"));*/ + daemonOption->optionValue=boost::any(false); + serviceOption->optionValue=boost::any(false); + + std::stringstream out; + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { + MainWindowItem* item = *it; + if(!item->isValid()){ + highlightWrongInput(QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, item->getWidgetToFocus()); + return false; + } + } + + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { + MainWindowItem* item = *it; + item->saveToStringStream(out); + } + + using namespace std; + + + QString backup=confpath+"~"; + if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors + if(QFile::exists(confpath)) QFile::rename(confpath, backup);//TODO handle errors + ofstream outfile; + outfile.open(confpath.toStdString());//TODO handle errors + outfile << out.str().c_str(); + outfile.close(); + + SaveTunnelsConfig(); + + onLoggingOptionsChange(); + + return true; +} + +void FileChooserItem::pushButtonReleased() { + QString fileName = lineEdit->text().trimmed(); + fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); + if(fileName.length()>0)lineEdit->setText(fileName); +} +void FolderChooserItem::pushButtonReleased() { + QString fileName = lineEdit->text().trimmed(); + fileName = QFileDialog::getExistingDirectory(nullptr, tr("Open Folder"), fileName); + if(fileName.length()>0)lineEdit->setText(fileName); +} + +void BaseStringItem::installListeners(MainWindow *mainWindow) { + QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), mainWindow, SLOT(updated())); +} +void ComboBoxItem::installListeners(MainWindow *mainWindow) { + QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), mainWindow, SLOT(updated())); +} +void CheckBoxItem::installListeners(MainWindow *mainWindow) { + QObject::connect(checkBox, SIGNAL(stateChanged(int)), mainWindow, SLOT(updated())); +} + +void MainWindow::updated() { + ui->wrongInputLabel->setVisible(false); + adjustSizesAccordingToWrongLabel(); + + applyTunnelsUiToConfigs(); + saveAllConfigs(); +} + +void MainWindowItem::installListeners(MainWindow *mainWindow) {} + +void MainWindow::appendTunnelForms(std::string tunnelNameToFocus) { + int height=0; + ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); + for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { + const std::string& name=it->first; + TunnelConfig* tunconf = it->second; + ServerTunnelConfig* stc = tunconf->asServerTunnelConfig(); + if(stc){ + ServerTunnelPane * tunnelPane=new ServerTunnelPane(&tunnelsPageUpdateListener, stc, ui->wrongInputLabel, ui->wrongInputLabel, this); + int h=tunnelPane->appendServerTunnelForm(stc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); + height+=h; + //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); + tunnelPanes.push_back(tunnelPane); + if(name==tunnelNameToFocus){ + tunnelPane->getNameLineEdit()->setFocus(); + ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); + } + continue; + } + ClientTunnelConfig* ctc = tunconf->asClientTunnelConfig(); + if(ctc){ + ClientTunnelPane * tunnelPane=new ClientTunnelPane(&tunnelsPageUpdateListener, ctc, ui->wrongInputLabel, ui->wrongInputLabel, this); + int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); + height+=h; + //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); + tunnelPanes.push_back(tunnelPane); + if(name==tunnelNameToFocus){ + tunnelPane->getNameLineEdit()->setFocus(); + ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); + } + continue; + } + throw "unknown TunnelConfig subtype"; + } + //qDebug() << "tun.setting height:" << height; + ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, height)); + QList childWidgets = ui->tunnelsScrollAreaWidgetContents->findChildren(); + foreach(QWidget* widget, childWidgets) + widget->show(); +} +void MainWindow::deleteTunnelForms() { + for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { + TunnelPane* tp = *it; + ServerTunnelPane* stp = tp->asServerTunnelPane(); + if(stp){ + stp->deleteServerTunnelForm(); + delete stp; + continue; + } + ClientTunnelPane* ctp = tp->asClientTunnelPane(); + if(ctp){ + ctp->deleteClientTunnelForm(); + delete ctp; + continue; + } + throw "unknown TunnelPane subtype"; + } + tunnelPanes.clear(); +} + +bool MainWindow::applyTunnelsUiToConfigs() { + for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { + TunnelPane* tp = *it; + if(!tp->applyDataFromUIToTunnelConfig())return false; + } + return true; +} + +void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { + deleteTunnelForms(); + for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { + TunnelConfig* tunconf = it->second; + delete tunconf; + } + tunnelConfigs.clear(); + ReadTunnelsConfig(); + appendTunnelForms(tunnelNameToFocus); +} + +void MainWindow::SaveTunnelsConfig() { + std::stringstream out; + + for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { + const std::string& name = it->first; + TunnelConfig* tunconf = it->second; + tunconf->saveHeaderToStringStream(out); + tunconf->saveToStringStream(out); + tunconf->saveI2CPParametersToStringStream(out); + } + + using namespace std; + + QString backup=tunconfpath+"~"; + if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors + if(QFile::exists(tunconfpath)) QFile::rename(tunconfpath, backup);//TODO handle errors + ofstream outfile; + outfile.open(tunconfpath.toStdString());//TODO handle errors + outfile << out.str().c_str(); + outfile.close(); + + i2p::client::context.ReloadConfig(); + +} + +void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string oldName, TunnelConfig* tunConf) { + if(oldName!=tunConf->getName()) { + //name has changed + std::map::const_iterator it=mainWindow->tunnelConfigs.find(oldName); + if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); + mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; + } + mainWindow->saveAllConfigs(); +} + +void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ + mainWindow->DeleteTunnelNamed(oldName); +} + +void MainWindow::addServerTunnelPushButtonReleased() { + CreateDefaultServerTunnel(); +} + +void MainWindow::addClientTunnelPushButtonReleased() { + CreateDefaultClientTunnel(); +} + +void MainWindow::setI2PController(i2p::qt::Controller* controller_) { + this->i2pController = controller_; +} + +void MainWindow::runPeerTest() { + i2p::transport::transports.PeerTest(); +} + +void MainWindow::enableTransit() { + i2p::context.SetAcceptsTunnels(true); + updateRouterCommandsButtons(); +} + +void MainWindow::disableTransit() { + i2p::context.SetAcceptsTunnels(false); + updateRouterCommandsButtons(); +} + +void MainWindow::anchorClickedHandler(const QUrl & link) { + QString debugStr=QString()+"anchorClicked: "+"\""+link.toString()+"\""; + qDebug()<show(); + textBrowser->hide(); + std::stringstream s; + i2p::http::ShowLocalDestination(s,str.toStdString()); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } +} + +void MainWindow::backClickedFromChild() { + showStatusPage(statusPage); +} + +void MainWindow::adjustSizesAccordingToWrongLabel() { + if(ui->wrongInputLabel->isVisible()) { + int dh = ui->wrongInputLabel->height()+ui->verticalLayout_7->layout()->spacing(); + ui->verticalLayout_7->invalidate(); + ui->wrongInputLabel->adjustSize(); + ui->stackedWidget->adjustSize(); + ui->stackedWidget->setFixedHeight(531-dh); + ui->settingsPage->setFixedHeight(531-dh); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531-dh)); + ui->stackedWidget->setFixedHeight(531-dh); + ui->settingsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); + ui->tunnelsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); + }else{ + ui->verticalLayout_7->invalidate(); + ui->wrongInputLabel->adjustSize(); + ui->stackedWidget->adjustSize(); + ui->stackedWidget->setFixedHeight(531); + ui->settingsPage->setFixedHeight(531); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531)); + ui->stackedWidget->setFixedHeight(531); + ui->settingsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); + ui->tunnelsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); + } +} + +void MainWindow::highlightWrongInput(QString warningText, QWidget* widgetToFocus) { + bool redVisible = ui->wrongInputLabel->isVisible(); + ui->wrongInputLabel->setVisible(true); + ui->wrongInputLabel->setText(warningText); + if(!redVisible)adjustSizesAccordingToWrongLabel(); + if(widgetToFocus){ui->settingsScrollArea->ensureWidgetVisible(widgetToFocus);widgetToFocus->setFocus();} + showSettingsPage(); +} diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h new file mode 100644 index 00000000..048f44af --- /dev/null +++ b/qt/i2pd_qt/mainwindow.h @@ -0,0 +1,799 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "QVBoxLayout" +#include "QUrl" + +#ifndef ANDROID +# include +# include +# include +#endif + +#include + +#include + +#include "MainWindowItems.h" +#include "TunnelPane.h" +#include "ServerTunnelPane.h" +#include "ClientTunnelPane.h" +#include "TunnelConfig.h" +#include "textbrowsertweaked1.h" + +#include "Config.h" +#include "FS.h" + +#include + +#include +#include + +#include "TunnelsPageUpdateListener.h" + +#include "DaemonQT.h" +#include "SignatureTypeComboboxFactory.h" +#include "pagewithbackbutton.h" + +#include + +#include "widgetlockregistry.h" +#include "widgetlock.h" + +class LogViewerManager; + +template +bool isType(boost::any& a) { + return +#ifdef BOOST_AUX_ANY_TYPE_ID_NAME + std::strcmp(a.type().name(), typeid(ValueType).name()) == 0 +#else + a.type() == typeid(ValueType) +#endif + ; +} + +class ConfigOption { +public: + QString section; + QString option; + //MainWindow::DefaultValueGetter defaultValueGetter; + ConfigOption(QString section_, QString option_/*, DefaultValueGetter defaultValueGetter_*/): + section(section_) + , option(option_) + //, defaultValueGetter(defaultValueGetter_) + {} + +}; + +extern std::string programOptionsWriterCurrentSection; + +class MainWindow; + +class MainWindowItem : public QObject { + Q_OBJECT + ConfigOption option; + QWidget* widgetToFocus; + QString requirementToBeValid; +public: + MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} + QWidget* getWidgetToFocus(){return widgetToFocus;} + QString& getRequirementToBeValid() { return requirementToBeValid; } + ConfigOption& getConfigOption() { return option; } + boost::any optionValue; + virtual ~MainWindowItem(){} + virtual void installListeners(MainWindow *mainWindow); + virtual void loadFromConfigOption(){ + std::string optName=""; + if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); + optName+=option.option.toStdString(); + //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; + boost::any programOption; + i2p::config::GetOptionAsAny(optName, programOption); + optionValue=programOption.empty()?boost::any(std::string("")) + :boost::any_cast(programOption).value(); + } + virtual void saveToStringStream(std::stringstream& out){ + if(isType(optionValue)) { + std::string v = boost::any_cast(optionValue); + if(v.empty())return; + } + if(optionValue.empty())return; + std::string rtti = optionValue.type().name(); + std::string optName=""; + if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); + optName+=option.option.toStdString(); + qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); + std::string sectionAsStdStr = option.section.toStdString(); + if(!option.section.isEmpty() && + sectionAsStdStr!=programOptionsWriterCurrentSection) { + out << "[" << sectionAsStdStr << "]\n"; + programOptionsWriterCurrentSection=sectionAsStdStr; + } + out << option.option.toStdString() << "="; + if(isType(optionValue)) { + out << boost::any_cast(optionValue); + }else if(isType(optionValue)) { + out << (boost::any_cast(optionValue) ? "true" : "false"); + }else if(isType(optionValue)) { + out << boost::any_cast(optionValue); + }else if(isType(optionValue)) { + out << boost::any_cast(optionValue); + }else if(isType(optionValue)) { + out << boost::any_cast(optionValue); + }else if(isType(optionValue)) { + out << boost::any_cast(optionValue); + }else out << boost::any_cast(optionValue); //let it throw + out << "\n\n"; + } + virtual bool isValid(){return true;} +}; +class NonGUIOptionItem : public MainWindowItem { +public: + NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {}; + virtual ~NonGUIOptionItem(){} + virtual bool isValid() { return true; } +}; +class BaseStringItem : public MainWindowItem { + Q_OBJECT +public: + QLineEdit* lineEdit; + BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_) : MainWindowItem(option_, lineEdit_, requirementToBeValid_), lineEdit(lineEdit_){}; + virtual ~BaseStringItem(){} + virtual void installListeners(MainWindow *mainWindow); + virtual QString toString(){ + return boost::any_cast(optionValue).c_str(); + } + virtual boost::any fromString(QString s){return boost::any(s.toStdString());} + virtual void loadFromConfigOption(){ + MainWindowItem::loadFromConfigOption(); + lineEdit->setText(toString()); + } + + virtual void saveToStringStream(std::stringstream& out){ + optionValue=fromString(lineEdit->text()); + MainWindowItem::saveToStringStream(out); + } + virtual bool isValid() { return true; } +}; +class FileOrFolderChooserItem : public BaseStringItem { +public: + QPushButton* browsePushButton; + FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : + BaseStringItem(option_, lineEdit_, QString()), browsePushButton(browsePushButton_) {} + virtual ~FileOrFolderChooserItem(){} +}; +class FileChooserItem : public FileOrFolderChooserItem { + Q_OBJECT +private slots: + void pushButtonReleased(); +public: + FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); + } +}; +class FolderChooserItem : public FileOrFolderChooserItem{ + Q_OBJECT +private slots: + void pushButtonReleased(); +public: + FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); + } +}; +class ComboBoxItem : public MainWindowItem { +public: + QComboBox* comboBox; + ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){}; + virtual ~ComboBoxItem(){} + virtual void installListeners(MainWindow *mainWindow); + virtual void loadFromConfigOption()=0; + virtual void saveToStringStream(std::stringstream& out)=0; + virtual bool isValid() { return true; } +}; +class LogDestinationComboBoxItem : public ComboBoxItem { +public: + LogDestinationComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : + ComboBoxItem(option_, comboBox_) {} + virtual ~LogDestinationComboBoxItem(){} + virtual void loadFromConfigOption(){ + MainWindowItem::loadFromConfigOption(); + const char * ld = boost::any_cast(optionValue).c_str(); + comboBox->setCurrentText(QString(ld)); + } + virtual void saveToStringStream(std::stringstream& out){ + std::string logDest = comboBox->currentText().toStdString(); + optionValue=logDest; + MainWindowItem::saveToStringStream(out); + } + virtual bool isValid() { return true; } + + Q_OBJECT +}; +class LogLevelComboBoxItem : public ComboBoxItem { +public: + LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + virtual ~LogLevelComboBoxItem(){} + virtual void loadFromConfigOption(){ + MainWindowItem::loadFromConfigOption(); + const char * ll = boost::any_cast(optionValue).c_str(); + comboBox->setCurrentText(QString(ll)); + } + virtual void saveToStringStream(std::stringstream& out){ + optionValue=comboBox->currentText().toStdString(); + MainWindowItem::saveToStringStream(out); + } + virtual bool isValid() { return true; } +}; +class SignatureTypeComboBoxItem : public ComboBoxItem { +public: + SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + virtual ~SignatureTypeComboBoxItem(){} + virtual void loadFromConfigOption(){ + MainWindowItem::loadFromConfigOption(); + while(comboBox->count()>0)comboBox->removeItem(0); + uint16_t selected = (uint16_t) boost::any_cast(optionValue); + SignatureTypeComboBoxFactory::fillComboBox(comboBox, selected); + } + virtual void saveToStringStream(std::stringstream& out){ + uint16_t selected = SignatureTypeComboBoxFactory::getSigType(comboBox->currentData()); + optionValue=(unsigned short)selected; + MainWindowItem::saveToStringStream(out); + } + virtual bool isValid() { return true; } +}; +class CheckBoxItem : public MainWindowItem { +public: + QCheckBox* checkBox; + CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){}; + virtual ~CheckBoxItem(){} + virtual void installListeners(MainWindow *mainWindow); + virtual void loadFromConfigOption(){ + MainWindowItem::loadFromConfigOption(); + checkBox->setChecked(boost::any_cast(optionValue)); + } + virtual void saveToStringStream(std::stringstream& out){ + optionValue=checkBox->isChecked(); + MainWindowItem::saveToStringStream(out); + } + virtual bool isValid() { return true; } +}; +class BaseFormattedStringItem : public BaseStringItem { +public: + QString fieldNameTranslated; + BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_) : + BaseStringItem(option_, lineEdit_, requirementToBeValid_), fieldNameTranslated(fieldNameTranslated_) {}; + virtual ~BaseFormattedStringItem(){} + virtual bool isValid()=0; +}; +class IntegerStringItem : public BaseFormattedStringItem { +public: + IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer.")) {}; + virtual ~IntegerStringItem(){} + virtual bool isValid(){ + auto str=lineEdit->text(); + bool ok; + str.toInt(&ok); + return ok; + } + virtual QString toString(){return QString::number(boost::any_cast(optionValue));} + virtual boost::any fromString(QString s){return boost::any(std::stoi(s.toStdString()));} +}; +class UShortStringItem : public BaseFormattedStringItem { +public: + UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer.")) {}; + virtual ~UShortStringItem(){} + virtual bool isValid(){ + auto str=lineEdit->text(); + bool ok; + str.toUShort(&ok); + return ok; + } + virtual QString toString(){return QString::number(boost::any_cast(optionValue));} + virtual boost::any fromString(QString s){return boost::any((unsigned short)std::stoi(s.toStdString()));} +}; +class UInt32StringItem : public BaseFormattedStringItem { +public: + UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer.")) {}; + virtual ~UInt32StringItem(){} + virtual bool isValid(){ + auto str=lineEdit->text(); + bool ok; + str.toUInt(&ok); + return ok; + } + virtual QString toString(){return QString::number(boost::any_cast(optionValue));} + virtual boost::any fromString(QString s){return boost::any((uint32_t)std::stoi(s.toStdString()));} +}; +class UInt16StringItem : public BaseFormattedStringItem { +public: + UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer.")) {}; + virtual ~UInt16StringItem(){} + virtual bool isValid(){ + auto str=lineEdit->text(); + bool ok; + str.toUShort(&ok); + return ok; + } + virtual QString toString(){return QString::number(boost::any_cast(optionValue));} + virtual boost::any fromString(QString s){return boost::any((uint16_t)std::stoi(s.toStdString()));} +}; +class IPAddressStringItem : public BaseFormattedStringItem { +public: + IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address")) {}; + virtual bool isValid(){return true;}//todo +}; +class TCPPortStringItem : public UShortStringItem { +public: + TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : + UShortStringItem(option_, lineEdit_, fieldNameTranslated_) {}; +}; + +namespace Ui { + class MainWindow; + class StatusButtonsForm; + class routerCommandsWidget; + class GeneralSettingsContentsForm; +} + +using namespace i2p::client; + +class TunnelPane; + +using namespace i2p::qt; + +class Controller; + +class MainWindow : public QMainWindow { + Q_OBJECT +private: + std::shared_ptr logStream; +public: + explicit MainWindow(std::shared_ptr logStream_, QWidget *parent=nullptr); + ~MainWindow(); + + void setI2PController(i2p::qt::Controller* controller_); + + void highlightWrongInput(QString warningText, QWidget* widgetToFocus); + + //typedef std::function DefaultValueGetter; + +//#ifndef ANDROID +// void setVisible(bool visible); +//#endif + +private: + enum StatusPage {main_page, commands, local_destinations, leasesets, tunnels, transit_tunnels, + transports, i2p_tunnels, sam_sessions}; +private slots: + void updated(); + + void handleQuitButton(); + void handleGracefulQuitButton(); + void handleDoRestartButton(); + void handleGracefulQuitTimerEvent(); +#ifndef ANDROID + void setIcon(); + void iconActivated(QSystemTrayIcon::ActivationReason reason); + void toggleVisibilitySlot(); +#endif + void scheduleStatusPageUpdates(); + void statusHtmlPageMouseReleased(); + void statusHtmlPageSelectionChanged(); + void updateStatusPage(); + + void showStatusMainPage(); + void showStatus_commands_Page(); + void runPeerTest(); + void enableTransit(); + void disableTransit(); +public slots: + void showStatus_local_destinations_Page(); + void showStatus_leasesets_Page(); + void showStatus_tunnels_Page(); + void showStatus_transit_tunnels_Page(); + void showStatus_transports_Page(); + void showStatus_i2p_tunnels_Page(); + void showStatus_sam_sessions_Page(); + + void showLogViewerPage(); + void showSettingsPage(); + void showTunnelsPage(); + void showRestartPage(); + void showQuitPage(); + +private: + StatusPage statusPage; + QTimer * statusPageUpdateTimer; + bool wasSelectingAtStatusMainPage; + bool showHiddenInfoStatusMainPage; + + LogViewerManager *logViewerManagerPtr; + + void showStatusPage(StatusPage newStatusPage); +#ifndef ANDROID + void createActions(); + void createTrayIcon(); + bool quitting; + QAction *toggleWindowVisibleAction; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; +#endif + +public: + Ui::MainWindow* ui; + Ui::StatusButtonsForm* statusButtonsUI; + Ui::routerCommandsWidget* routerCommandsUI; + Ui::GeneralSettingsContentsForm* uiSettings; + void adjustSizesAccordingToWrongLabel(); + bool applyTunnelsUiToConfigs(); +private: + int settingsTitleLabelNominalHeight; + TextBrowserTweaked1 * textBrowser; + QWidget * routerCommandsParent; + PageWithBackButton * pageWithBackButton; + TextBrowserTweaked1 * childTextBrowser; + + widgetlockregistry widgetlocks; + + i2p::qt::Controller* i2pController; + +protected: + + void updateRouterCommandsButtons(); + +#ifndef ANDROID + void closeEvent(QCloseEvent *event); +#endif + void resizeEvent(QResizeEvent* event); + void onResize(); + + void setStatusButtonsVisible(bool visible); + + QString getStatusPageHtml(bool showHiddenInfo); + + QList configItems; + NonGUIOptionItem* daemonOption; + NonGUIOptionItem* serviceOption; + //LogDestinationComboBoxItem* logOption; + FileChooserItem* logFileNameOption; + + FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton); + void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); + //void initCombobox(ConfigOption option, QComboBox* comboBox); + void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); + void initLogLevelCombobox(ConfigOption option, QComboBox* comboBox); + void initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox); + void initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated); + void initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated); + void initCheckBox(ConfigOption option, QCheckBox* checkBox); + void initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); + void initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); + void initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); + void initStringBox(ConfigOption option, QLineEdit* lineEdit); + NonGUIOptionItem* initNonGUIOption(ConfigOption option); + + void loadAllConfigs(); + +public slots: + /** returns false iff not valid items present and save was aborted */ + bool saveAllConfigs(); + void SaveTunnelsConfig(); + void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); + + //focus none + void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } + void addServerTunnelPushButtonReleased(); + void addClientTunnelPushButtonReleased(); + + void anchorClickedHandler(const QUrl & link); + void backClickedFromChild(); + + void logDestinationComboBoxValueChanged(const QString & text); + +private: + QString datadir; + QString confpath; + QString tunconfpath; + + std::map tunnelConfigs; + std::list tunnelPanes; + + void appendTunnelForms(std::string tunnelNameToFocus); + void deleteTunnelForms(); + + template + std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const + { + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); + } + + template + void ReadI2CPOptions (const Section& section, std::map& options, I2CPParameters& param + /*TODO fill param*/) const + { + std::string _INBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + param.setInbound_length(QString(_INBOUND_TUNNEL_LENGTH.c_str())); + std::string _OUTBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); + param.setOutbound_length(QString(_OUTBOUND_TUNNEL_LENGTH.c_str())); + std::string _INBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); + param.setInbound_quantity( QString(_INBOUND_TUNNELS_QUANTITY.c_str())); + std::string _OUTBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + param.setOutbound_quantity(QString(_OUTBOUND_TUNNELS_QUANTITY.c_str())); + std::string _TAGS_TO_SEND = options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); + param.setCrypto_tagsToSend(QString(_TAGS_TO_SEND.c_str())); + options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);//TODO include into param + options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);//TODO include into param + } + + void CreateDefaultI2CPOptions (I2CPParameters& param + /*TODO fill param*/) const + { + const int _INBOUND_TUNNEL_LENGTH = DEFAULT_INBOUND_TUNNEL_LENGTH; + param.setInbound_length(QString::number(_INBOUND_TUNNEL_LENGTH)); + const int _OUTBOUND_TUNNEL_LENGTH = DEFAULT_OUTBOUND_TUNNEL_LENGTH; + param.setOutbound_length(QString::number(_OUTBOUND_TUNNEL_LENGTH)); + const int _INBOUND_TUNNELS_QUANTITY = DEFAULT_INBOUND_TUNNELS_QUANTITY; + param.setInbound_quantity( QString::number(_INBOUND_TUNNELS_QUANTITY)); + const int _OUTBOUND_TUNNELS_QUANTITY = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; + param.setOutbound_quantity(QString::number(_OUTBOUND_TUNNELS_QUANTITY)); + const int _TAGS_TO_SEND = DEFAULT_TAGS_TO_SEND; + param.setCrypto_tagsToSend(QString::number(_TAGS_TO_SEND)); + } + + + void DeleteTunnelNamed(std::string name) { + std::map::const_iterator it=tunnelConfigs.find(name); + if(it!=tunnelConfigs.end()){ + TunnelConfig* tc=it->second; + tunnelConfigs.erase(it); + delete tc; + } + saveAllConfigs(); + reloadTunnelsConfigAndUI(""); + } + + std::string GenerateNewTunnelName() { + int i=1; + while(true){ + std::stringstream name; + name << "name" << i; + const std::string& str=name.str(); + if(tunnelConfigs.find(str)==tunnelConfigs.end())return str; + ++i; + } + } + + void CreateDefaultClientTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () + std::string name=GenerateNewTunnelName(); + std::string type = I2P_TUNNELS_SECTION_TYPE_CLIENT; + std::string dest = "127.0.0.1"; + int port = 0; + std::string keys = ""; + std::string address = "127.0.0.1"; + int destinationPort = 0; + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; + // I2CP + I2CPParameters i2cpParameters; + CreateDefaultI2CPOptions (i2cpParameters); + + tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, + dest, + port, + keys, + address, + destinationPort, + sigType); + + saveAllConfigs(); + reloadTunnelsConfigAndUI(name); + } + + void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () + std::string name=GenerateNewTunnelName(); + std::string type=I2P_TUNNELS_SECTION_TYPE_SERVER; + std::string host = "127.0.0.1"; + int port = 0; + std::string keys = ""; + int inPort = 0; + std::string accessList = ""; + std::string hostOverride = ""; + std::string webircpass = ""; + bool gzip = true; + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; + std::string address = "127.0.0.1"; + bool isUniqueLocal = true; + + // I2CP + I2CPParameters i2cpParameters; + CreateDefaultI2CPOptions (i2cpParameters); + + tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, + host, + port, + keys, + inPort, + accessList, + hostOverride, + webircpass, + gzip, + sigType, + address, + isUniqueLocal); + + + saveAllConfigs(); + reloadTunnelsConfigAndUI(name); + } + + void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () + { + boost::property_tree::ptree pt; + std::string tunConf=tunconfpath.toStdString(); + if (tunConf == "") { + // TODO: cleanup this in 2.8.0 + tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); + if (i2p::fs::Exists(tunConf)) { + LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); + } else { + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + } + } + LogPrint(eLogDebug, "tunnels config file: ", tunConf); + try + { + boost::property_tree::read_ini (tunConf, pt); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());//TODO show err box and disable tunn.page + return; + } + + for (auto& section: pt) + { + std::string name = section.first; + try + { + std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); + if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT + || type == I2P_TUNNELS_SECTION_TYPE_SOCKS + || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS + || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY + || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) + { + // mandatory params + std::string dest; + if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { + dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); + std::cout << "had read tunnel dest: " << dest << std::endl; + } + int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); + std::cout << "had read tunnel port: " << port << std::endl; + // optional params + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); + 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); + std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; + i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + // I2CP + std::map options; + I2CPParameters i2cpParameters; + ReadI2CPOptions (section, options, i2cpParameters); + + tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, + dest, + port, + keys, + address, + destinationPort, + sigType); + } + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER + || type == I2P_TUNNELS_SECTION_TYPE_HTTP + || type == I2P_TUNNELS_SECTION_TYPE_IRC + || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) + { + // mandatory params + std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); + int port = section.second.get (I2P_SERVER_TUNNEL_PORT); + // optional params + std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS, ""); + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_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, true); + i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); + bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); + + // I2CP + std::map options; + I2CPParameters i2cpParameters; + ReadI2CPOptions (section, options, i2cpParameters); + + /* + std::set idents; + if (accessList.length () > 0) + { + size_t pos = 0, comma; + do + { + comma = accessList.find (',', pos); + i2p::data::IdentHash ident; + ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + idents.insert (ident); + pos = comma + 1; + } + while (comma != std::string::npos); + } + */ + tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, + host, + port, + keys, + inPort, + accessList, + hostOverride, + webircpass, + gzip, + sigType, + address, + isUniqueLocal); + } + else + LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);//TODO show err box and disable the tunn gui + + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ());//TODO show err box and disable the tunn gui + } + } + } + +private: + class TunnelsPageUpdateListenerMainWindowImpl : public TunnelsPageUpdateListener { + MainWindow* mainWindow; + public: + TunnelsPageUpdateListenerMainWindowImpl(MainWindow* mainWindow_):mainWindow(mainWindow_){} + virtual void updated(std::string oldName, TunnelConfig* tunConf); + virtual void needsDeleting(std::string oldName); + }; + + TunnelsPageUpdateListenerMainWindowImpl tunnelsPageUpdateListener; + + void onLoggingOptionsChange() {} +}; + +#endif // MAINWINDOW_H diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui new file mode 100644 index 00000000..dcdf88bd --- /dev/null +++ b/qt/i2pd_qt/mainwindow.ui @@ -0,0 +1,1026 @@ + + + MainWindow + + + + 0 + 0 + 908 + 554 + + + + + 908 + 0 + + + + + 908 + 16777215 + + + + MainWindow + + + + + 0 + 0 + + + + + 908 + 550 + + + + + 908 + 550 + + + + + + 10 + 10 + 888 + 531 + + + + + QLayout::SetMaximumSize + + + + + QLayout::SetMinimumSize + + + + 0 + 0 + 170 + 496 + + + + + + true + + + Status + + + + + + + + 0 + 0 + + + + + 172 + 0 + + + + + + + + Log + + + + + + + true + + + General settings + + + + + + + true + + + Tunnels settings + + + + + + + true + + + Restart + + + + + + + true + + + Quit + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 171 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + QLayout::SetMinAndMaxSize + + + + + + 0 + 30 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + wrongInputMessageLabel + + + true + + + 10 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 713 + 713 + + + + 1 + + + + + 0 + 0 + + + + + + 0 + 0 + 713 + 531 + + + + + QLayout::SetMaximumSize + + + + + + 15 + + + + Status + + + + + + + QLayout::SetMaximumSize + + + + + + + + + + 0 + 0 + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + Log + + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + 10000 + + + false + + + true + + + + + + + + + + 0 + 0 + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + General settings + + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 81 + 28 + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + Tunnels settings + + + + + + + + + Add Client Tunnel + + + + + + + Add Server Tunnel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::ScrollBarAlwaysOn + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 699 + 425 + + + + + + + + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + Restart + + + + + + + Restart i2pd + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + Quit + + + + + + + Quit Now + + + + + + + Graceful Quit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + handleQuitButton() + handleGracefulQuitButton() + + diff --git a/qt/i2pd_qt/pagewithbackbutton.cpp b/qt/i2pd_qt/pagewithbackbutton.cpp new file mode 100644 index 00000000..bc297ac2 --- /dev/null +++ b/qt/i2pd_qt/pagewithbackbutton.cpp @@ -0,0 +1,24 @@ +#include "pagewithbackbutton.h" +#include "QVBoxLayout" +#include "QHBoxLayout" +#include "QPushButton" + +PageWithBackButton::PageWithBackButton(QWidget *parent, QWidget* child) : QWidget(parent) +{ + QVBoxLayout * layout = new QVBoxLayout(); + setLayout(layout); + QWidget * topBar = new QWidget(); + QHBoxLayout * topBarLayout = new QHBoxLayout(); + topBar->setLayout(topBarLayout); + layout->addWidget(topBar); + layout->addWidget(child); + + QPushButton * backButton = new QPushButton(topBar); + backButton->setText("< Back"); + topBarLayout->addWidget(backButton); + connect(backButton, SIGNAL(released()), this, SLOT(backReleasedSlot())); +} + +void PageWithBackButton::backReleasedSlot() { + emit backReleased(); +} diff --git a/qt/i2pd_qt/pagewithbackbutton.h b/qt/i2pd_qt/pagewithbackbutton.h new file mode 100644 index 00000000..60779f80 --- /dev/null +++ b/qt/i2pd_qt/pagewithbackbutton.h @@ -0,0 +1,21 @@ +#ifndef PAGEWITHBACKBUTTON_H +#define PAGEWITHBACKBUTTON_H + +#include + +class PageWithBackButton : public QWidget +{ + Q_OBJECT +public: + explicit PageWithBackButton(QWidget *parent, QWidget* child); + +signals: + + void backReleased(); + +private slots: + + void backReleasedSlot(); +}; + +#endif // PAGEWITHBACKBUTTON_H diff --git a/qt/i2pd_qt/resources/icons/mask.ico b/qt/i2pd_qt/resources/icons/mask.ico new file mode 100644 index 00000000..f5807de5 Binary files /dev/null and b/qt/i2pd_qt/resources/icons/mask.ico differ diff --git a/qt/i2pd_qt/resources/images/icon.png b/qt/i2pd_qt/resources/images/icon.png new file mode 100644 index 00000000..a5dc7b68 Binary files /dev/null and b/qt/i2pd_qt/resources/images/icon.png differ diff --git a/qt/i2pd_qt/routercommandswidget.ui b/qt/i2pd_qt/routercommandswidget.ui new file mode 100644 index 00000000..c5098e8e --- /dev/null +++ b/qt/i2pd_qt/routercommandswidget.ui @@ -0,0 +1,127 @@ + + + routerCommandsWidget + + + + 0 + 0 + 711 + 300 + + + + + 0 + 0 + + + + Form + + + + + 0 + 0 + 711 + 301 + + + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + + 75 + true + + + + Router Commands + + + + + + + + 0 + 0 + + + + Run peer test + + + + + + + + 0 + 0 + + + + Decline transit tunnels + + + + + + + + 0 + 0 + + + + Accept transit tunnels + + + + + + + false + + + + 0 + 0 + + + + Cancel graceful quit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + diff --git a/qt/i2pd_qt/statusbuttons.ui b/qt/i2pd_qt/statusbuttons.ui new file mode 100644 index 00000000..edf5a90c --- /dev/null +++ b/qt/i2pd_qt/statusbuttons.ui @@ -0,0 +1,163 @@ + + + StatusButtonsForm + + + + 0 + 0 + 171 + 295 + + + + + 0 + 0 + + + + + 171 + 295 + + + + Form + + + + + 21 + 0 + 171 + 300 + + + + + QLayout::SetDefaultConstraint + + + + + + 150 + 16777215 + + + + Main page + + + + + + + + 150 + 16777215 + + + + Router commands + + + + + + + + 150 + 16777215 + + + + Local destinations + + + + + + + + 150 + 16777215 + + + + Leasesets + + + + + + + + 150 + 16777215 + + + + Tunnels + + + + + + + + 150 + 16777215 + + + + Transit tunnels + + + + + + + + 150 + 16777215 + + + + Transports + + + + + + + + 150 + 16777215 + + + + I2P tunnels + + + + + + + + 150 + 16777215 + + + + SAM sessions + + + + + + + + + diff --git a/qt/i2pd_qt/textbrowsertweaked1.cpp b/qt/i2pd_qt/textbrowsertweaked1.cpp new file mode 100644 index 00000000..f8802061 --- /dev/null +++ b/qt/i2pd_qt/textbrowsertweaked1.cpp @@ -0,0 +1,9 @@ +#include "textbrowsertweaked1.h" + +TextBrowserTweaked1::TextBrowserTweaked1(QWidget * parent): QTextBrowser(parent) +{ +} + +/*void TextBrowserTweaked1::setSource(const QUrl & url) { + emit navigatedTo(url); +}*/ diff --git a/qt/i2pd_qt/textbrowsertweaked1.h b/qt/i2pd_qt/textbrowsertweaked1.h new file mode 100644 index 00000000..288a3c32 --- /dev/null +++ b/qt/i2pd_qt/textbrowsertweaked1.h @@ -0,0 +1,26 @@ +#ifndef TEXTBROWSERTWEAKED1_H +#define TEXTBROWSERTWEAKED1_H + +#include +#include + +class TextBrowserTweaked1 : public QTextBrowser +{ + Q_OBJECT + +public: + TextBrowserTweaked1(QWidget * parent); + //virtual void setSource(const QUrl & url); + +signals: + void mouseReleased(); + //void navigatedTo(const QUrl & link); + +protected: + void mouseReleaseEvent(QMouseEvent *event) { + QTextBrowser::mouseReleaseEvent(event); + emit mouseReleased(); + } +}; + +#endif // TEXTBROWSERTWEAKED1_H diff --git a/qt/i2pd_qt/tunnelform.ui b/qt/i2pd_qt/tunnelform.ui new file mode 100644 index 00000000..61e54770 --- /dev/null +++ b/qt/i2pd_qt/tunnelform.ui @@ -0,0 +1,104 @@ + + + Form + + + + 0 + 0 + 527 + 452 + + + + Form + + + + + 0 + 0 + 521 + 451 + + + + + + + server_tunnel_name + + + + + 0 + 20 + 511 + 421 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + + + + + Host: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/widgetlock.cpp b/qt/i2pd_qt/widgetlock.cpp new file mode 100644 index 00000000..a601fb36 --- /dev/null +++ b/qt/i2pd_qt/widgetlock.cpp @@ -0,0 +1 @@ +#include "widgetlock.h" diff --git a/qt/i2pd_qt/widgetlock.h b/qt/i2pd_qt/widgetlock.h new file mode 100644 index 00000000..5b21125c --- /dev/null +++ b/qt/i2pd_qt/widgetlock.h @@ -0,0 +1,35 @@ +#ifndef WIDGETLOCK_H +#define WIDGETLOCK_H + +#include +#include +#include +#include + +class widgetlock : public QObject { + Q_OBJECT + +private: + QWidget* widget; + QPushButton* lockButton; + +public slots: + void lockButtonClicked(bool) { + bool wasEnabled = widget->isEnabled(); + widget->setEnabled(!wasEnabled); + lockButton->setText(widget->isEnabled()?lockButton->tr("Lock"):lockButton->tr("Edit")); + } + +public: + widgetlock(QWidget* widget_, QPushButton* lockButton_): widget(widget_),lockButton(lockButton_) { + widget->setEnabled(false); + lockButton->setText(lockButton->tr("Edit")); + QObject::connect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); + } + virtual ~widgetlock() {} + void deleteListener() { + QObject::disconnect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); + } +}; + +#endif // WIDGETLOCK_H diff --git a/qt/i2pd_qt/widgetlockregistry.cpp b/qt/i2pd_qt/widgetlockregistry.cpp new file mode 100644 index 00000000..0d66d327 --- /dev/null +++ b/qt/i2pd_qt/widgetlockregistry.cpp @@ -0,0 +1,2 @@ +#include "widgetlockregistry.h" + diff --git a/qt/i2pd_qt/widgetlockregistry.h b/qt/i2pd_qt/widgetlockregistry.h new file mode 100644 index 00000000..78ee142a --- /dev/null +++ b/qt/i2pd_qt/widgetlockregistry.h @@ -0,0 +1,27 @@ +#ifndef WIDGETLOCKREGISTRY_H +#define WIDGETLOCKREGISTRY_H + +#include +#include + +class widgetlockregistry { + std::vector locks; + +public: + widgetlockregistry() : locks() {} + virtual ~widgetlockregistry() {} + void add(widgetlock* lock) { + locks.push_back(lock); + } + + void deleteListeners() { + while(!locks.empty()) { + widgetlock* lock = locks.back(); + lock->deleteListener(); + delete lock; + locks.pop_back(); + } + } +}; + +#endif // WIDGETLOCKREGISTRY_H 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..498cff17 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,66 +1,29 @@ -SYS := $(shell $(CXX) -dumpmachine) - -CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++17 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files -INCFLAGS += -I../libi2pd - -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 +CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files +TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 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-blinding: test-blinding.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) - -test-elligator: test-elligator.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) - -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-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.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-blinding.cpp b/tests/test-blinding.cpp deleted file mode 100644 index 10b72e4f..00000000 --- a/tests/test-blinding.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include "Blinding.h" -#include "Identity.h" -#include "Timestamp.h" - -using namespace i2p::data; -using namespace i2p::util; -using namespace i2p::crypto; - -void BlindTest (SigningKeyType sigType) -{ - auto keys = PrivateKeys::CreateRandomKeys (sigType); - BlindedPublicKey blindedKey (keys.GetPublic ()); - auto timestamp = GetSecondsSinceEpoch (); - char date[9]; - GetDateString (timestamp, date); - uint8_t blindedPriv[32], blindedPub[32]; - auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - uint8_t blindedPub1[32]; - blindedKey.GetBlindedKey (date, blindedPub1); - // check if public key produced from private blinded key matches blided public key - assert (!memcmp (blindedPub, blindedPub1, publicKeyLen)); - // try to sign and verify - std::unique_ptr blindedSigner (PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); - uint8_t buf[100], signature[64]; - 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)); -} - -int main () -{ - // EdDSA test - BlindTest (SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); - // RedDSA test - BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); -} diff --git a/tests/test-eddsa.cpp b/tests/test-eddsa.cpp deleted file mode 100644 index b3895e2b..00000000 --- a/tests/test-eddsa.cpp +++ /dev/null @@ -1,68 +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); -#if OPENSSL_EDDSA - assert(memcmp (s, sig, 64) == 0); -#endif - - i2p::crypto::EDDSA25519Verifier verifier; - verifier.SetPublicKey (pub); - assert(verifier.Verify (msg, 1023, s)); -} diff --git a/tests/test-elligator.cpp b/tests/test-elligator.cpp deleted file mode 100644 index 359c71c5..00000000 --- a/tests/test-elligator.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include - -#include "Elligator.h" - -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] = -{ - 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] = -{ - 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 -}; - -const uint8_t encoded1[32] = -{ - 0xe7, 0x35, 0x07, 0xd3, 0x8b, 0xae, 0x63, 0x99, 0x2b, 0x3f, 0x57, 0xaa, 0xc4, 0x8c, 0x0a, 0xbc, - 0x14, 0x50, 0x95, 0x89, 0x28, 0x84, 0x57, 0x99, 0x5a, 0x2b, 0x4c, 0xa3, 0x49, 0x0a, 0xa2, 0x07 -}; - -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 -}; - -const uint8_t encoded2[32] = -{ - 0x95, 0xa1, 0x60, 0x19, 0x04, 0x1d, 0xbe, 0xfe, 0xd9, 0x83, 0x20, 0x48, 0xed, 0xe1, 0x19, 0x28, - 0xd9, 0x03, 0x65, 0xf2, 0x4a, 0x38, 0xaa, 0x7a, 0xef, 0x1b, 0x97, 0xe2, 0x39, 0x54, 0x10, 0x1b -}; - -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 -}; - -const uint8_t encoded3[32] = -{ - 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f -}; - -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 -}; - -const uint8_t failed_key[32] = -{ - 0xe6, 0xf6, 0x6f, 0xdf, 0x6e, 0x23, 0x0c, 0x60, 0x3c, 0x5e, 0x6e, 0x59, 0xa2, 0x54, 0xea, 0x14, - 0x76, 0xa1, 0x3e, 0xb9, 0x51, 0x1b, 0x95, 0x49, 0x84, 0x67, 0x81, 0xe1, 0x2e, 0x52, 0x23, 0x0a -}; - -int main () -{ - uint8_t buf[32]; - i2p::crypto::Elligator2 el; - // encoding tests - el.Encode (key, buf, false, false); - assert(memcmp (buf, encoded_key, 32) == 0); - el.Encode (key, buf, true, false); // with highY - assert(memcmp (buf, encoded_key_high_y, 32) == 0); - // decoding tests - el.Decode (encoded1, buf); - assert(memcmp (buf, key1, 32) == 0); - el.Decode (encoded2, buf); - assert(memcmp (buf, key2, 32) == 0); - el.Decode (encoded3, buf); - assert(memcmp (buf, key3, 32) == 0); - // encoding fails - assert (!el.Encode (failed_key, buf)); -} diff --git a/tests/test-gost-sig.cpp b/tests/test-gost-sig.cpp index 1348af54..55cc2d1b 100644 --- a/tests/test-gost-sig.cpp +++ b/tests/test-gost-sig.cpp @@ -21,14 +21,12 @@ int main () i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub); i2p::crypto::GOSTR3410_512_Signer signer (i2p::crypto::eGOSTR3410TC26A512, priv); signer.Sign (example2, 72, signature); - i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512); - verifier.SetPublicKey (pub); + i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512, pub); assert (verifier.Verify (example2, 72, signature)); i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub); i2p::crypto::GOSTR3410_256_Signer signer1 (i2p::crypto::eGOSTR3410CryptoProA, priv); signer1.Sign (example2, 72, signature); - i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA); - verifier1.SetPublicKey (pub); + i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA, pub); assert (verifier1.Verify (example2, 72, signature)); } 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..9f249dbd --- /dev/null +++ b/tests/test-x25519.cpp @@ -0,0 +1,36 @@ +#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 () +{ + 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); +} +